From adbe4541cb7dfffed0ac2112a7259b25f88014a8 Mon Sep 17 00:00:00 2001 From: Aleksandr Lebedev Date: Tue, 27 Jan 2026 20:45:34 +0100 Subject: [PATCH] Dotfiles --- alacritty/.config/alacritty/alacritty.toml | 2 + emacs/.config/emacs/config.el | 1075 +++ emacs/.config/emacs/init.el | 15 + git/.config/git/config | 7 + niri/.config/niri/config.kdl | 631 ++ quickshell/.config/quickshell/.gitignore | 99 + .../.config/quickshell/Common/Anims.qml | 25 + .../quickshell/Common/AppUsageHistoryData.qml | 131 + .../.config/quickshell/Common/Appearance.qml | 66 + .../.config/quickshell/Common/CacheUtils.qml | 45 + .../quickshell/Common/ModalManager.qml | 16 + .../.config/quickshell/Common/Paths.qml | 61 + quickshell/.config/quickshell/Common/Ref.qml | 9 + .../.config/quickshell/Common/SessionData.qml | 597 ++ .../quickshell/Common/SettingsData.qml | 1123 +++ .../.config/quickshell/Common/StockThemes.js | 380 + .../.config/quickshell/Common/Theme.qml | 819 ++ quickshell/.config/quickshell/Common/fzf.js | 1307 +++ .../quickshell/Common/markdown2html.js | 106 + .../Modals/Clipboard/ClipboardConstants.qml | 19 + .../Modals/Clipboard/ClipboardContent.qml | 169 + .../Modals/Clipboard/ClipboardEntry.qml | 137 + .../Modals/Clipboard/ClipboardHeader.qml | 65 + .../Clipboard/ClipboardHistoryModal.qml | 216 + .../Clipboard/ClipboardKeyboardController.qml | 95 + .../Clipboard/ClipboardKeyboardHints.qml | 42 + .../Modals/Clipboard/ClipboardProcesses.qml | 94 + .../Modals/Clipboard/ClipboardThumbnail.qml | 174 + .../quickshell/Modals/Common/ConfirmModal.qml | 227 + .../quickshell/Modals/Common/DankModal.qml | 234 + .../Modals/FileBrowser/FileBrowserModal.qml | 818 ++ .../Modals/FileBrowser/FileInfo.qml | 237 + .../Modals/FileBrowser/KeyboardHints.qml | 50 + .../quickshell/Modals/NetworkInfoModal.qml | 162 + .../quickshell/Modals/NotificationModal.qml | 146 + .../quickshell/Modals/PowerMenuModal.qml | 345 + .../quickshell/Modals/ProcessListModal.qml | 356 + .../Modals/Settings/ProfileSection.qml | 211 + .../Modals/Settings/SettingsContent.qml | 142 + .../Modals/Settings/SettingsModal.qml | 198 + .../Modals/Settings/SettingsSidebar.qml | 130 + .../Modals/Spotlight/SpotlightContent.qml | 226 + .../Modals/Spotlight/SpotlightContextMenu.qml | 205 + .../Modals/Spotlight/SpotlightModal.qml | 109 + .../Modals/Spotlight/SpotlightResults.qml | 324 + .../quickshell/Modals/WifiPasswordModal.qml | 296 + .../Modules/AppDrawer/AppDrawerPopout.qml | 821 ++ .../Modules/AppDrawer/AppLauncher.qml | 168 + .../Modules/AppDrawer/CategorySelector.qml | 143 + .../ControlCenter/ControlCenterPopout.qml | 750 ++ .../Details/AudioInputDetail.qml | 178 + .../Details/AudioOutputDetail.qml | 138 + .../Details/BluetoothCodecSelector.qml | 307 + .../ControlCenter/Details/BluetoothDetail.qml | 543 ++ .../ControlCenter/Details/NetworkDetail.qml | 533 ++ .../Modules/ControlCenter/PowerMenu.qml | 310 + .../ControlCenter/Widgets/AudioInputPill.qml | 57 + .../ControlCenter/Widgets/AudioOutputPill.qml | 60 + .../ControlCenter/Widgets/AudioSlider.qml | 50 + .../ControlCenter/Widgets/AudioSliderRow.qml | 75 + .../ControlCenter/Widgets/BasePill.qml | 149 + .../ControlCenter/Widgets/BluetoothPill.qml | 66 + .../Widgets/BrightnessSlider.qml | 34 + .../Widgets/BrightnessSliderRow.qml | 142 + .../ControlCenter/Widgets/CompactSlider.qml | 70 + .../ControlCenter/Widgets/DetailView.qml | 29 + .../ControlCenter/Widgets/NetworkPill.qml | 72 + .../ControlCenter/Widgets/SimpleSlider.qml | 50 + .../ControlCenter/Widgets/ToggleButton.qml | 95 + .../Modules/DankDash/DankDashPopout.qml | 315 + .../Modules/DankDash/MediaPlayerTab.qml | 759 ++ .../Overview/CalendarOverviewCard.qml | 447 + .../Modules/DankDash/Overview/Card.qml | 22 + .../Modules/DankDash/Overview/ClockCard.qml | 98 + .../DankDash/Overview/MediaOverviewCard.qml | 481 + .../DankDash/Overview/SystemMonitorCard.qml | 178 + .../DankDash/Overview/UserInfoCard.qml | 167 + .../DankDash/Overview/WeatherOverviewCard.qml | 91 + .../Modules/DankDash/OverviewTab.qml | 76 + .../Modules/DankDash/WallpaperTab.qml | 525 ++ .../Modules/DankDash/WeatherTab.qml | 642 ++ .../Modules/Lock/CustomButtonKeyboard.qml | 24 + .../quickshell/Modules/Lock/Keyboard.qml | 361 + .../Modules/Lock/KeyboardController.qml | 36 + .../.config/quickshell/Modules/Lock/Lock.qml | 153 + .../Modules/Lock/LockScreenContent.qml | 1010 ++ .../Modules/Lock/LockScreenDemo.qml | 45 + .../quickshell/Modules/Lock/LockSurface.qml | 37 + .../KeyboardNavigatedNotificationList.qml | 137 + .../Notifications/Center/NotificationCard.qml | 728 ++ .../Center/NotificationCenterPopout.qml | 202 + .../Center/NotificationEmptyState.qml | 34 + .../Center/NotificationHeader.qml | 157 + .../Center/NotificationKeyboardController.qml | 459 + .../Center/NotificationKeyboardHints.qml | 50 + .../Center/NotificationSettings.qml | 255 + .../Notifications/Popup/NotificationPopup.qml | 586 ++ .../Popup/NotificationPopupManager.qml | 271 + .../quickshell/Modules/OSD/BrightnessOSD.qml | 134 + .../Modules/OSD/IdleInhibitorOSD.qml | 27 + .../quickshell/Modules/OSD/MicMuteOSD.qml | 27 + .../quickshell/Modules/OSD/VolumeOSD.qml | 120 + .../Modules/ProcessList/PerformanceTab.qml | 483 + .../ProcessList/ProcessContextMenu.qml | 235 + .../Modules/ProcessList/ProcessListItem.qml | 200 + .../Modules/ProcessList/ProcessListPopout.qml | 138 + .../Modules/ProcessList/ProcessListView.qml | 260 + .../Modules/ProcessList/ProcessesTab.qml | 28 + .../Modules/ProcessList/SystemOverview.qml | 435 + .../Modules/ProcessList/SystemTab.qml | 613 ++ .../quickshell/Modules/Settings/AboutTab.qml | 535 ++ .../Modules/Settings/DisplaysTab.qml | 369 + .../Modules/Settings/PersonalizationTab.qml | 1439 +++ .../Modules/Settings/RecentAppsTab.qml | 246 + .../Modules/Settings/SettingsSection.qml | 106 + .../Modules/Settings/ThemeColorsTab.qml | 942 ++ .../quickshell/Modules/Settings/TimeTab.qml | 384 + .../quickshell/Modules/Settings/TopBarTab.qml | 1206 +++ .../Modules/Settings/WeatherTab.qml | 308 + .../Modules/Settings/WidgetSelectionPopup.qml | 310 + .../Modules/Settings/WidgetTweaksTab.qml | 510 + .../Modules/Settings/WidgetsTabSection.qml | 761 ++ .../.config/quickshell/Modules/Toast.qml | 336 + .../Modules/TopBar/AudioVisualization.qml | 77 + .../quickshell/Modules/TopBar/Battery.qml | 259 + .../Modules/TopBar/BatteryPopout.qml | 591 ++ .../quickshell/Modules/TopBar/Clock.qml | 99 + .../Modules/TopBar/ControlCenterButton.qml | 184 + .../quickshell/Modules/TopBar/CpuMonitor.qml | 99 + .../Modules/TopBar/CpuTemperature.qml | 107 + .../quickshell/Modules/TopBar/FocusedApp.qml | 129 + .../Modules/TopBar/GpuTemperature.qml | 198 + .../Modules/TopBar/IdleInhibitor.qml | 57 + .../Modules/TopBar/KeyboardLayoutName.qml | 59 + .../Modules/TopBar/LauncherButton.qml | 83 + .../quickshell/Modules/TopBar/Media.qml | 343 + .../Modules/TopBar/NetworkMonitor.qml | 117 + .../Modules/TopBar/NotepadButton.qml | 71 + .../TopBar/NotificationCenterButton.qml | 78 + .../Modules/TopBar/PrivacyIndicator.qml | 170 + .../quickshell/Modules/TopBar/RamMonitor.qml | 99 + .../quickshell/Modules/TopBar/RunningApps.qml | 437 + .../Modules/TopBar/RunningAppsTooltip.qml | 67 + .../Modules/TopBar/SystemTrayBar.qml | 132 + .../quickshell/Modules/TopBar/TopBar.qml | 1007 ++ .../.config/quickshell/Modules/TopBar/Vpn.qml | 113 + .../quickshell/Modules/TopBar/VpnPopout.qml | 418 + .../quickshell/Modules/TopBar/Weather.qml | 98 + .../Modules/TopBar/WorkspaceSwitcher.qml | 379 + .../Modules/WallpaperBackground.qml | 135 + quickshell/.config/quickshell/README.md | 604 ++ .../quickshell/Services/AppSearchService.qml | 178 + .../quickshell/Services/AudioService.qml | 211 + .../quickshell/Services/BatteryService.qml | 228 + .../quickshell/Services/BluetoothService.qml | 485 + .../quickshell/Services/CalendarService.qml | 250 + .../quickshell/Services/CavaService.qml | 58 + .../quickshell/Services/CompositorService.qml | 69 + .../quickshell/Services/DesktopService.qml | 73 + .../quickshell/Services/DgopService.qml | 699 ++ .../quickshell/Services/DisplayService.qml | 974 ++ .../quickshell/Services/LockScreenService.qml | 68 + .../quickshell/Services/MprisController.qml | 60 + .../quickshell/Services/NetworkService.qml | 1042 ++ .../quickshell/Services/NiriService.qml | 566 ++ .../Services/NotificationService.qml | 687 ++ .../quickshell/Services/PortalService.qml | 210 + .../quickshell/Services/PrivacyService.qml | 144 + .../quickshell/Services/SessionService.qml | 187 + .../quickshell/Services/ToastService.qml | 105 + .../quickshell/Services/UserInfoService.qml | 115 + .../quickshell/Services/VpnService.qml | 240 + .../Services/WallpaperCyclingService.qml | 239 + .../quickshell/Services/WeatherService.qml | 646 ++ .../quickshell/Widgets/CachingImage.qml | 57 + .../quickshell/Widgets/DankActionButton.qml | 34 + .../quickshell/Widgets/DankBackdrop.qml | 63 + .../quickshell/Widgets/DankColorPicker.qml | 117 + .../quickshell/Widgets/DankDropdown.qml | 348 + .../quickshell/Widgets/DankFlickable.qml | 169 + .../quickshell/Widgets/DankGridView.qml | 159 + .../.config/quickshell/Widgets/DankIcon.qml | 41 + .../quickshell/Widgets/DankIconPicker.qml | 291 + .../quickshell/Widgets/DankListView.qml | 182 + .../quickshell/Widgets/DankLocationSearch.qml | 275 + .../.config/quickshell/Widgets/DankOSD.qml | 155 + .../.config/quickshell/Widgets/DankPopout.qml | 150 + .../quickshell/Widgets/DankScrollbar.qml | 43 + .../.config/quickshell/Widgets/DankSlider.qml | 209 + .../.config/quickshell/Widgets/DankTabBar.qml | 253 + .../quickshell/Widgets/DankTextField.qml | 178 + .../.config/quickshell/Widgets/DankToggle.qml | 120 + .../.config/quickshell/Widgets/StateLayer.qml | 22 + .../.config/quickshell/Widgets/StyledRect.qml | 37 + .../.config/quickshell/Widgets/StyledText.qml | 51 + .../.config/quickshell/Widgets/SystemLogo.qml | 36 + quickshell/.config/quickshell/alejandra.toml | 1 + quickshell/.config/quickshell/assets/dank.svg | 49 + .../.config/quickshell/assets/discord.svg | 1 + .../.config/quickshell/assets/hyprland.svg | 32 + .../quickshell/assets/matrix-logo-white.svg | 18 + quickshell/.config/quickshell/assets/niri.svg | 405 + .../.config/quickshell/assets/reddit.svg | 20 + .../.config/quickshell/docs/CUSTOM_THEMES.md | 137 + quickshell/.config/quickshell/docs/IPC.md | 578 ++ .../docs/theme_cyberpunk_electric.json | 42 + .../quickshell/docs/theme_hotline_miami.json | 42 + .../quickshell/docs/theme_miami_vice.json | 42 + .../docs/theme_synthwave_electric.json | 42 + .../.config/quickshell/matugen-config.toml | 33 + .../quickshell/matugen/configs/base.toml | 13 + .../quickshell/matugen/configs/dgop.toml | 3 + .../quickshell/matugen/configs/ghostty.toml | 3 + .../quickshell/matugen/configs/kitty.toml | 3 + .../quickshell/matugen/configs/niri.toml | 3 + .../quickshell/matugen/configs/qt5ct.toml | 3 + .../quickshell/matugen/configs/qt6ct.toml | 3 + .../.config/quickshell/matugen/dank16.py | 252 + .../quickshell/matugen/templates/dgop.json | 49 + .../quickshell/matugen/templates/ghostty.conf | 5 + .../matugen/templates/gtk-colors.css | 92 + .../matugen/templates/gtk3-colloid-dark.css | 8372 +++++++++++++++++ .../matugen/templates/gtk3-colloid-light.css | 975 ++ .../quickshell/matugen/templates/kitty.conf | 8 + .../templates/matugen-kcolorscheme.colors | 146 + .../matugen/templates/niri-colors.kdl | 29 + .../matugen/templates/qtct-colors.conf | 144 + .../.config/quickshell/qmlformat-all.sh | 7 + quickshell/.config/quickshell/scripts/gtk.sh | 66 + .../quickshell/scripts/matugen-worker.sh | 206 + quickshell/.config/quickshell/scripts/qt.sh | 73 + quickshell/.config/quickshell/shell.qml | 438 + .../.config/quickshell/spam-notifications.sh | 207 + .../quickshell/verify-notifications.sh | 86 + .../.local/share/fonts/FiraCode-Regular.ttf | 1 + .../.local/share/fonts/InterVariable.ttf | Bin 0 -> 879708 bytes .../share/fonts/MaterialSymbolsRounded.ttf | Bin 0 -> 14586584 bytes 237 files changed, 64642 insertions(+) create mode 100644 alacritty/.config/alacritty/alacritty.toml create mode 100644 emacs/.config/emacs/config.el create mode 100644 emacs/.config/emacs/init.el create mode 100644 git/.config/git/config create mode 100644 niri/.config/niri/config.kdl create mode 100644 quickshell/.config/quickshell/.gitignore create mode 100644 quickshell/.config/quickshell/Common/Anims.qml create mode 100644 quickshell/.config/quickshell/Common/AppUsageHistoryData.qml create mode 100644 quickshell/.config/quickshell/Common/Appearance.qml create mode 100644 quickshell/.config/quickshell/Common/CacheUtils.qml create mode 100644 quickshell/.config/quickshell/Common/ModalManager.qml create mode 100644 quickshell/.config/quickshell/Common/Paths.qml create mode 100644 quickshell/.config/quickshell/Common/Ref.qml create mode 100644 quickshell/.config/quickshell/Common/SessionData.qml create mode 100644 quickshell/.config/quickshell/Common/SettingsData.qml create mode 100644 quickshell/.config/quickshell/Common/StockThemes.js create mode 100644 quickshell/.config/quickshell/Common/Theme.qml create mode 100644 quickshell/.config/quickshell/Common/fzf.js create mode 100644 quickshell/.config/quickshell/Common/markdown2html.js create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardConstants.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardContent.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardEntry.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardHeader.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardKeyboardController.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardKeyboardHints.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardProcesses.qml create mode 100644 quickshell/.config/quickshell/Modals/Clipboard/ClipboardThumbnail.qml create mode 100644 quickshell/.config/quickshell/Modals/Common/ConfirmModal.qml create mode 100644 quickshell/.config/quickshell/Modals/Common/DankModal.qml create mode 100644 quickshell/.config/quickshell/Modals/FileBrowser/FileBrowserModal.qml create mode 100644 quickshell/.config/quickshell/Modals/FileBrowser/FileInfo.qml create mode 100644 quickshell/.config/quickshell/Modals/FileBrowser/KeyboardHints.qml create mode 100644 quickshell/.config/quickshell/Modals/NetworkInfoModal.qml create mode 100644 quickshell/.config/quickshell/Modals/NotificationModal.qml create mode 100644 quickshell/.config/quickshell/Modals/PowerMenuModal.qml create mode 100644 quickshell/.config/quickshell/Modals/ProcessListModal.qml create mode 100644 quickshell/.config/quickshell/Modals/Settings/ProfileSection.qml create mode 100644 quickshell/.config/quickshell/Modals/Settings/SettingsContent.qml create mode 100644 quickshell/.config/quickshell/Modals/Settings/SettingsModal.qml create mode 100644 quickshell/.config/quickshell/Modals/Settings/SettingsSidebar.qml create mode 100644 quickshell/.config/quickshell/Modals/Spotlight/SpotlightContent.qml create mode 100644 quickshell/.config/quickshell/Modals/Spotlight/SpotlightContextMenu.qml create mode 100644 quickshell/.config/quickshell/Modals/Spotlight/SpotlightModal.qml create mode 100644 quickshell/.config/quickshell/Modals/Spotlight/SpotlightResults.qml create mode 100644 quickshell/.config/quickshell/Modals/WifiPasswordModal.qml create mode 100644 quickshell/.config/quickshell/Modules/AppDrawer/AppDrawerPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/AppDrawer/AppLauncher.qml create mode 100644 quickshell/.config/quickshell/Modules/AppDrawer/CategorySelector.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/ControlCenterPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Details/AudioInputDetail.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Details/AudioOutputDetail.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Details/BluetoothCodecSelector.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/PowerMenu.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/AudioInputPill.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/AudioOutputPill.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/AudioSlider.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/AudioSliderRow.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/BasePill.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/BluetoothPill.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/BrightnessSlider.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/BrightnessSliderRow.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/CompactSlider.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/DetailView.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/NetworkPill.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/SimpleSlider.qml create mode 100644 quickshell/.config/quickshell/Modules/ControlCenter/Widgets/ToggleButton.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/DankDashPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/MediaPlayerTab.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/CalendarOverviewCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/Card.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/ClockCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/MediaOverviewCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/SystemMonitorCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/UserInfoCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/Overview/WeatherOverviewCard.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/OverviewTab.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/WallpaperTab.qml create mode 100644 quickshell/.config/quickshell/Modules/DankDash/WeatherTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/CustomButtonKeyboard.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/Keyboard.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/KeyboardController.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/Lock.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/LockScreenContent.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/LockScreenDemo.qml create mode 100644 quickshell/.config/quickshell/Modules/Lock/LockSurface.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationCard.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationEmptyState.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationHeader.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationKeyboardController.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationKeyboardHints.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Center/NotificationSettings.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Popup/NotificationPopup.qml create mode 100644 quickshell/.config/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml create mode 100644 quickshell/.config/quickshell/Modules/OSD/BrightnessOSD.qml create mode 100644 quickshell/.config/quickshell/Modules/OSD/IdleInhibitorOSD.qml create mode 100644 quickshell/.config/quickshell/Modules/OSD/MicMuteOSD.qml create mode 100644 quickshell/.config/quickshell/Modules/OSD/VolumeOSD.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/PerformanceTab.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/ProcessContextMenu.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/ProcessListItem.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/ProcessListPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/ProcessListView.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/ProcessesTab.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/SystemOverview.qml create mode 100644 quickshell/.config/quickshell/Modules/ProcessList/SystemTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/AboutTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/DisplaysTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/PersonalizationTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/RecentAppsTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/SettingsSection.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/ThemeColorsTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/TimeTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/TopBarTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/WeatherTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/WidgetSelectionPopup.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/WidgetTweaksTab.qml create mode 100644 quickshell/.config/quickshell/Modules/Settings/WidgetsTabSection.qml create mode 100644 quickshell/.config/quickshell/Modules/Toast.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/AudioVisualization.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/Battery.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/BatteryPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/Clock.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/ControlCenterButton.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/CpuMonitor.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/CpuTemperature.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/FocusedApp.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/GpuTemperature.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/IdleInhibitor.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/KeyboardLayoutName.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/LauncherButton.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/Media.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/NetworkMonitor.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/NotepadButton.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/NotificationCenterButton.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/PrivacyIndicator.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/RamMonitor.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/RunningApps.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/RunningAppsTooltip.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/SystemTrayBar.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/TopBar.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/Vpn.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/VpnPopout.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/Weather.qml create mode 100644 quickshell/.config/quickshell/Modules/TopBar/WorkspaceSwitcher.qml create mode 100644 quickshell/.config/quickshell/Modules/WallpaperBackground.qml create mode 100644 quickshell/.config/quickshell/README.md create mode 100644 quickshell/.config/quickshell/Services/AppSearchService.qml create mode 100644 quickshell/.config/quickshell/Services/AudioService.qml create mode 100644 quickshell/.config/quickshell/Services/BatteryService.qml create mode 100644 quickshell/.config/quickshell/Services/BluetoothService.qml create mode 100644 quickshell/.config/quickshell/Services/CalendarService.qml create mode 100644 quickshell/.config/quickshell/Services/CavaService.qml create mode 100644 quickshell/.config/quickshell/Services/CompositorService.qml create mode 100644 quickshell/.config/quickshell/Services/DesktopService.qml create mode 100644 quickshell/.config/quickshell/Services/DgopService.qml create mode 100644 quickshell/.config/quickshell/Services/DisplayService.qml create mode 100644 quickshell/.config/quickshell/Services/LockScreenService.qml create mode 100644 quickshell/.config/quickshell/Services/MprisController.qml create mode 100644 quickshell/.config/quickshell/Services/NetworkService.qml create mode 100644 quickshell/.config/quickshell/Services/NiriService.qml create mode 100644 quickshell/.config/quickshell/Services/NotificationService.qml create mode 100644 quickshell/.config/quickshell/Services/PortalService.qml create mode 100644 quickshell/.config/quickshell/Services/PrivacyService.qml create mode 100644 quickshell/.config/quickshell/Services/SessionService.qml create mode 100644 quickshell/.config/quickshell/Services/ToastService.qml create mode 100644 quickshell/.config/quickshell/Services/UserInfoService.qml create mode 100644 quickshell/.config/quickshell/Services/VpnService.qml create mode 100644 quickshell/.config/quickshell/Services/WallpaperCyclingService.qml create mode 100644 quickshell/.config/quickshell/Services/WeatherService.qml create mode 100644 quickshell/.config/quickshell/Widgets/CachingImage.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankActionButton.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankBackdrop.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankColorPicker.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankDropdown.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankFlickable.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankGridView.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankIcon.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankIconPicker.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankListView.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankLocationSearch.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankOSD.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankPopout.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankScrollbar.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankSlider.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankTabBar.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankTextField.qml create mode 100644 quickshell/.config/quickshell/Widgets/DankToggle.qml create mode 100644 quickshell/.config/quickshell/Widgets/StateLayer.qml create mode 100644 quickshell/.config/quickshell/Widgets/StyledRect.qml create mode 100644 quickshell/.config/quickshell/Widgets/StyledText.qml create mode 100644 quickshell/.config/quickshell/Widgets/SystemLogo.qml create mode 100644 quickshell/.config/quickshell/alejandra.toml create mode 100644 quickshell/.config/quickshell/assets/dank.svg create mode 100644 quickshell/.config/quickshell/assets/discord.svg create mode 100644 quickshell/.config/quickshell/assets/hyprland.svg create mode 100644 quickshell/.config/quickshell/assets/matrix-logo-white.svg create mode 100644 quickshell/.config/quickshell/assets/niri.svg create mode 100644 quickshell/.config/quickshell/assets/reddit.svg create mode 100644 quickshell/.config/quickshell/docs/CUSTOM_THEMES.md create mode 100644 quickshell/.config/quickshell/docs/IPC.md create mode 100644 quickshell/.config/quickshell/docs/theme_cyberpunk_electric.json create mode 100644 quickshell/.config/quickshell/docs/theme_hotline_miami.json create mode 100644 quickshell/.config/quickshell/docs/theme_miami_vice.json create mode 100644 quickshell/.config/quickshell/docs/theme_synthwave_electric.json create mode 100644 quickshell/.config/quickshell/matugen-config.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/base.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/dgop.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/ghostty.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/kitty.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/niri.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/qt5ct.toml create mode 100644 quickshell/.config/quickshell/matugen/configs/qt6ct.toml create mode 100755 quickshell/.config/quickshell/matugen/dank16.py create mode 100644 quickshell/.config/quickshell/matugen/templates/dgop.json create mode 100644 quickshell/.config/quickshell/matugen/templates/ghostty.conf create mode 100644 quickshell/.config/quickshell/matugen/templates/gtk-colors.css create mode 100644 quickshell/.config/quickshell/matugen/templates/gtk3-colloid-dark.css create mode 100644 quickshell/.config/quickshell/matugen/templates/gtk3-colloid-light.css create mode 100644 quickshell/.config/quickshell/matugen/templates/kitty.conf create mode 100644 quickshell/.config/quickshell/matugen/templates/matugen-kcolorscheme.colors create mode 100644 quickshell/.config/quickshell/matugen/templates/niri-colors.kdl create mode 100644 quickshell/.config/quickshell/matugen/templates/qtct-colors.conf create mode 100755 quickshell/.config/quickshell/qmlformat-all.sh create mode 100755 quickshell/.config/quickshell/scripts/gtk.sh create mode 100755 quickshell/.config/quickshell/scripts/matugen-worker.sh create mode 100755 quickshell/.config/quickshell/scripts/qt.sh create mode 100644 quickshell/.config/quickshell/shell.qml create mode 100755 quickshell/.config/quickshell/spam-notifications.sh create mode 100755 quickshell/.config/quickshell/verify-notifications.sh create mode 100644 quickshell/.local/share/fonts/FiraCode-Regular.ttf create mode 100644 quickshell/.local/share/fonts/InterVariable.ttf create mode 100644 quickshell/.local/share/fonts/MaterialSymbolsRounded.ttf diff --git a/alacritty/.config/alacritty/alacritty.toml b/alacritty/.config/alacritty/alacritty.toml new file mode 100644 index 0000000..77dea2b --- /dev/null +++ b/alacritty/.config/alacritty/alacritty.toml @@ -0,0 +1,2 @@ +[colors.normal] +black = "#3d3d3d" \ No newline at end of file diff --git a/emacs/.config/emacs/config.el b/emacs/.config/emacs/config.el new file mode 100644 index 0000000..4db734d --- /dev/null +++ b/emacs/.config/emacs/config.el @@ -0,0 +1,1075 @@ +(recentf-mode t) + +(use-package golden-ratio + :ensure t + :init + (setq golden-ratio-auto-scale t) + (golden-ratio-mode 1)) + +(defun kylekrein/duplicate-line() + "Duplicate current line and move cursor to it" + (interactive) + (let ((column (- (point) (point-at-bol))) + (line (let ((s (thing-at-point 'line t))) + (if s (string-remove-suffix "\n" s) "")))) + (move-end-of-line 1) + (newline) + (insert line) + (move-beginning-of-line 1) + (forward-char column))) + +(global-set-key [remap list-buffers] 'ibuffer) +(global-set-key (kbd "M-o") 'other-window) +(global-set-key (kbd "C-c o t") 'vterm-toggle) +(global-set-key (kbd "C-c o a") 'org-agenda) +(global-set-key (kbd "C-c o m") 'magit) + +(global-set-key (kbd "C-.") 'kylekrein/duplicate-line) +;;(windmove-default-keybindings) ;; move between windows with S-, S-, S-, S- + +(defun split-and-follow-horizontally () + (interactive) + (split-window-below) + (balance-windows)) + + (defun split-and-follow-vertically () + (interactive) + (split-window-right) + (balance-windows)) + + (use-package emacs + :bind (:map ctl-x-map + ("2" . split-and-follow-horizontally) + ("3" . split-and-follow-vertically)) + :custom + (info-lookup-other-window-flag t) + (help-window-select t "Switch to help buffers automatically")) +;; Auto-select new Info buffer window when it’s created. + (advice-add 'info-lookup :after + (lambda (&rest _) + (when-let (window (get-buffer-window "*info*")) + (select-window window)))) + + ;; Auto-select new window after splitting. Splitting commands almost + ;;,all use `split-window’, so advice the function for auto selection. + (advice-add 'split-window :after + (lambda (&rest _) (select-window (get-lru-window)))) + +(defun git-package (url) + (let* ((pkg-name (file-name-base (directory-file-name url))) + (pkg-sym (intern pkg-name))) + (eval + `(use-package ,pkg-sym + :vc (:url ,url :rev :newest) + :ensure nil)))) + +;;(setq select-enable-primary t) +(defun kylekrein/copy-to-clipboard (text) + (with-temp-buffer + (insert text) + (copy-region-as-kill (point-min) (point-max)) + (clipboard-kill-region (point-min) (point-max)))) + +(defun kylekrein/detect-wsl () + (and (eq system-type 'gnu/linux) + (file-exists-p "/proc/sys/fs/binfmt_misc/WSLInterop"))) + +(use-package alert + :ensure t + ) + +(use-package alert-toast :ensure t :after alert) + +(setq alert-default-style + (cond + ((kylekrein/detect-wsl) 'toast) + (t 'libnotify))) + +(unless (file-exists-p "~/.cache/emacs/tildafiles") + (make-directory "~/.cache/emacs/tildafiles")) +(setq backup-directory-alist '((".*" . "~/.cache/emacs/tildafiles"))) + +(use-package diminish :ensure t) + +(defun kylekrein/copy-emoji-to-clipboard() + (interactive) + (let ((emoji (emoji--read-emoji))) + (when emoji + (kylekrein/copy-to-clipboard emoji) + (message "Copied: %s" (current-kill 0 t))))) + +(setq ediff-split-window-function 'split-window-horizontally) +;;(setq ediff-window-setup-function 'ediff-setup-windows-plain) + +(set-face-attribute 'variable-pitch nil + :family "DejaVu Sans";;"ET Bembo" + :height 160 + :weight 'normal) +(set-face-attribute 'default nil + :family "Iosevka" + :height 150 + :weight 'medium) + +(set-face-attribute 'fixed-pitch nil + :family "Iosevka" + :height 150 + :weight 'medium) + + ;; Makes commented text and keywords italics. + ;; This is working in emacsclient but not emacs. + ;; Your font must have an italic face available. + (set-face-attribute 'font-lock-comment-face nil + :slant 'italic) + (set-face-attribute 'font-lock-keyword-face nil + :slant 'italic) + + ;; This sets the default font on all graphical frames created after restarting Emacs. + ;; Does the same thing as 'set-face-attribute default' above, but emacsclient fonts + ;; are not right unless I also add this method of setting the default font. + ;;(add-to-list 'default-frame-alist '(font . "Iosevka Mono-20")) + + ;; Uncomment the following line if line spacing needs adjusting. + (setq-default line-spacing 0.12) + + + (add-hook 'text-mode-hook #'variable-pitch-mode) + ;; Enable variable-pitch-mode in Org + (add-hook 'org-mode-hook #'variable-pitch-mode) + + ;; Ensure code blocks, tables, and special elements remain fixed-pitch + (custom-set-faces + ;; Keep code blocks, src, and tables fixed-pitch (Iosevka) + '(org-block ((t (:inherit fixed-pitch)))) + '(org-block-begin-line ((t (:inherit fixed-pitch)))) + '(org-block-end-line ((t (:inherit fixed-pitch)))) + '(org-table ((t (:inherit fixed-pitch)))) + '(org-code ((t (:inherit fixed-pitch)))) + '(org-verbatim ((t (:inherit fixed-pitch)))) + '(org-meta-line ((t (:inherit fixed-pitch)))) + '(org-checkbox ((t (:inherit fixed-pitch)))) + ) + +(electric-indent-mode -1) ;; Turn off the weird indenting that Emacs does by default. +(electric-pair-mode 1) ;; Turns on automatic parens pairing +;; The following prevents <> from auto-pairing when electric-pair-mode is on. +;; Otherwise, org-tempo is broken when you try to ) and Redo (C-c ) for windows +(setq sentence-end-double-space t) ;; Single space doesn't end a sentence + +(save-place-mode t) ;; Restore cursor place in file + +(use-package nov :ensure t) +(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) + +(use-package magit + :ensure t) + +(use-package doom-modeline + :ensure t + :init (doom-modeline-mode 1) + :config + (setq doom-modeline-height 35 ;; sets modeline height + doom-modeline-bar-width 5 ;; sets right bar width + doom-modeline-persp-name nil ;; adds perspective name to modeline + doom-modeline-time t ;; shows time + doom-modeline-persp-icon nil)) ;; adds folder icon next to persp name + +(use-package rainbow-delimiters + :ensure t + :hook ((emacs-lisp-mode . rainbow-delimiters-mode) + (clojure-mode . rainbow-delimiters-mode))) + +(setq calendar-date-style "european") +(setq calendar-week-start-day 1) + +;;Line truncation +(defun kylekrein/truncate-calendar-hook () + "Turn line truncation on." + (toggle-truncate-lines 1)) + +(add-hook 'calendar-mode-hook #'kylekrein/truncate-calendar-hook) + +;;Current month is the first +(add-hook 'calendar-initial-window-hook #'calendar-scroll-left) + +;;Calendar in org agenda +(setq org-agenda-include-diary t) + +(defadvice revert-buffer (after refresh-org-agenda-on-revert activate) +(if (member (buffer-file-name (current-buffer)) org-agenda-files) + (org-agenda-redo-all t))) + +(org-babel-do-load-languages + 'org-babel-load-languages + '((shell . t) + (C . t) + (python . t))) + +(use-package org + :config + (org-link-set-parameters + "copy" + :follow (lambda (link) (kill-new link)) + :export (lambda (_ desc &rest _) desc))) + +;;;; Better Looking Bullets +(add-hook 'org-mode-hook 'org-indent-mode) +(use-package org-bullets :ensure t) +(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1))) + +(custom-set-faces + '(org-level-1 ((t (:inherit outline-1 :height 1.45)))) + '(org-level-2 ((t (:inherit outline-2 :height 1.35)))) + '(org-level-3 ((t (:inherit outline-3 :height 1.30)))) + '(org-level-4 ((t (:inherit outline-4 :height 1.25)))) + '(org-level-5 ((t (:inherit outline-5 :height 1.20)))) + '(org-level-6 ((t (:inherit outline-5 :height 1.15)))) + '(org-level-7 ((t (:inherit outline-5 :height 1.10))))) + +(require 'org-tempo) + +(defun org-update-table-by-name (name) + "Update the named table." + (org-table-map-tables + (lambda () + (let ((table_name (org-element-property :name (org-element-at-point)))) + (if (and table_name (string-match-p name table_name)) + (org-table-recalculate)))))) + +(defun org-update-and-realign-tables () + (interactive) + (org-map-dblocks 'org-update-dblock) + (redisplay) + (org-table-map-tables 'org-table-recalculate) + (org-table-map-tables 'org-table-align)) + +(global-set-key (kbd "C-c n u") 'org-update-and-realign-tables) + +(use-package org-transclusion :ensure t) +(custom-set-faces + '(org-transclusion-fringe + ((t + (:background "green")))) + '(org-transclusion-source-fringe + ((t + (:background "blue"))))) + +(use-package org-roam + :ensure t + :init + (setq org-roam-v2-ack t) + (when (file-exists-p "~/Документы/org") + (setq org-roam-directory "~/Документы/org")) + (when (file-exists-p "~/Documents/org") + (setq org-roam-directory "~/Documents/org")) + :custom + (org-roam-completion-everywhere t) + (org-roam-capture-templates + '(("d" "default" plain + "%?" + :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n") + :unnarrowed t) + ("p" "project" plain "* Goals\n\n%?\n\n* Tasks\n\n** TODO Add initial tasks\n\n* Dates\n\n" + :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+category: ${title}\n#+filetags: Project") + :unnarrowed t)) + ) + (org-roam-dailies-capture-templates + '(("d" "default" entry "* %<%I:%M %p>: %?" + :if-new (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n")))) + :bind (("C-c n l" . org-roam-buffer-toggle) + ("C-c n f" . org-roam-node-find) + ("C-c n i" . org-roam-node-insert) + :map org-mode-map + ("C-M-i" . completion-at-point)) + :bind-keymap + ("C-c n d" . org-roam-dailies-map) + :config + (require 'org-roam-dailies) ;; Ensure the keymap is available + (org-roam-db-autosync-mode) + (org-roam-setup)) + +(defun kylekrein/org-roam-ripgrep () + (interactive) + (require 'consult) + (require 'org-roam) + (let ((consult-ripgrep-command "rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS")) + (consult-ripgrep org-roam-directory))) +(global-set-key (kbd "C-c n r") #'kylekrein/org-roam-ripgrep) + +(defun kylekrein/org-roam-capture-inbox () + (interactive) + (org-roam-capture- :node (org-roam-node-create) + :templates '(("i" "inbox" plain "* %?" + :if-new (file+head "Inbox.org" "#+title: Inbox\n#+category: Inbox\n#+filetags: Project"))))) +(global-set-key (kbd "C-c n b") #'kylekrein/org-roam-capture-inbox) + +(defun org-agenda-refresh () + "Refresh all `org-agenda' buffers." + (dolist (buffer (buffer-list)) + (with-current-buffer buffer + (when (derived-mode-p 'org-agenda-mode) + (org-agenda-maybe-redo))))) + +(defadvice org-schedule (after refresh-agenda activate) + "Refresh org-agenda." + (org-agenda-refresh)) + +(require 'org-roam-node) +(defun kylekrein/org-roam-filter-by-tag (tag-name) + (lambda (node) + (member tag-name (org-roam-node-tags node)))) + +(defun kylekrein/org-roam-list-notes-by-tag (tag-name) + (mapcar #'org-roam-node-file + (seq-filter + (kylekrein/org-roam-filter-by-tag tag-name) + (org-roam-node-list)))) + +(defun kylekrein/org-roam-refresh-agenda-list () + (interactive) + (setq org-agenda-files (kylekrein/org-roam-list-notes-by-tag "Project"))) + + +(setq org-agenda-files nil + org-roam-node-display-template "${title} ${tags}" + org-agenda-start-on-weekday 1 ;; Week starts on Monday instead of Sunday + ) +;; Build the agenda list the first time for the session +(kylekrein/org-roam-refresh-agenda-list) + +;; Log time a task was set to DONE. +(setq org-log-done (quote time)) + +;; Don't log the time a task was rescheduled or redeadlined. +(setq org-log-redeadline nil) +(setq org-log-reschedule nil) + +;; Prefer rescheduling to future dates and times +(setq org-read-date-prefer-future 'time) + +(use-package emacs + :config + ;; start warning 60 minutes before the appointment + (setq appt-message-warning-time 60) + + ;; warn me every 5 minutes + (setq appt-display-interval 15) + (setq appt-disp-window-function + (lambda (remaining new-time msg) + (alert (format "In %s minutes" remaining) + :title msg + :severity 'moderate + :category 'org-agenda + :id (intern msg)))) + + (advice-add 'appt-check + :before + (lambda (&rest args) + (org-agenda-to-appt t))) + + (appt-activate t)) +(setq alert-fade-time 50) + +(use-package org-upcoming-modeline + :ensure t + :after org + :config + (setq appt-display-mode-line nil) + (org-upcoming-modeline-mode)) + +(use-package rainbow-mode + :ensure t + :hook + ((org-mode prog-mode) . rainbow-mode)) + +(use-package gptel + :ensure t + :bind + ("C-c a c" . gptel) + ("C-c a r" . gptel-rewrite) + ("C-c a s" . gptel-send) + ("C-c a f" . gptel-add-file)) + (setq + gptel-model 'llama3.1 + gptel-backend (gptel-make-ollama "Ollama" + :host "localhost:11434" + :stream t + :models '(llama3.1 qwen2.5-coder:7b)) + gptel-track-media t + gptel-default-mode 'org-mode) +(add-hook 'gptel-post-stream-hook 'gptel-auto-scroll) +(add-hook 'gptel-post-response-functions 'gptel-end-of-response) + +(use-package eshell-syntax-highlighting + :ensure t + :after esh-mode + :config + (eshell-syntax-highlighting-global-mode +1)) + +(use-package vterm + :ensure t +) + +(use-package vterm-toggle + :ensure t + :after vterm + :config + (setq vterm-toggle-fullscreen-p nil) + (setq vterm-toggle-scope 'project) + (add-to-list 'display-buffer-alist + '((lambda (buffer-or-name _) + (let ((buffer (get-buffer buffer-or-name))) + (with-current-buffer buffer + (or (equal major-mode 'vterm-mode) + (string-prefix-p vterm-buffer-name (buffer-name buffer)))))) + (display-buffer-reuse-window display-buffer-at-bottom) + ;;(display-buffer-reuse-window display-buffer-in-direction) + ;;display-buffer-in-direction/direction/dedicated is added in emacs27 + ;;(direction . bottom) + ;;(dedicated . t) ;dedicated is supported in emacs27 + (reusable-frames . visible) + (window-height . 0.3)))) + +(git-package "https://github.com/darcamo/cmake-integration.git") +(use-package cmake-integration + :commands (cmake-integration-transient) + :custom + (cmake-integration-generator "Ninja") + (cmake-integration-use-separated-compilation-buffer-for-each-target t)) + +(defun is-cmake-project? () + "Determine if the current directory is a CMake project." + (interactive) + (if-let* ((project (project-current)) + (project-root (project-root project)) + (cmakelist-path (expand-file-name "CMakeLists.txt" project-root))) + (file-exists-p cmakelist-path))) + + +(defun cmake-integration-keybindings-mode-turn-on-in-cmake-projects () + "Turn on `cmake-integration-keybindings-mode' in CMake projects." + (when (is-cmake-project?) + (cmake-integration-keybindings-mode 1))) + + +(define-minor-mode cmake-integration-keybindings-mode + "A minor-mode for adding keybindings to compile C++ code using cmake-integration package." + nil + "cmake" + '( + ([f5] . cmake-integration-transient) ;; Open main transient menu + ([M-f9] . cmake-integration-save-and-compile) ;; Ask for the target name and compile it + ([f9] . cmake-integration-save-and-compile-last-target) ;; Recompile the last target + ([C-f9] . cmake-integration-run-ctest) ;; Run CTest + ([f7] . cmake-integration-run-last-target) ;; Run the target (using any previously set command line parameters) + ([S-f7] . kill-compilation) + ([C-f7] . cmake-integration-debug-last-target) ;; Debug the target (using any previously set command line parameters) + ([M-f7] . cmake-integration-run-last-target-with-arguments) ;; Ask for command line parameters to run the target + ([M-f8] . cmake-integration-select-configure-preset) ;; Ask for a preset name and call CMake to configure the project + ([f8] . cmake-integration-cmake-reconfigure) ;; Call CMake to configure the project using the last chosen preset + ) + ) + +(define-globalized-minor-mode global-cmake-integration-keybindings-mode + cmake-integration-keybindings-mode cmake-integration-keybindings-mode-turn-on-in-cmake-projects) + + +(global-cmake-integration-keybindings-mode) + +;; Extend project.el to recognize local projects based on a .project file +(cl-defmethod project-root ((project (head local))) + (cdr project)) + +(defun mu--project-files-in-directory (dir) + "Use `fd' to list files in DIR." + (let* ((default-directory dir) + (localdir (file-local-name (expand-file-name dir))) + (command (format "fd -t f -0 . %s" localdir))) + (project--remote-file-names + (sort (split-string (shell-command-to-string command) "\0" t) + #'string<)))) + +(cl-defmethod project-files ((project (head local)) &optional dirs) + "Override `project-files' to use `fd' in local projects." + (mapcan #'mu--project-files-in-directory + (or dirs (list (project-root project))))) + +(defun mu-project-try-local (dir) + "Determine if DIR is a non-Git project. +DIR must include a .project file to be considered a project." + (let ((root (locate-dominating-file dir ".project"))) + (and root (cons 'local root)))) + +(use-package project + :defer t + :config + (add-to-list 'project-find-functions 'mu-project-try-local) + ) + +(use-package direnv + :ensure t + :config + (direnv-mode)) + +(defun kylekrein/project-enable-direnv-flake () + "Add `use flake` to .envrc and run `direnv allow` in the project root." + (interactive) + (let* ((project (project-current t)) + (root (project-root project)) + (envrc-path (expand-file-name ".envrc" root))) + (unless (file-exists-p envrc-path) + (with-temp-buffer + (insert "use flake\n") + (write-file envrc-path))) + (unless (string-match-p "use flake" (with-temp-buffer + (insert-file-contents envrc-path) + (buffer-string))) + (with-temp-buffer + (insert-file-contents envrc-path) + (goto-char (point-max)) + (insert "\nuse flake\n") + (write-file envrc-path))) + (let ((default-directory root)) + (direnv-allow)) + (message "Added 'use flake' to .envrc and ran direnv allow in %s" root))) + +(use-package glsl-mode + :ensure t) + +(add-to-list 'auto-mode-alist '("\\.rml\\'" . html-ts-mode)) +(add-to-list 'auto-mode-alist '("\\.rcss\\'" . css-ts-mode)) + +(add-to-list 'auto-mode-alist '("CMakeLists\\.txt\\'" . cmake-ts-mode)) +(add-to-list 'auto-mode-alist '("\\.cmake\\'" . cmake-ts-mode)) + +(use-package zig-mode + :ensure t) + +(autoload 'zig-mode "zig-mode" nil t) +(add-to-list 'auto-mode-alist '("\\.\\(zig\\|zon\\)\\'" . zig-mode)) + +(use-package treesit-auto + :ensure t + :demand t + :config + (global-treesit-auto-mode)) + +(use-package eldoc + :init + (global-eldoc-mode)) + + (use-package eglot + :hook (prog-mode . eglot-ensure) + ;;:init + ;;(setq eglot-stay-out-of '(flymake)) + :bind (:map + eglot-mode-map + ("C-c c a" . eglot-code-actions) + ;;("C-c c o" . eglot-code-actions-organize-imports) + ("C-c c r" . eglot-rename) + ("C-c c f" . eglot-format))) + + (use-package flymake + :hook (prog-mode . flymake-mode) + :bind (:map flymake-mode-map + ("C-c ! n" . flymake-goto-next-error) + ("C-c ! p" . flymake-goto-prev-error) + ("C-c ! l" . flymake-show-buffer-diagnostics))) + +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '((c-ts-mode c++-ts-mode) + . ("clangd" + "-j=8" + "--log=error" + "--malloc-trim" + "--background-index" + "--clang-tidy" + "--cross-file-rename" + "--completion-style=detailed" + "--pch-storage=memory" + "--header-insertion=never" + "--header-insertion-decorators=0"))) + (add-hook 'c-ts-mode-hook #'eglot-ensure) + (add-hook 'c++-ts-mode-hook #'eglot-ensure)) + +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '(zig-mode . ( + ;; Use `zls` if it is in your PATH + "zls" + ;; There are two ways to set config options: + ;; - edit your `zls.json` that applies to any editor that uses ZLS + ;; - set in-editor config options with the `initializationOptions` field below. + ;; + ;; Further information on how to configure ZLS: + ;; https://zigtools.org/zls/configure/ + :initializationOptions + (;; Whether to enable build-on-save diagnostics + ;; + ;; Further information about build-on save: + ;; https://zigtools.org/zls/guides/build-on-save/ + ;;enable_build_on_save t + + ;; omit the following line if `zig` is in your PATH + ;:zig_exe_path "/path/to/zig_executable" + )))) + (add-hook 'zig-mode-hook #'eglot-ensure)) + +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '(csharp-ts-mode + . ("csharp-ls"))) + (add-hook 'csharp-ts-mode-hook #'eglot-ensure)) + +(with-eval-after-load 'eglot + (add-to-list 'eglot-server-programs + '(python-ts-mode + . ("ty"))) + (add-hook 'python-ts-mode-hook #'eglot-ensure)) + +(use-package nerd-icons + :ensure t + ;; :custom + ;; The Nerd Font you want to use in GUI + ;; "Symbols Nerd Font Mono" is the default and is recommended + ;; but you can use any other Nerd Font if you want + ;; (nerd-icons-font-family "Symbols Nerd Font Mono") + ) + +(use-package nerd-icons-completion + :ensure t + :after marginalia + :config + (nerd-icons-completion-mode) + (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup)) + +(use-package persist-state + :ensure t + :after server + :if server-process + :config + (persist-state-mode)) + +(use-package multiple-cursors +:ensure t +:bind ( +("C-S-c C-S-c" . mc/edit-lines) +("C->" . mc/mark-next-like-this) +("C-<" . mc/mark-previous-like-this) +("C-C C-<" . mc/mark-all-like-this) +("C-\"" . mc/skip-to-next-like-this) +("C-:" . mc/skip-to-previous-like-this) +("C-C C->" . mc/mark-more-like-this-extended) +("C-S-" . mc/add-cursor-on-click) +)) + +(require 'windmove) + +;;;###autoload +(defun buf-move-up () + "Swap the current buffer and the buffer above the split. +If there is no split, ie now window above the current one, an +error is signaled." +;; "Switches between the current buffer, and the buffer above the +;; split, if possible." + (interactive) + (let* ((other-win (windmove-find-other-window 'up)) + (buf-this-buf (window-buffer (selected-window)))) + (if (null other-win) + (error "No window above this one") + ;; swap top with this one + (set-window-buffer (selected-window) (window-buffer other-win)) + ;; move this one to top + (set-window-buffer other-win buf-this-buf) + (select-window other-win)))) + +;;;###autoload +(defun buf-move-down () +"Swap the current buffer and the buffer under the split. +If there is no split, ie now window under the current one, an +error is signaled." + (interactive) + (let* ((other-win (windmove-find-other-window 'down)) + (buf-this-buf (window-buffer (selected-window)))) + (if (or (null other-win) + (string-match "^ \\*Minibuf" (buffer-name (window-buffer other-win)))) + (error "No window under this one") + ;; swap top with this one + (set-window-buffer (selected-window) (window-buffer other-win)) + ;; move this one to top + (set-window-buffer other-win buf-this-buf) + (select-window other-win)))) + +;;;###autoload +(defun buf-move-left () +"Swap the current buffer and the buffer on the left of the split. +If there is no split, ie now window on the left of the current +one, an error is signaled." + (interactive) + (let* ((other-win (windmove-find-other-window 'left)) + (buf-this-buf (window-buffer (selected-window)))) + (if (null other-win) + (error "No left split") + ;; swap top with this one + (set-window-buffer (selected-window) (window-buffer other-win)) + ;; move this one to top + (set-window-buffer other-win buf-this-buf) + (select-window other-win)))) + +;;;###autoload +(defun buf-move-right () +"Swap the current buffer and the buffer on the right of the split. +If there is no split, ie now window on the right of the current +one, an error is signaled." + (interactive) + (let* ((other-win (windmove-find-other-window 'right)) + (buf-this-buf (window-buffer (selected-window)))) + (if (null other-win) + (error "No right split") + ;; swap top with this one + (set-window-buffer (selected-window) (window-buffer other-win)) + ;; move this one to top + (set-window-buffer other-win buf-this-buf) + (select-window other-win)))) + +(use-package windmove + :bind + (("" . windmove-up) + ("" . windmove-down) + ("" . windmove-left) + ("" . windmove-right) + ("" . buf-move-up) + ("" . buf-move-down) + ("" . buf-move-left) + ("" . buf-move-right))) + +(use-package corfu + :ensure t + ;; Optional customizations + :custom + (corfu-cycle t) ;; Enable cycling for `corfu-next/previous' + (corfu-auto t) + (corfu-auto-prefix 2) + (corfu-quit-at-boundary 'separator) + (corfu-echo-documentation 0.25) + (corfu-preselect-first nil) + (corfu-popupinfo-delay '(1.0 . 0.3)) ;;default '(2.0 . 1.0) + ;; (corfu-quit-no-match nil) ;; Never quit, even if there is no match + ;; (corfu-preview-current nil) ;; Disable current candidate preview + ;; (corfu-preselect 'prompt) ;; Preselect the prompt + ;; (corfu-on-exact-match nil) ;; Configure handling of exact matches + + ;; Enable Corfu only for certain modes. See also `global-corfu-modes'. + ;; :hook ((prog-mode . corfu-mode) + ;; (shell-mode . corfu-mode) + ;; (eshell-mode . corfu-mode)) + :bind (:map corfu-map + ("M-SPC" . corfu-insert-separator) + ("RET" . nil) + ("TAB" . corfu-next) + ([tab] . corfu-next) + ("SHIFT-TAB" . corfu-previous) + ([backtab] . corfu-previous) + ("S-" . corfu-insert)) + + ;; Recommended: Enable Corfu globally. This is recommended since Dabbrev can + ;; be used globally (M-/). See also the customization variable + ;; `global-corfu-modes' to exclude certain modes. + :init + (global-corfu-mode) + (corfu-history-mode) + (corfu-popupinfo-mode)) + +;; A few more useful configurations... +(use-package emacs + :custom + ;; TAB cycle if there are only few candidates + ;; (completion-cycle-threshold 3) + + ;; Enable indentation+completion using the TAB key. + ;; `completion-at-point' is often bound to M-TAB. + (tab-always-indent 'complete) + + ;; Emacs 30 and newer: Disable Ispell completion function. + ;; Try `cape-dict' as an alternative. + (text-mode-ispell-word-completion nil) + + ;; Hide commands in M-x which do not apply to the current mode. Corfu + ;; commands are hidden, since they are not used via M-x. This setting is + ;; useful beyond Corfu. + (read-extended-command-predicate #'command-completion-default-include-p)) + +(use-package cape + :ensure t + :defer 10 + :init +(add-to-list 'completion-at-point-functions #'cape-file)) + +;; Enable vertico + (use-package vertico + :ensure t + :custom + ;; (vertico-scroll-margin 0) ;; Different scroll margin + ;; (vertico-count 20) ;; Show more candidates + ;; (vertico-resize t) ;; Grow and shrink the Vertico minibuffer + (vertico-cycle t) ;; Enable cycling for `vertico-next/previous' + :init + (vertico-mode)) + +(vertico-mode t) ;; enable vertico for all buffers + ;; Persist history over Emacs restarts. Vertico sorts by history position. + (use-package savehist + :init + (savehist-mode)) + + ;; A few more useful configurations... + (use-package emacs + :custom + ;; Support opening new minibuffers from inside existing minibuffers. + (enable-recursive-minibuffers t) + ;; Hide commands in M-x which do not work in the current mode. Vertico + ;; commands are hidden in normal buffers. This setting is useful beyond + ;; Vertico. + (read-extended-command-predicate #'command-completion-default-include-p) + :init + ;; Add prompt indicator to `completing-read-multiple'. + ;; We display [CRM], e.g., [CRM,] if the separator is a comma. + (defun crm-indicator (args) + (cons (format "[CRM%s] %s" + (replace-regexp-in-string + "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" + crm-separator) + (car args)) + (cdr args))) + (advice-add #'completing-read-multiple :filter-args #'crm-indicator) + + ;; Do not allow the cursor in the minibuffer prompt + (setq minibuffer-prompt-properties + '(read-only t cursor-intangible t face minibuffer-prompt)) + (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)) + +;; Optionally use the `orderless' completion style. +(use-package orderless + :ensure t + :custom + ;; Configure a custom style dispatcher (see the Consult wiki) + ;; (orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch)) + ;; (orderless-component-separator #'orderless-escapable-split-on-space) + (completion-styles '(orderless flex basic partial-completion)) + + (completion-category-defaults nil) + (completion-category-overrides '((file (styles partial-completion))))) + +;; Example configuration for Consult + (use-package consult + :ensure t + ;; Replace bindings. Lazily loaded by `use-package'. + :bind (;; C-c bindings in `mode-specific-map' + ("C-c M-x" . consult-mode-command) + ("C-c h" . consult-history) + ("C-c k" . consult-kmacro) + ("C-c m" . consult-man) + ("C-c i" . consult-info) + ([remap Info-search] . consult-info) + ;; C-x bindings in `ctl-x-map' + ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command + ("C-x b" . consult-buffer) ;; orig. switch-to-buffer + ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window + ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame + ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab + ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump + ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer + ;; Custom M-# bindings for fast register access + ("M-#" . consult-register-load) + ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) + ("C-M-#" . consult-register) + ;; Other custom bindings + ("M-y" . consult-yank-pop) ;; orig. yank-pop + ;; M-g bindings in `goto-map' + ("M-g e" . consult-compile-error) + ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck + ("M-g g" . consult-goto-line) ;; orig. goto-line + ("M-g M-g" . consult-goto-line) ;; orig. goto-line + ("M-g o" . consult-outline) ;; Alternative: consult-org-heading + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-imenu-multi) + ;; M-s bindings in `search-map' + ("M-s d" . consult-find) ;; Alternative: consult-fd + ("M-s c" . consult-locate) + ("M-s g" . consult-grep) + ("M-s G" . consult-git-grep) + ("M-s r" . consult-ripgrep) + ("M-s l" . consult-line) + ("M-s L" . consult-line-multi) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + ;; Isearch integration + ("M-s e" . consult-isearch-history) + :map isearch-mode-map + ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s l" . consult-line) ;; needed by consult-line to detect isearch + ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch + ;; Minibuffer history + :map minibuffer-local-map + ("M-s" . consult-history) ;; orig. next-matching-history-element + ("M-r" . consult-history)) ;; orig. previous-matching-history-element + + ;; Enable automatic preview at point in the *Completions* buffer. This is + ;; relevant when you use the default completion UI. + :hook (completion-list-mode . consult-preview-at-point-mode) + + ;; The :init configuration is always executed (Not lazy) + :init + + ;; Tweak the register preview for `consult-register-load', + ;; `consult-register-store' and the built-in commands. This improves the + ;; register formatting, adds thin separator lines, register sorting and hides + ;; the window mode line. + (advice-add #'register-preview :override #'consult-register-window) + (setq register-preview-delay 0.5) + + ;; Use Consult to select xref locations with preview + (setq xref-show-xrefs-function #'consult-xref + xref-show-definitions-function #'consult-xref) + + ;; Configure other variables and modes in the :config section, + ;; after lazily loading the package. + :config + + ;; Optionally configure preview. The default value + ;; is 'any, such that any key triggers the preview. + ;; (setq consult-preview-key 'any) + ;; (setq consult-preview-key "M-.") + ;; (setq consult-preview-key '("S-" "S-")) + ;; For some commands and buffer sources it is useful to configure the + ;; :preview-key on a per-command basis using the `consult-customize' macro. + (consult-customize + consult-theme :preview-key '(:debounce 0.2 any) + consult-ripgrep consult-git-grep consult-grep consult-man + consult-bookmark consult-recent-file consult-xref + consult--source-bookmark consult--source-file-register + consult--source-recent-file consult--source-project-recent-file + ;; :preview-key "M-." + :preview-key '(:debounce 0.4 any)) + + ;; Optionally configure the narrowing key. + ;; Both < and C-+ work reasonably well. + (setq consult-narrow-key "<") ;; "C-+" + + ;; Optionally make narrowing help available in the minibuffer. + ;; You may want to use `embark-prefix-help-command' or which-key instead. + ;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help) + ) +(require 'consult) +;;(setq read-file-name-function #'consult-find-file-with-preview) + +;;Previewing files in find-file +(defun consult-find-file-with-preview (prompt &optional dir default mustmatch initial pred) + (interactive) + (let ((default-directory (expand-file-name (or dir default-directory))) + (minibuffer-completing-file-name t)) + (consult--read #'read-file-name-internal :state (consult--file-preview) + :prompt prompt + :initial initial + :require-match mustmatch + :predicate pred))) + +;;Previewing files for project-find-file +(setq project-read-file-name-function #'consult-project-find-file-with-preview) + +(defun consult-project-find-file-with-preview (prompt all-files &optional pred hist _mb) + (let ((prompt (if (and all-files + (file-name-absolute-p (car all-files))) + prompt + ( concat prompt + ( format " in %s" + (consult--fast-abbreviate-file-name default-directory))))) + (minibuffer-completing-file-name t)) + (consult--read (mapcar + (lambda (file) + (file-relative-name file)) + all-files) + :state (consult--file-preview) + :prompt (concat prompt ": ") + :require-match t + :history hist + :category 'file + :predicate pred))) + +;; Enable rich annotations using the Marginalia package +(use-package marginalia + :ensure t + ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding + ;; available in the *Completions* buffer, add it to the + ;; `completion-list-mode-map'. + :bind (:map minibuffer-local-map + ("M-A" . marginalia-cycle)) + + ;; The :init section is always executed. + :init + + ;; Marginalia must be activated in the :init section of use-package such that + ;; the mode gets enabled right away. Note that this forces loading the + ;; package. + (marginalia-mode)) + +(use-package doom-themes + :ensure t + :config + ;; Global settings (defaults) + (setq doom-themes-enable-bold t ; if nil, bold is universally disabled + doom-themes-enable-italic t) ; if nil, italics is universally disabled + (load-theme 'doom-one t) + + ;; Enable flashing mode-line on errors + (doom-themes-visual-bell-config) + ;; Enable custom neotree theme (nerd-icons must be installed!) + (doom-themes-neotree-config) + ;; or for treemacs users + (setq doom-themes-treemacs-theme "doom-atom") ; use "doom-colors" for less minimal icon theme + (doom-themes-treemacs-config) + ;; Corrects (and improves) org-mode's native fontification. + (doom-themes-org-config)) + +(unless (kylekrein/detect-wsl) + (add-to-list 'default-frame-alist '(alpha-background . 90))) ; For all new frames henceforth + +(use-package sudo-edit + :ensure t) + +(use-package which-key + :ensure t + :init + (which-key-mode 1) + :config + (setq which-key-side-window-location 'bottom + which-key-sort-order #'which-key-key-order-alpha + which-key-sort-uppercase-first nil + which-key-add-column-padding 1 + which-key-max-display-columns nil + which-key-min-display-lines 6 + which-key-side-window-slot -10 + which-key-side-window-max-height 0.25 + which-key-idle-delay 0.8 + which-key-max-description-length 25 + which-key-allow-imprecise-window-fit nil + which-key-separator " → " )) + +(when (kylekrein/detect-wsl) + (setq select-active-regions nil) + (setq select-enable-clipboard 't) + (setq select-enable-primary nil) + (setq interprogram-cut-function #'gui-select-text) +) diff --git a/emacs/.config/emacs/init.el b/emacs/.config/emacs/init.el new file mode 100644 index 0000000..1416633 --- /dev/null +++ b/emacs/.config/emacs/init.el @@ -0,0 +1,15 @@ +;;; -*- lexical-binding: t; -*- +(setq custom-file "~/.config/emacs/custom.el") +(when (file-exists-p custom-file) + (load custom-file)) +(setq package-user-dir "~/.cache/emacs/elpa") +(make-directory package-user-dir t) +(require 'package) +;;https://github.com/wbolster/emacs-direnv/issues/85 +(setenv "PATH" (mapconcat 'identity exec-path ":")) ;;fixes direnv losing nix pkgs +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(package-initialize) +(package-refresh-contents) +(add-to-list 'load-path (getenv "EMACSLOADPATH")) + +(load "~/.config/emacs/config.el") diff --git a/git/.config/git/config b/git/.config/git/config new file mode 100644 index 0000000..7652017 --- /dev/null +++ b/git/.config/git/config @@ -0,0 +1,7 @@ +[user] + name = Aleksandr Lebedev + email = alex.lebedev2003@icloud.com +[core] + editor = emacsclient -c +[color] + ui = auto \ No newline at end of file diff --git a/niri/.config/niri/config.kdl b/niri/.config/niri/config.kdl new file mode 100644 index 0000000..5c10671 --- /dev/null +++ b/niri/.config/niri/config.kdl @@ -0,0 +1,631 @@ +// This config is in the KDL format: https://kdl.dev +// "/-" comments out the following node. +// Check the wiki for a full description of the configuration: +// https://yalter.github.io/niri/Configuration:-Introduction + +// Input device configuration. +// Find the full list of options on the wiki: +// https://yalter.github.io/niri/Configuration:-Input +input { + keyboard { + xkb { + // You can set rules, model, layout, variant and options. + // For more information, see xkeyboard-config(7). + + // For example: + layout "eu,ru" + options "grp:lctrl_toggle, ctrl:nocaps" + + // If this section is empty, niri will fetch xkb settings + // from org.freedesktop.locale1. You can control these using + // localectl set-x11-keymap. + } + + // Enable numlock on startup, omitting this setting disables it. + numlock + track-layout "window" + } + + // Next sections include libinput settings. + // Omitting settings disables them, or leaves them at their default values. + // All commented-out settings here are examples, not defaults. + touchpad { + // off + tap + dwt + // dwtp + drag true + drag-lock + natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "two-finger" + // disabled-on-external-mouse + } + + mouse { + // off + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "no-scroll" + } + + trackpoint { + // off + // natural-scroll + // accel-speed 0.2 + // accel-profile "flat" + // scroll-method "on-button-down" + // scroll-button 273 + // scroll-button-lock + // middle-emulation + } + + // Uncomment this to make the mouse warp to the center of newly focused windows. + // warp-mouse-to-focus + + // Focus windows and outputs automatically when moving the mouse into them. + // Setting max-scroll-amount="0%" makes it work only on windows already fully on screen. + // focus-follows-mouse max-scroll-amount="0%" + + //disable-power-key-handling +} + +// You can configure outputs by their name, which you can find +// by running `niri msg outputs` while inside a niri instance. +// The built-in laptop monitor is usually called "eDP-1". +// Find more information on the wiki: +// https://yalter.github.io/niri/Configuration:-Outputs +// Remember to uncomment the node by removing "/-"! +/-output "eDP-1" { + // Uncomment this line to disable this output. + // off + + // Resolution and, optionally, refresh rate of the output. + // The format is "x" or "x@". + // If the refresh rate is omitted, niri will pick the highest refresh rate + // for the resolution. + // If the mode is omitted altogether or is invalid, niri will pick one automatically. + // Run `niri msg outputs` while inside a niri instance to list all outputs and their modes. + mode "1920x1080@120.030" + + // You can use integer or fractional scale, for example use 1.5 for 150% scale. + scale 2 + + // Transform allows to rotate the output counter-clockwise, valid values are: + // normal, 90, 180, 270, flipped, flipped-90, flipped-180 and flipped-270. + transform "normal" + + // Position of the output in the global coordinate space. + // This affects directional monitor actions like "focus-monitor-left", and cursor movement. + // The cursor can only move between directly adjacent outputs. + // Output scale and rotation has to be taken into account for positioning: + // outputs are sized in logical, or scaled, pixels. + // For example, a 3840×2160 output with scale 2.0 will have a logical size of 1920×1080, + // so to put another output directly adjacent to it on the right, set its x to 1920. + // If the position is unset or results in an overlap, the output is instead placed + // automatically. + position x=1280 y=0 +} + +// Settings that influence how windows are positioned and sized. +// Find more information on the wiki: +// https://yalter.github.io/niri/Configuration:-Layout +layout { + // Set gaps around windows in logical pixels. + gaps 16 + + // When to center a column when changing focus, options are: + // - "never", default behavior, focusing an off-screen column will keep at the left + // or right edge of the screen. + // - "always", the focused column will always be centered. + // - "on-overflow", focusing a column will center it if it doesn't fit + // together with the previously focused column. + center-focused-column "never" + + // You can customize the widths that "switch-preset-column-width" (Mod+R) toggles between. + preset-column-widths { + // Proportion sets the width as a fraction of the output width, taking gaps into account. + // For example, you can perfectly fit four windows sized "proportion 0.25" on an output. + // The default preset widths are 1/3, 1/2 and 2/3 of the output. + proportion 1.0 + proportion 0.5 + proportion 0.33333 + proportion 0.66667 + + // Fixed sets the width in logical pixels exactly. + // fixed 1920 + } + + // You can also customize the heights that "switch-preset-window-height" (Mod+Shift+R) toggles between. + // preset-window-heights { } + + // You can change the default width of the new windows. + default-column-width { proportion 1.0; } + // If you leave the brackets empty, the windows themselves will decide their initial width. + // default-column-width {} + + // By default focus ring and border are rendered as a solid background rectangle + // behind windows. That is, they will show up through semitransparent windows. + // This is because windows using client-side decorations can have an arbitrary shape. + // + // If you don't like that, you should uncomment `prefer-no-csd` below. + // Niri will draw focus ring and border *around* windows that agree to omit their + // client-side decorations. + // + // Alternatively, you can override it with a window rule called + // `draw-border-with-background`. + + // You can change how the focus ring looks. + focus-ring { + // Uncomment this line to disable the focus ring. + // off + + // How many logical pixels the ring extends out from the windows. + width 4 + + // Colors can be set in a variety of ways: + // - CSS named colors: "red" + // - RGB hex: "#rgb", "#rgba", "#rrggbb", "#rrggbbaa" + // - CSS-like notation: "rgb(255, 127, 0)", rgba(), hsl() and a few others. + + // Color of the ring on the active monitor. + active-color "#b19cd9" //Light Pastel Purple + + // Color of the ring on inactive monitors. + // + // The focus ring only draws around the active window, so the only place + // where you can see its inactive-color is on other monitors. + inactive-color "#e6e6fa" //Lavender Mist + + // You can also use gradients. They take precedence over solid colors. + // Gradients are rendered the same as CSS linear-gradient(angle, from, to). + // The angle is the same as in linear-gradient, and is optional, + // defaulting to 180 (top-to-bottom gradient). + // You can use any CSS linear-gradient tool on the web to set these up. + // Changing the color space is also supported, check the wiki for more info. + // + // active-gradient from="#80c8ff" to="#c7ff7f" angle=45 + + // You can also color the gradient relative to the entire view + // of the workspace, rather than relative to just the window itself. + // To do that, set relative-to="workspace-view". + // + // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" + } + + // You can also add a border. It's similar to the focus ring, but always visible. + border { + // The settings are the same as for the focus ring. + // If you enable the border, you probably want to disable the focus ring. + off + + width 4 + active-color "#ffc87f" + inactive-color "#505050" + + // Color of the border around windows that request your attention. + urgent-color "#9b0000" + + // Gradients can use a few different interpolation color spaces. + // For example, this is a pastel rainbow gradient via in="oklch longer hue". + // + // active-gradient from="#e5989b" to="#ffb4a2" angle=45 relative-to="workspace-view" in="oklch longer hue" + + // inactive-gradient from="#505050" to="#808080" angle=45 relative-to="workspace-view" + } + + // You can enable drop shadows for windows. + shadow { + // Uncomment the next line to enable shadows. + // on + + // By default, the shadow draws only around its window, and not behind it. + // Uncomment this setting to make the shadow draw behind its window. + // + // Note that niri has no way of knowing about the CSD window corner + // radius. It has to assume that windows have square corners, leading to + // shadow artifacts inside the CSD rounded corners. This setting fixes + // those artifacts. + // + // However, instead you may want to set prefer-no-csd and/or + // geometry-corner-radius. Then, niri will know the corner radius and + // draw the shadow correctly, without having to draw it behind the + // window. These will also remove client-side shadows if the window + // draws any. + // + // draw-behind-window true + + // You can change how shadows look. The values below are in logical + // pixels and match the CSS box-shadow properties. + + // Softness controls the shadow blur radius. + softness 30 + + // Spread expands the shadow. + spread 5 + + // Offset moves the shadow relative to the window. + offset x=0 y=5 + + // You can also change the shadow color and opacity. + color "#0007" + } + + // Struts shrink the area occupied by windows, similarly to layer-shell panels. + // You can think of them as a kind of outer gaps. They are set in logical pixels. + // Left and right struts will cause the next window to the side to always be visible. + // Top and bottom struts will simply add outer gaps in addition to the area occupied by + // layer-shell panels and regular gaps. + struts { + // left 64 + // right 64 + // top 64 + // bottom 64 + } +} + +// Add lines like this to spawn processes at startup. +// Note that running niri as a session supports xdg-desktop-autostart, +// which may be more convenient to use. +// See the binds section below for more spawn examples. + +spawn-sh-at-startup "/usr/bin/pipewire-launcher.sh" +spawn-sh-at-startup "gnome-keyring-daemon" +spawn-sh-at-startup "alacritty --daemon" +spawn-sh-at-startup "emacs --daemon" +spawn-sh-at-startup "qs" +spawn-sh-at-startup "wl-paste --watch cliphist store &" +spawn-sh-at-startup "nextcloud --background" + +config-notification { + disable-failed +} + +debug { + honor-xdg-activation-with-invalid-serial +} + +hotkey-overlay { + // Uncomment this line to disable the "Important Hotkeys" pop-up at startup. + // skip-at-startup +} + +// Uncomment this line to ask the clients to omit their client-side decorations if possible. +// If the client will specifically ask for CSD, the request will be honored. +// Additionally, clients will be informed that they are tiled, removing some client-side rounded corners. +// This option will also fix border/focus ring drawing behind some semitransparent windows. +// After enabling or disabling this, you need to restart the apps for this to take effect. +prefer-no-csd + +// You can change the path where screenshots are saved. +// A ~ at the front will be expanded to the home directory. +// The path is formatted with strftime(3) to give you the screenshot date and time. +screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" + +// You can also set this to null to disable saving screenshots to disk. +// screenshot-path null + +// Animation settings. +// The wiki explains how to configure individual animations: +// https://yalter.github.io/niri/Configuration:-Animations +animations { + // Uncomment to turn off all animations. + // off + + // Slow down all animations by this factor. Values below 1 speed them up instead. + // slowdown 3.0 +} + +// Window rules let you adjust behavior for individual windows. +// Find more information on the wiki: +// https://yalter.github.io/niri/Configuration:-Window-Rules + +// Work around WezTerm's initial configure bug +// by setting an empty default-column-width. +window-rule { + // This regular expression is intentionally made as specific as possible, + // since this is the default config, and we want no false positives. + // You can get away with just app-id="wezterm" if you want. + match app-id=r#"^org\.wezfurlong\.wezterm$"# + default-column-width {} +} + +// Open the Firefox picture-in-picture player as floating by default. +window-rule { + // This app-id regular expression will work for both: + // - host Firefox (app-id is "firefox") + // - Flatpak Firefox (app-id is "org.mozilla.firefox") + match app-id=r#"librewolf$"# title="^Picture-in-Picture$" + open-floating true +} + +// Example: block out two password managers from screen capture. +// (This example rule is commented out with a "/-" in front.) +/-window-rule { + match app-id=r#"^org\.keepassxc\.KeePassXC$"# + match app-id=r#"^org\.gnome\.World\.Secrets$"# + + block-out-from "screen-capture" + + // Use this instead if you want them visible on third-party screenshot tools. + // block-out-from "screencast" +} + +// Example: enable rounded corners for all windows. +// (This example rule is commented out with a "/-" in front.) +window-rule { + geometry-corner-radius 12 + clip-to-geometry true +} + +binds { + // Keys consist of modifiers separated by + signs, followed by an XKB key name + // in the end. To find an XKB name for a particular key, you may use a program + // like wev. + // + // "Mod" is a special modifier equal to Super when running on a TTY, and to Alt + // when running as a winit window. + // + // Most actions that you can bind here can also be invoked programmatically with + // `niri msg action do-something`. + + // Mod-Shift-/, which is usually the same as Mod-?, + // shows a list of important hotkeys. + Mod+Shift+Slash { show-hotkey-overlay; } + + // Suggested binds for running programs: terminal, app launcher, screen locker. + Mod+T hotkey-overlay-title="Open a Terminal: Alacritty" { spawn-sh "alacritty msg create-window"; } + Mod+E hotkey-overlay-title="Run Emacs" { spawn-sh "emacsclient -c"; } + Mod+B hotkey-overlay-title="Open a browser: Librewolf" { spawn "librewolf"; } + Super+Alt+L hotkey-overlay-title="Lock the Screen: swaylock" { spawn "swaylock"; } + + // Use spawn-sh to run a shell command. Do this if you need pipes, multiple commands, etc. + Mod+Space hotkey-overlay-title="Application Launcher" { + spawn-sh "qs ipc call spotlight toggle"; + } + Mod+V hotkey-overlay-title="Clipboard Manager" { + spawn-sh "qs ipc call clipboard toggle"; + } + Mod+M hotkey-overlay-title="Task Manager" { + spawn-sh "qs ipc call processlist toggle"; + } + Super+L hotkey-overlay-title="Lock Screen" { + spawn-sh "qs ipc call lock lock"; + } + Mod+Y hotkey-overlay-title="Browse Wallpapers" { + spawn-sh "qs ipc call dankdash wallpaper"; + } + XF86AudioRaiseVolume allow-when-locked=true { + spawn-sh "qs ipc call audio increment 3"; + } + XF86AudioLowerVolume allow-when-locked=true { + spawn-sh "qs ipc call audio decrement 3"; + } + XF86AudioMute allow-when-locked=true { + spawn-sh "qs ipc call audio mute"; + } + XF86AudioMicMute allow-when-locked=true { + spawn-sh "qs ipc call audio micmute"; + } + XF86MonBrightnessUp allow-when-locked=true { + spawn-sh "qs ipc call brightness increment 5 ''"; + } + // You can override the default device for e.g. keyboards by adding the device name to the last param + XF86MonBrightnessDown allow-when-locked=true { + spawn-sh "qs ipc call brightness decrement 5 ''"; + } + // Night mode toggle + Mod+Shift+N allow-when-locked=true { + spawn-sh "qs ipc call night toggle"; + } + // Open/close the Overview: a zoomed-out view of workspaces and windows. + // You can also move the mouse into the top-left hot corner, + // or do a four-finger swipe up on a touchpad. + Mod+Tab repeat=false { toggle-overview; } + + Mod+Q repeat=false { close-window; } + + Mod+Left { focus-column-left; } + Mod+Down { focus-window-or-workspace-down; } + Mod+Up { focus-window-or-workspace-up; } + Mod+Right { focus-column-right; } + + Mod+Shift+Left { move-column-left; } + Mod+Shift+Down { move-window-down-or-to-workspace-down; } + Mod+Shift+Up { move-window-up-or-to-workspace-up; } + Mod+Shift+Right { move-column-right; } + + Mod+Home { focus-column-first; } + Mod+End { focus-column-last; } + Mod+Ctrl+Home { move-column-to-first; } + Mod+Ctrl+End { move-column-to-last; } + + Mod+Ctrl+Left { focus-monitor-left; } + Mod+Ctrl+Down { focus-monitor-down; } + Mod+Ctrl+Up { focus-monitor-up; } + Mod+Ctrl+Right { focus-monitor-right; } + + Mod+Shift+Ctrl+Left { move-column-to-monitor-left; } + Mod+Shift+Ctrl+Down { move-column-to-monitor-down; } + Mod+Shift+Ctrl+Up { move-column-to-monitor-up; } + Mod+Shift+Ctrl+Right { move-column-to-monitor-right; } + + // Alternatively, there are commands to move just a single window: + // Mod+Shift+Ctrl+Left { move-window-to-monitor-left; } + // ... + + // And you can also move a whole workspace to another monitor: + // Mod+Shift+Ctrl+Left { move-workspace-to-monitor-left; } + // ... + + Mod+Page_Down { focus-workspace-down; } + Mod+Page_Up { focus-workspace-up; } + Mod+U { focus-workspace-down; } + Mod+I { focus-workspace-up; } + Mod+Ctrl+Page_Down { move-column-to-workspace-down; } + Mod+Ctrl+Page_Up { move-column-to-workspace-up; } + Mod+Ctrl+U { move-column-to-workspace-down; } + Mod+Ctrl+I { move-column-to-workspace-up; } + + // Alternatively, there are commands to move just a single window: + // Mod+Ctrl+Page_Down { move-window-to-workspace-down; } + // ... + + Mod+Shift+Page_Down { move-workspace-down; } + Mod+Shift+Page_Up { move-workspace-up; } + Mod+Shift+U { move-workspace-down; } + Mod+Shift+I { move-workspace-up; } + + // You can bind mouse wheel scroll ticks using the following syntax. + // These binds will change direction based on the natural-scroll setting. + // + // To avoid scrolling through workspaces really fast, you can use + // the cooldown-ms property. The bind will be rate-limited to this value. + // You can set a cooldown on any bind, but it's most useful for the wheel. + Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; } + Mod+WheelScrollUp cooldown-ms=150 { focus-workspace-up; } + Mod+Ctrl+WheelScrollDown cooldown-ms=150 { move-column-to-workspace-down; } + Mod+Ctrl+WheelScrollUp cooldown-ms=150 { move-column-to-workspace-up; } + + Mod+WheelScrollRight { focus-column-right; } + Mod+WheelScrollLeft { focus-column-left; } + Mod+Ctrl+WheelScrollRight { move-column-right; } + Mod+Ctrl+WheelScrollLeft { move-column-left; } + + // Usually scrolling up and down with Shift in applications results in + // horizontal scrolling; these binds replicate that. + Mod+Shift+WheelScrollDown { focus-column-right; } + Mod+Shift+WheelScrollUp { focus-column-left; } + Mod+Ctrl+Shift+WheelScrollDown { move-column-right; } + Mod+Ctrl+Shift+WheelScrollUp { move-column-left; } + + // Similarly, you can bind touchpad scroll "ticks". + // Touchpad scrolling is continuous, so for these binds it is split into + // discrete intervals. + // These binds are also affected by touchpad's natural-scroll, so these + // example binds are "inverted", since we have natural-scroll enabled for + // touchpads by default. + // Mod+TouchpadScrollDown { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02+"; } + // Mod+TouchpadScrollUp { spawn-sh "wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.02-"; } + + // You can refer to workspaces by index. However, keep in mind that + // niri is a dynamic workspace system, so these commands are kind of + // "best effort". Trying to refer to a workspace index bigger than + // the current workspace count will instead refer to the bottommost + // (empty) workspace. + // + // For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on + // will all refer to the 3rd workspace. + Mod+1 { focus-workspace 1; } + Mod+2 { focus-workspace 2; } + Mod+3 { focus-workspace 3; } + Mod+4 { focus-workspace 4; } + Mod+5 { focus-workspace 5; } + Mod+6 { focus-workspace 6; } + Mod+7 { focus-workspace 7; } + Mod+8 { focus-workspace 8; } + Mod+9 { focus-workspace 9; } + Mod+Ctrl+1 { move-column-to-workspace 1; } + Mod+Ctrl+2 { move-column-to-workspace 2; } + Mod+Ctrl+3 { move-column-to-workspace 3; } + Mod+Ctrl+4 { move-column-to-workspace 4; } + Mod+Ctrl+5 { move-column-to-workspace 5; } + Mod+Ctrl+6 { move-column-to-workspace 6; } + Mod+Ctrl+7 { move-column-to-workspace 7; } + Mod+Ctrl+8 { move-column-to-workspace 8; } + Mod+Ctrl+9 { move-column-to-workspace 9; } + + // Alternatively, there are commands to move just a single window: + // Mod+Ctrl+1 { move-window-to-workspace 1; } + + // Switches focus between the current and the previous workspace. + // Mod+Tab { focus-workspace-previous; } + + // The following binds move the focused window in and out of a column. + // If the window is alone, they will consume it into the nearby column to the side. + // If the window is already in a column, they will expel it out. + Mod+BracketLeft { consume-or-expel-window-left; } + Mod+BracketRight { consume-or-expel-window-right; } + + // Consume one window from the right to the bottom of the focused column. + Mod+Comma { consume-window-into-column; } + // Expel the bottom window from the focused column to the right. + Mod+Period { expel-window-from-column; } + + Mod+R { switch-preset-column-width; } + // Cycling through the presets in reverse order is also possible. + // Mod+R { switch-preset-column-width-back; } + Mod+Shift+R { switch-preset-window-height; } + Mod+Ctrl+R { reset-window-height; } + Mod+Shift+F { maximize-column; } + Mod+F { fullscreen-window; } + + // Expand the focused column to space not taken up by other fully visible columns. + // Makes the column "fill the rest of the space". + Mod+Ctrl+F { expand-column-to-available-width; } + + Mod+C { center-column; } + + // Center all fully visible columns on screen. + Mod+Ctrl+C { center-visible-columns; } + + // Finer width adjustments. + // This command can also: + // * set width in pixels: "1000" + // * adjust width in pixels: "-5" or "+5" + // * set width as a percentage of screen width: "25%" + // * adjust width as a percentage of screen width: "-10%" or "+10%" + // Pixel sizes use logical, or scaled, pixels. I.e. on an output with scale 2.0, + // set-column-width "100" will make the column occupy 200 physical screen pixels. + Mod+Minus { set-column-width "-10%"; } + Mod+Equal { set-column-width "+10%"; } + + // Finer height adjustments when in column with other windows. + Mod+Shift+Minus { set-window-height "-10%"; } + Mod+Shift+Equal { set-window-height "+10%"; } + + // Move the focused window between the floating and the tiling layout. + Mod+Shift+V { toggle-window-floating; } + Mod+Ctrl+V { switch-focus-between-floating-and-tiling; } + + // Toggle tabbed column display mode. + // Windows in this column will appear as vertical tabs, + // rather than stacked on top of each other. + Mod+W { toggle-column-tabbed-display; } + + // Actions to switch layouts. + // Note: if you uncomment these, make sure you do NOT have + // a matching layout switch hotkey configured in xkb options above. + // Having both at once on the same hotkey will break the switching, + // since it will switch twice upon pressing the hotkey (once by xkb, once by niri). + // Mod+Space { switch-layout "next"; } + // Mod+Shift+Space { switch-layout "prev"; } + + Print { screenshot; } + Ctrl+Print { screenshot-screen; } + Alt+Print { screenshot-window; } + + // Applications such as remote-desktop clients and software KVM switches may + // request that niri stops processing the keyboard shortcuts defined here + // so they may, for example, forward the key presses as-is to a remote machine. + // It's a good idea to bind an escape hatch to toggle the inhibitor, + // so a buggy application can't hold your session hostage. + // + // The allow-inhibiting=false property can be applied to other binds as well, + // which ensures niri always processes them, even when an inhibitor is active. + Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; } + + // The quit action will show a confirmation dialog to avoid accidental exits. + Mod+Shift+E { quit; } + Ctrl+Alt+Delete { quit; } + + // Powers off the monitors. To turn them back on, do any input like + // moving the mouse or pressing any other key. + Mod+Shift+P { power-off-monitors; } +} + +cursor { + hide-after-inactive-ms 10000 +} \ No newline at end of file diff --git a/quickshell/.config/quickshell/.gitignore b/quickshell/.config/quickshell/.gitignore new file mode 100644 index 0000000..08f100f --- /dev/null +++ b/quickshell/.config/quickshell/.gitignore @@ -0,0 +1,99 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +*_qmlcache.qrc +UNUSED +.qmlls.ini + +CLAUDE-activeContext.md +CLAUDE-temp.md + +# Auto-generated theme files +*.generated.* +niri-colors.generated.kdl +ghostty-colors.generated.conf + +result + +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Code coverage profiles and other test artifacts +*.out +coverage.* +*.coverprofile +profile.cov + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +# Editor/IDE +# .idea/ +# .vscode/ diff --git a/quickshell/.config/quickshell/Common/Anims.qml b/quickshell/.config/quickshell/Common/Anims.qml new file mode 100644 index 0000000..349e991 --- /dev/null +++ b/quickshell/.config/quickshell/Common/Anims.qml @@ -0,0 +1,25 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +Singleton { + id: root + + readonly property int durShort: 200 + readonly property int durMed: 450 + readonly property int durLong: 600 + + readonly property int slidePx: 80 + + readonly property var emphasized: [0.05, 0.00, 0.133333, 0.06, 0.166667, 0.40, 0.208333, 0.82, 0.25, 1.00, 1.00, 1.00] + + readonly property var emphasizedDecel: [0.05, 0.70, 0.10, 1.00, 1.00, 1.00] + + readonly property var emphasizedAccel: [0.30, 0.00, 0.80, 0.15, 1.00, 1.00] + + readonly property var standard: [0.20, 0.00, 0.00, 1.00, 1.00, 1.00] + readonly property var standardDecel: [0.00, 0.00, 0.00, 1.00, 1.00, 1.00] + readonly property var standardAccel: [0.30, 0.00, 1.00, 1.00, 1.00, 1.00] +} diff --git a/quickshell/.config/quickshell/Common/AppUsageHistoryData.qml b/quickshell/.config/quickshell/Common/AppUsageHistoryData.qml new file mode 100644 index 0000000..cac3383 --- /dev/null +++ b/quickshell/.config/quickshell/Common/AppUsageHistoryData.qml @@ -0,0 +1,131 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + + id: root + + property var appUsageRanking: { + + } + + Component.onCompleted: { + loadSettings() + } + + function loadSettings() { + parseSettings(settingsFile.text()) + } + + function parseSettings(content) { + try { + if (content && content.trim()) { + var settings = JSON.parse(content) + appUsageRanking = settings.appUsageRanking || {} + } + } catch (e) { + + } + } + + function saveSettings() { + settingsFile.setText(JSON.stringify({ + "appUsageRanking": appUsageRanking + }, null, 2)) + } + + function addAppUsage(app) { + if (!app) + return + + var appId = app.id || (app.execString || app.exec || "") + if (!appId) + return + + var currentRanking = Object.assign({}, appUsageRanking) + + if (currentRanking[appId]) { + currentRanking[appId].usageCount = (currentRanking[appId].usageCount + || 1) + 1 + currentRanking[appId].lastUsed = Date.now() + currentRanking[appId].icon = app.icon || currentRanking[appId].icon + || "application-x-executable" + currentRanking[appId].name = app.name + || currentRanking[appId].name || "" + } else { + currentRanking[appId] = { + "name": app.name || "", + "exec": app.execString || app.exec || "", + "icon": app.icon || "application-x-executable", + "comment": app.comment || "", + "usageCount": 1, + "lastUsed": Date.now() + } + } + + appUsageRanking = currentRanking + saveSettings() + } + + function getAppUsageRanking() { + return appUsageRanking + } + + function getRankedApps() { + var apps = [] + for (var appId in appUsageRanking) { + var appData = appUsageRanking[appId] + apps.push({ + "id": appId, + "name": appData.name, + "exec": appData.exec, + "icon": appData.icon, + "comment": appData.comment, + "usageCount": appData.usageCount, + "lastUsed": appData.lastUsed + }) + } + + return apps.sort(function (a, b) { + if (a.usageCount !== b.usageCount) + return b.usageCount - a.usageCount + return a.name.localeCompare(b.name) + }) + } + + function cleanupAppUsageRanking(availableAppIds) { + var currentRanking = Object.assign({}, appUsageRanking) + var hasChanges = false + + for (var appId in currentRanking) { + if (availableAppIds.indexOf(appId) === -1) { + delete currentRanking[appId] + hasChanges = true + } + } + + if (hasChanges) { + appUsageRanking = currentRanking + saveSettings() + } + } + + FileView { + id: settingsFile + + path: StandardPaths.writableLocation( + StandardPaths.GenericStateLocation) + "/DankMaterialShell/appusage.json" + blockLoading: true + blockWrites: true + watchChanges: true + onLoaded: { + parseSettings(settingsFile.text()) + } + onLoadFailed: error => {} + } +} diff --git a/quickshell/.config/quickshell/Common/Appearance.qml b/quickshell/.config/quickshell/Common/Appearance.qml new file mode 100644 index 0000000..3c22566 --- /dev/null +++ b/quickshell/.config/quickshell/Common/Appearance.qml @@ -0,0 +1,66 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell + +Singleton { + id: root + + readonly property Rounding rounding: Rounding {} + readonly property Spacing spacing: Spacing {} + readonly property FontSize fontSize: FontSize {} + readonly property Anim anim: Anim {} + + component Rounding: QtObject { + readonly property int small: 8 + readonly property int normal: 12 + readonly property int large: 16 + readonly property int extraLarge: 24 + readonly property int full: 1000 + } + + component Spacing: QtObject { + readonly property int small: 4 + readonly property int normal: 8 + readonly property int large: 12 + readonly property int extraLarge: 16 + readonly property int huge: 24 + } + + component FontSize: QtObject { + readonly property int small: 12 + readonly property int normal: 14 + readonly property int large: 16 + readonly property int extraLarge: 20 + readonly property int huge: 24 + } + + component AnimCurves: QtObject { + readonly property list standard: [0.2, 0, 0, 1, 1, 1] + readonly property list standardAccel: [0.3, 0, 1, 1, 1, 1] + readonly property list standardDecel: [0, 0, 0, 1, 1, 1] + readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 + / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] + readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] + readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property list expressiveFastSpatial: [0.42, 1.67, 0.21, 0.9, 1, 1] + readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1, 1, 1] + readonly property list expressiveEffects: [0.34, 0.8, 0.34, 1, 1, 1] + } + + component AnimDurations: QtObject { + readonly property int quick: 150 + readonly property int normal: 300 + readonly property int slow: 500 + readonly property int extraSlow: 1000 + readonly property int expressiveFastSpatial: 350 + readonly property int expressiveDefaultSpatial: 500 + readonly property int expressiveEffects: 200 + } + + component Anim: QtObject { + readonly property AnimCurves curves: AnimCurves {} + readonly property AnimDurations durations: AnimDurations {} + } +} diff --git a/quickshell/.config/quickshell/Common/CacheUtils.qml b/quickshell/.config/quickshell/Common/CacheUtils.qml new file mode 100644 index 0000000..0de1049 --- /dev/null +++ b/quickshell/.config/quickshell/Common/CacheUtils.qml @@ -0,0 +1,45 @@ +import Quickshell +pragma Singleton + +Singleton { + id: root + + // Clear all image cache + function clearImageCache() { + Quickshell.execDetached(["rm", "-rf", Paths.stringify( + Paths.imagecache)]) + Paths.mkdir(Paths.imagecache) + } + + // Clear cache older than specified minutes + function clearOldCache(ageInMinutes) { + Quickshell.execDetached( + ["find", Paths.stringify( + Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]) + } + + // Clear cache for specific size + function clearCacheForSize(size) { + Quickshell.execDetached( + ["find", Paths.stringify( + Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]) + } + + // Get cache size in MB + function getCacheSize(callback) { + var process = Qt.createQmlObject(` + import Quickshell.Io + Process { + command: ["du", "-sm", "${Paths.stringify( + Paths.imagecache)}"] + running: true + stdout: StdioCollector { + onStreamFinished: { + var sizeMB = parseInt(text.split("\\t")[0]) || 0 + callback(sizeMB) + } + } + } + `, root) + } +} diff --git a/quickshell/.config/quickshell/Common/ModalManager.qml b/quickshell/.config/quickshell/Common/ModalManager.qml new file mode 100644 index 0000000..9119cb1 --- /dev/null +++ b/quickshell/.config/quickshell/Common/ModalManager.qml @@ -0,0 +1,16 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + id: modalManager + + signal closeAllModalsExcept(var excludedModal) + + function openModal(modal) { + if (!modal.allowStacking) { + closeAllModalsExcept(modal) + } + } +} diff --git a/quickshell/.config/quickshell/Common/Paths.qml b/quickshell/.config/quickshell/Common/Paths.qml new file mode 100644 index 0000000..ec41330 --- /dev/null +++ b/quickshell/.config/quickshell/Common/Paths.qml @@ -0,0 +1,61 @@ +pragma Singleton + +import Quickshell +import QtCore + +Singleton { + id: root + + readonly property url home: StandardPaths.standardLocations( + StandardPaths.HomeLocation)[0] + readonly property url pictures: StandardPaths.standardLocations( + StandardPaths.PicturesLocation)[0] + + readonly property url data: `${StandardPaths.standardLocations( + StandardPaths.GenericDataLocation)[0]}/DankMaterialShell` + readonly property url state: `${StandardPaths.standardLocations( + StandardPaths.GenericStateLocation)[0]}/DankMaterialShell` + readonly property url cache: `${StandardPaths.standardLocations( + StandardPaths.GenericCacheLocation)[0]}/DankMaterialShell` + readonly property url config: `${StandardPaths.standardLocations( + StandardPaths.GenericConfigLocation)[0]}/DankMaterialShell` + + readonly property url imagecache: `${cache}/imagecache` + + function stringify(path: url): string { + return path.toString().replace(/%20/g, " ") + } + + function expandTilde(path: string): string { + return strip(path.replace("~", stringify(root.home))) + } + + function shortenHome(path: string): string { + return path.replace(strip(root.home), "~") + } + + function strip(path: url): string { + return stringify(path).replace("file://", "") + } + + function mkdir(path: url): void { + Quickshell.execDetached(["mkdir", "-p", strip(path)]) + } + + function copy(from: url, to: url): void { + Quickshell.execDetached(["cp", strip(from), strip(to)]) + } + + // ! Spotify and maybe some other apps report the wrong app id in toplevels, hardcode special case + function moddedAppId(appId: string): string { + if (appId === "Spotify") + return "spotify-launcher" + if (appId === "beepertexts") + return "beeper" + if (appId === "home assistant desktop") + return "homeassistant-desktop" + if (appId.includes("com.transmissionbt.transmission")) + return "transmission-gtk" + return appId + } +} diff --git a/quickshell/.config/quickshell/Common/Ref.qml b/quickshell/.config/quickshell/Common/Ref.qml new file mode 100644 index 0000000..406b50c --- /dev/null +++ b/quickshell/.config/quickshell/Common/Ref.qml @@ -0,0 +1,9 @@ +import QtQuick +import Quickshell + +QtObject { + required property Singleton service + + Component.onCompleted: service.refCount++ + Component.onDestruction: service.refCount-- +} diff --git a/quickshell/.config/quickshell/Common/SessionData.qml b/quickshell/.config/quickshell/Common/SessionData.qml new file mode 100644 index 0000000..ab1c0a4 --- /dev/null +++ b/quickshell/.config/quickshell/Common/SessionData.qml @@ -0,0 +1,597 @@ +pragma Singleton + +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Services + +Singleton { + + id: root + + property bool isLightMode: false + property string wallpaperPath: "" + property string wallpaperLastPath: "" + property string profileLastPath: "" + property bool perMonitorWallpaper: false + property var monitorWallpapers: ({}) + property bool doNotDisturb: false + property bool nightModeEnabled: false + property int nightModeTemperature: 4500 + property bool nightModeAutoEnabled: false + property string nightModeAutoMode: "time" + + property bool hasTriedDefaultSession: false + readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + readonly property string _stateDir: _stateUrl.startsWith("file://") ? _stateUrl.substring(7) : _stateUrl + property int nightModeStartHour: 18 + property int nightModeStartMinute: 0 + property int nightModeEndHour: 6 + property int nightModeEndMinute: 0 + property real latitude: 0.0 + property real longitude: 0.0 + property string nightModeLocationProvider: "" + property var pinnedApps: [] + property int selectedGpuIndex: 0 + property bool nvidiaGpuTempEnabled: false + property bool nonNvidiaGpuTempEnabled: false + property var enabledGpuPciIds: [] + property bool wallpaperCyclingEnabled: false + property string wallpaperCyclingMode: "interval" // "interval" or "time" + property int wallpaperCyclingInterval: 300 // seconds (5 minutes) + property string wallpaperCyclingTime: "06:00" // HH:mm format + property string lastBrightnessDevice: "" + property string notepadContent: "" + property string notepadCurrentFileName: "" + property string notepadCurrentFileUrl: "" + property string notepadLastSavedContent: "" + property var notepadTabs: [] + property int notepadCurrentTabIndex: 0 + + Component.onCompleted: { + loadSettings() + } + + function loadSettings() { + parseSettings(settingsFile.text()) + } + + function parseSettings(content) { + try { + if (content && content.trim()) { + var settings = JSON.parse(content) + isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false + wallpaperPath = settings.wallpaperPath !== undefined ? settings.wallpaperPath : "" + wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : "" + profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : "" + perMonitorWallpaper = settings.perMonitorWallpaper !== undefined ? settings.perMonitorWallpaper : false + monitorWallpapers = settings.monitorWallpapers !== undefined ? settings.monitorWallpapers : {} + doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false + nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false + nightModeTemperature = settings.nightModeTemperature !== undefined ? settings.nightModeTemperature : 4500 + nightModeAutoEnabled = settings.nightModeAutoEnabled !== undefined ? settings.nightModeAutoEnabled : false + nightModeAutoMode = settings.nightModeAutoMode !== undefined ? settings.nightModeAutoMode : "time" + // Handle legacy time format + if (settings.nightModeStartTime !== undefined) { + const parts = settings.nightModeStartTime.split(":") + nightModeStartHour = parseInt(parts[0]) || 18 + nightModeStartMinute = parseInt(parts[1]) || 0 + } else { + nightModeStartHour = settings.nightModeStartHour !== undefined ? settings.nightModeStartHour : 18 + nightModeStartMinute = settings.nightModeStartMinute !== undefined ? settings.nightModeStartMinute : 0 + } + if (settings.nightModeEndTime !== undefined) { + const parts = settings.nightModeEndTime.split(":") + nightModeEndHour = parseInt(parts[0]) || 6 + nightModeEndMinute = parseInt(parts[1]) || 0 + } else { + nightModeEndHour = settings.nightModeEndHour !== undefined ? settings.nightModeEndHour : 6 + nightModeEndMinute = settings.nightModeEndMinute !== undefined ? settings.nightModeEndMinute : 0 + } + latitude = settings.latitude !== undefined ? settings.latitude : 0.0 + longitude = settings.longitude !== undefined ? settings.longitude : 0.0 + nightModeLocationProvider = settings.nightModeLocationProvider !== undefined ? settings.nightModeLocationProvider : "" + pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [] + selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0 + nvidiaGpuTempEnabled = settings.nvidiaGpuTempEnabled !== undefined ? settings.nvidiaGpuTempEnabled : false + nonNvidiaGpuTempEnabled = settings.nonNvidiaGpuTempEnabled !== undefined ? settings.nonNvidiaGpuTempEnabled : false + enabledGpuPciIds = settings.enabledGpuPciIds !== undefined ? settings.enabledGpuPciIds : [] + wallpaperCyclingEnabled = settings.wallpaperCyclingEnabled !== undefined ? settings.wallpaperCyclingEnabled : false + wallpaperCyclingMode = settings.wallpaperCyclingMode !== undefined ? settings.wallpaperCyclingMode : "interval" + wallpaperCyclingInterval = settings.wallpaperCyclingInterval !== undefined ? settings.wallpaperCyclingInterval : 300 + wallpaperCyclingTime = settings.wallpaperCyclingTime !== undefined ? settings.wallpaperCyclingTime : "06:00" + lastBrightnessDevice = settings.lastBrightnessDevice !== undefined ? settings.lastBrightnessDevice : "" + notepadContent = settings.notepadContent !== undefined ? settings.notepadContent : "" + + // Generate system themes but don't override user's theme choice + if (typeof Theme !== "undefined") { + Theme.generateSystemThemesFromCurrentTheme() + } + notepadCurrentFileName = settings.notepadCurrentFileName !== undefined ? settings.notepadCurrentFileName : "" + notepadCurrentFileUrl = settings.notepadCurrentFileUrl !== undefined ? settings.notepadCurrentFileUrl : "" + notepadLastSavedContent = settings.notepadLastSavedContent !== undefined ? settings.notepadLastSavedContent : "" + notepadTabs = settings.notepadTabs !== undefined ? settings.notepadTabs : [] + notepadCurrentTabIndex = settings.notepadCurrentTabIndex !== undefined ? settings.notepadCurrentTabIndex : 0 + + // Migrate legacy single notepad to tabs if needed + if (notepadTabs.length === 0 && (notepadContent || notepadCurrentFileName)) { + notepadTabs = [{ + id: Date.now(), + title: notepadCurrentFileName || "Untitled", + content: notepadContent, + fileName: notepadCurrentFileName, + fileUrl: notepadCurrentFileUrl, + lastSavedContent: notepadLastSavedContent, + hasUnsavedChanges: false + }] + notepadCurrentTabIndex = 0 + } + + // Ensure at least one tab exists + if (notepadTabs.length === 0) { + notepadTabs = [{ + id: Date.now(), + title: "Untitled", + content: "", + fileName: "", + fileUrl: "", + lastSavedContent: "", + hasUnsavedChanges: false + }] + notepadCurrentTabIndex = 0 + } + } + } catch (e) { + + } + } + + function saveSettings() { + settingsFile.setText(JSON.stringify({ + "isLightMode": isLightMode, + "wallpaperPath": wallpaperPath, + "wallpaperLastPath": wallpaperLastPath, + "profileLastPath": profileLastPath, + "perMonitorWallpaper": perMonitorWallpaper, + "monitorWallpapers": monitorWallpapers, + "doNotDisturb": doNotDisturb, + "nightModeEnabled": nightModeEnabled, + "nightModeTemperature": nightModeTemperature, + "nightModeAutoEnabled": nightModeAutoEnabled, + "nightModeAutoMode": nightModeAutoMode, + "nightModeStartHour": nightModeStartHour, + "nightModeStartMinute": nightModeStartMinute, + "nightModeEndHour": nightModeEndHour, + "nightModeEndMinute": nightModeEndMinute, + "latitude": latitude, + "longitude": longitude, + "nightModeLocationProvider": nightModeLocationProvider, + "pinnedApps": pinnedApps, + "selectedGpuIndex": selectedGpuIndex, + "nvidiaGpuTempEnabled": nvidiaGpuTempEnabled, + "nonNvidiaGpuTempEnabled": nonNvidiaGpuTempEnabled, + "enabledGpuPciIds": enabledGpuPciIds, + "wallpaperCyclingEnabled": wallpaperCyclingEnabled, + "wallpaperCyclingMode": wallpaperCyclingMode, + "wallpaperCyclingInterval": wallpaperCyclingInterval, + "wallpaperCyclingTime": wallpaperCyclingTime, + "lastBrightnessDevice": lastBrightnessDevice, + "notepadContent": notepadContent, + "notepadCurrentFileName": notepadCurrentFileName, + "notepadCurrentFileUrl": notepadCurrentFileUrl, + "notepadLastSavedContent": notepadLastSavedContent, + "notepadTabs": notepadTabs, + "notepadCurrentTabIndex": notepadCurrentTabIndex + }, null, 2)) + } + + function setLightMode(lightMode) { + isLightMode = lightMode + saveSettings() + } + + function setDoNotDisturb(enabled) { + doNotDisturb = enabled + saveSettings() + } + + function setNightModeEnabled(enabled) { + nightModeEnabled = enabled + saveSettings() + } + + function setNightModeTemperature(temperature) { + nightModeTemperature = temperature + saveSettings() + } + + function setNightModeAutoEnabled(enabled) { + console.log("SessionData: Setting nightModeAutoEnabled to", enabled) + nightModeAutoEnabled = enabled + saveSettings() + } + + function setNightModeAutoMode(mode) { + nightModeAutoMode = mode + saveSettings() + } + + function setNightModeStartHour(hour) { + nightModeStartHour = hour + saveSettings() + } + + function setNightModeStartMinute(minute) { + nightModeStartMinute = minute + saveSettings() + } + + function setNightModeEndHour(hour) { + nightModeEndHour = hour + saveSettings() + } + + function setNightModeEndMinute(minute) { + nightModeEndMinute = minute + saveSettings() + } + + function setLatitude(lat) { + console.log("SessionData: Setting latitude to", lat) + latitude = lat + saveSettings() + } + + function setLongitude(lng) { + console.log("SessionData: Setting longitude to", lng) + longitude = lng + saveSettings() + } + + function setNightModeLocationProvider(provider) { + nightModeLocationProvider = provider + saveSettings() + } + + function setWallpaperPath(path) { + wallpaperPath = path + saveSettings() + } + + function setWallpaper(imagePath) { + wallpaperPath = imagePath + saveSettings() + + if (typeof Theme !== "undefined") { + if (Theme.currentTheme === Theme.dynamic) { + Theme.extractColors() + } + Theme.generateSystemThemesFromCurrentTheme() + } + } + + function setWallpaperColor(color) { + wallpaperPath = color + saveSettings() + + if (typeof Theme !== "undefined") { + if (Theme.currentTheme === Theme.dynamic) { + Theme.extractColors() + } + Theme.generateSystemThemesFromCurrentTheme() + } + } + + function clearWallpaper() { + wallpaperPath = "" + saveSettings() + + if (typeof Theme !== "undefined") { + if (typeof SettingsData !== "undefined" && SettingsData.theme) { + Theme.switchTheme(SettingsData.theme) + } else { + Theme.switchTheme("blue") + } + } + } + + function setWallpaperLastPath(path) { + wallpaperLastPath = path + saveSettings() + } + + function setProfileLastPath(path) { + profileLastPath = path + saveSettings() + } + + function setPinnedApps(apps) { + pinnedApps = apps + saveSettings() + } + + function addPinnedApp(appId) { + if (!appId) + return + var currentPinned = [...pinnedApps] + if (currentPinned.indexOf(appId) === -1) { + currentPinned.push(appId) + setPinnedApps(currentPinned) + } + } + + function removePinnedApp(appId) { + if (!appId) + return + var currentPinned = pinnedApps.filter(id => id !== appId) + setPinnedApps(currentPinned) + } + + function isPinnedApp(appId) { + return appId && pinnedApps.indexOf(appId) !== -1 + } + + function setSelectedGpuIndex(index) { + selectedGpuIndex = index + saveSettings() + } + + function setNvidiaGpuTempEnabled(enabled) { + nvidiaGpuTempEnabled = enabled + saveSettings() + } + + function setNonNvidiaGpuTempEnabled(enabled) { + nonNvidiaGpuTempEnabled = enabled + saveSettings() + } + + function setEnabledGpuPciIds(pciIds) { + enabledGpuPciIds = pciIds + saveSettings() + } + + function setWallpaperCyclingEnabled(enabled) { + wallpaperCyclingEnabled = enabled + saveSettings() + } + + function setWallpaperCyclingMode(mode) { + wallpaperCyclingMode = mode + saveSettings() + } + + function setWallpaperCyclingInterval(interval) { + wallpaperCyclingInterval = interval + saveSettings() + } + + function setWallpaperCyclingTime(time) { + wallpaperCyclingTime = time + saveSettings() + } + + function setPerMonitorWallpaper(enabled) { + perMonitorWallpaper = enabled + + // Disable automatic cycling when per-monitor mode is enabled + if (enabled && wallpaperCyclingEnabled) { + wallpaperCyclingEnabled = false + } + + saveSettings() + + // Refresh dynamic theming when per-monitor mode changes + if (typeof Theme !== "undefined") { + Theme.generateSystemThemesFromCurrentTheme() + } + } + + function setMonitorWallpaper(screenName, path) { + var newMonitorWallpapers = Object.assign({}, monitorWallpapers) + if (path && path !== "") { + newMonitorWallpapers[screenName] = path + } else { + delete newMonitorWallpapers[screenName] + } + monitorWallpapers = newMonitorWallpapers + saveSettings() + + // Trigger dynamic theming if this is the first monitor and dynamic theming is enabled + if (typeof Theme !== "undefined" && typeof Quickshell !== "undefined") { + var screens = Quickshell.screens + if (screens.length > 0 && screenName === screens[0].name) { + if (typeof SettingsData !== "undefined" && SettingsData.wallpaperDynamicTheming) { + Theme.switchTheme("dynamic") + Theme.extractColors() + } + Theme.generateSystemThemesFromCurrentTheme() + } + } + } + + function getMonitorWallpaper(screenName) { + if (!perMonitorWallpaper) { + return wallpaperPath + } + return monitorWallpapers[screenName] || wallpaperPath + } + + function setLastBrightnessDevice(device) { + lastBrightnessDevice = device + saveSettings() + } + + FileView { + id: settingsFile + + path: StandardPaths.writableLocation(StandardPaths.GenericStateLocation) + "/DankMaterialShell/session.json" + blockLoading: true + blockWrites: true + watchChanges: true + onLoaded: { + parseSettings(settingsFile.text()) + hasTriedDefaultSession = false + } + onLoadFailed: error => { + if (!hasTriedDefaultSession) { + hasTriedDefaultSession = true + defaultSessionCheckProcess.running = true + } + } + } + + Process { + id: defaultSessionCheckProcess + + command: ["sh", "-c", "CONFIG_DIR=\"" + _stateDir + + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-session.json\" ] && [ ! -f \"$CONFIG_DIR/session.json\" ]; then cp \"$CONFIG_DIR/default-session.json\" \"$CONFIG_DIR/session.json\" && echo 'copied'; else echo 'not_found'; fi"] + running: false + onExited: exitCode => { + if (exitCode === 0) { + console.log("Copied default-session.json to session.json") + settingsFile.reload() + } + } + } + + IpcHandler { + target: "wallpaper" + + function get(): string { + if (root.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use getFor(screenName) instead." + } + return root.wallpaperPath || "" + } + + function set(path: string): string { + if (root.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use setFor(screenName, path) instead." + } + + if (!path) { + return "ERROR: No path provided" + } + + var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path + + try { + root.setWallpaper(absolutePath) + return "SUCCESS: Wallpaper set to " + absolutePath + } catch (e) { + return "ERROR: Failed to set wallpaper: " + e.toString() + } + } + + function clear(): string { + root.setWallpaper("") + root.setPerMonitorWallpaper(false) + root.monitorWallpapers = {} + root.saveSettings() + return "SUCCESS: All wallpapers cleared" + } + + function next(): string { + if (root.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use nextFor(screenName) instead." + } + + if (!root.wallpaperPath) { + return "ERROR: No wallpaper set" + } + + try { + WallpaperCyclingService.cycleNextManually() + return "SUCCESS: Cycling to next wallpaper" + } catch (e) { + return "ERROR: Failed to cycle wallpaper: " + e.toString() + } + } + + function prev(): string { + if (root.perMonitorWallpaper) { + return "ERROR: Per-monitor mode enabled. Use prevFor(screenName) instead." + } + + if (!root.wallpaperPath) { + return "ERROR: No wallpaper set" + } + + try { + WallpaperCyclingService.cyclePrevManually() + return "SUCCESS: Cycling to previous wallpaper" + } catch (e) { + return "ERROR: Failed to cycle wallpaper: " + e.toString() + } + } + + function getFor(screenName: string): string { + if (!screenName) { + return "ERROR: No screen name provided" + } + return root.getMonitorWallpaper(screenName) || "" + } + + function setFor(screenName: string, path: string): string { + if (!screenName) { + return "ERROR: No screen name provided" + } + + if (!path) { + return "ERROR: No path provided" + } + + var absolutePath = path.startsWith("/") ? path : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/" + path + + try { + if (!root.perMonitorWallpaper) { + root.setPerMonitorWallpaper(true) + } + root.setMonitorWallpaper(screenName, absolutePath) + return "SUCCESS: Wallpaper set for " + screenName + " to " + absolutePath + } catch (e) { + return "ERROR: Failed to set wallpaper for " + screenName + ": " + e.toString() + } + } + + function nextFor(screenName: string): string { + if (!screenName) { + return "ERROR: No screen name provided" + } + + var currentWallpaper = root.getMonitorWallpaper(screenName) + if (!currentWallpaper) { + return "ERROR: No wallpaper set for " + screenName + } + + try { + WallpaperCyclingService.cycleNextForMonitor(screenName) + return "SUCCESS: Cycling to next wallpaper for " + screenName + } catch (e) { + return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString() + } + } + + function prevFor(screenName: string): string { + if (!screenName) { + return "ERROR: No screen name provided" + } + + var currentWallpaper = root.getMonitorWallpaper(screenName) + if (!currentWallpaper) { + return "ERROR: No wallpaper set for " + screenName + } + + try { + WallpaperCyclingService.cyclePrevForMonitor(screenName) + return "SUCCESS: Cycling to previous wallpaper for " + screenName + } catch (e) { + return "ERROR: Failed to cycle wallpaper for " + screenName + ": " + e.toString() + } + } + } +} diff --git a/quickshell/.config/quickshell/Common/SettingsData.qml b/quickshell/.config/quickshell/Common/SettingsData.qml new file mode 100644 index 0000000..b9b6e57 --- /dev/null +++ b/quickshell/.config/quickshell/Common/SettingsData.qml @@ -0,0 +1,1123 @@ +pragma Singleton + +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import qs.Services + +Singleton { + id: root + + // Theme settings + property string currentThemeName: "blue" + property string customThemeFile: "" + property real topBarTransparency: 0.75 + property real topBarWidgetTransparency: 0.85 + property real popupTransparency: 0.92 + property real dockTransparency: 1 + property bool use24HourClock: true + property bool useFahrenheit: false + property bool nightModeEnabled: false + property string weatherLocation: "New York, NY" + property string weatherCoordinates: "40.7128,-74.0060" + property bool useAutoLocation: false + property bool showLauncherButton: true + property bool showWorkspaceSwitcher: true + property bool showFocusedWindow: true + property bool showWeather: true + property bool showMusic: true + property bool showClipboard: true + property bool showCpuUsage: true + property bool showMemUsage: true + property bool showCpuTemp: true + property bool showGpuTemp: true + property int selectedGpuIndex: 0 + property var enabledGpuPciIds: [] + property bool showSystemTray: true + property bool showClock: true + property bool showNotificationButton: true + property bool showBattery: true + property bool showControlCenterButton: true + property bool controlCenterShowNetworkIcon: true + property bool controlCenterShowBluetoothIcon: true + property bool controlCenterShowAudioIcon: true + property bool showWorkspaceIndex: false + property bool showWorkspacePadding: false + property bool showWorkspaceApps: false + property int maxWorkspaceIcons: 3 + property bool workspacesPerMonitor: true + property var workspaceNameIcons: ({}) + property bool clockCompactMode: false + property bool focusedWindowCompactMode: false + property bool runningAppsCompactMode: true + property bool runningAppsCurrentWorkspace: false + property string clockDateFormat: "" + property string lockDateFormat: "" + property int mediaSize: 1 + property var topBarLeftWidgets: ["launcherButton", "workspaceSwitcher", "focusedWindow"] + property var topBarCenterWidgets: ["music", "clock", "weather"] + property var topBarRightWidgets: ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"] + property alias topBarLeftWidgetsModel: leftWidgetsModel + property alias topBarCenterWidgetsModel: centerWidgetsModel + property alias topBarRightWidgetsModel: rightWidgetsModel + property string appLauncherViewMode: "list" + property string spotlightModalViewMode: "list" + property string networkPreference: "auto" + property string iconTheme: "System Default" + property var availableIconThemes: ["System Default"] + property string systemDefaultIconTheme: "" + property bool qt5ctAvailable: false + property bool qt6ctAvailable: false + property bool gtkAvailable: false + property bool useOSLogo: false + property string osLogoColorOverride: "" + property real osLogoBrightness: 0.5 + property real osLogoContrast: 1 + property bool wallpaperDynamicTheming: true + property bool weatherEnabled: true + property string fontFamily: "Inter Variable" + property string monoFontFamily: "Fira Code" + property int fontWeight: Font.Normal + property real fontScale: 1.0 + property bool gtkThemingEnabled: false + property bool qtThemingEnabled: false + property bool showDock: false + property bool dockAutoHide: false + property real cornerRadius: 12 + property bool notificationOverlayEnabled: false + property bool topBarAutoHide: false + property bool topBarOpenOnOverview: false + property bool topBarVisible: true + property real topBarSpacing: 4 + property real topBarBottomGap: 0 + property real topBarInnerPadding: 8 + property bool topBarSquareCorners: false + property bool topBarNoBackground: false + property bool lockScreenShowPowerActions: true + property bool hideBrightnessSlider: false + property int notificationTimeoutLow: 5000 + property int notificationTimeoutNormal: 5000 + property int notificationTimeoutCritical: 0 + property var screenPreferences: ({}) + readonly property string defaultFontFamily: "Inter Variable" + readonly property string defaultMonoFontFamily: "Fira Code" + readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) + readonly property string _configUrl: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + readonly property string _configDir: _configUrl.startsWith("file://") ? _configUrl.substring(7) : _configUrl + + signal forceTopBarLayoutRefresh + signal widgetDataChanged + signal workspaceIconsUpdated + + function getEffectiveTimeFormat() { + if (use24HourClock) { + return Locale.ShortFormat + } else { + return "h:mm AP" + } + } + + function getEffectiveClockDateFormat() { + return clockDateFormat && clockDateFormat.length > 0 ? clockDateFormat : "ddd d" + } + + function getEffectiveLockDateFormat() { + return lockDateFormat && lockDateFormat.length > 0 ? lockDateFormat : Locale.LongFormat + } + + function initializeListModels() { + // ! Hack-ish to add all properties to the listmodel once + // ! allows the properties to be bound on new widget addtions + var dummyItem = { + "widgetId": "dummy", + "enabled": true, + "size": 20, + "selectedGpuIndex": 0, + "pciId": "" + } + leftWidgetsModel.append(dummyItem) + centerWidgetsModel.append(dummyItem) + rightWidgetsModel.append(dummyItem) + + updateListModel(leftWidgetsModel, topBarLeftWidgets) + updateListModel(centerWidgetsModel, topBarCenterWidgets) + updateListModel(rightWidgetsModel, topBarRightWidgets) + } + + function loadSettings() { + parseSettings(settingsFile.text()) + } + + function parseSettings(content) { + try { + if (content && content.trim()) { + var settings = JSON.parse(content) + // Auto-migrate from old theme system + if (settings.themeIndex !== undefined || settings.themeIsDynamic !== undefined) { + const themeNames = ["blue", "deepBlue", "purple", "green", "orange", "red", "cyan", "pink", "amber", "coral"] + if (settings.themeIsDynamic) { + currentThemeName = "dynamic" + } else if (settings.themeIndex >= 0 && settings.themeIndex < themeNames.length) { + currentThemeName = themeNames[settings.themeIndex] + } + console.log("Auto-migrated theme from index", settings.themeIndex, "to", currentThemeName) + } else { + currentThemeName = settings.currentThemeName !== undefined ? settings.currentThemeName : "blue" + } + customThemeFile = settings.customThemeFile !== undefined ? settings.customThemeFile : "" + topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75 + topBarWidgetTransparency = settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 0.85 + popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92 + dockTransparency = settings.dockTransparency !== undefined ? (settings.dockTransparency > 1 ? settings.dockTransparency / 100 : settings.dockTransparency) : 1 + use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true + useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false + nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false + weatherLocation = settings.weatherLocation !== undefined ? settings.weatherLocation : "New York, NY" + weatherCoordinates = settings.weatherCoordinates !== undefined ? settings.weatherCoordinates : "40.7128,-74.0060" + useAutoLocation = settings.useAutoLocation !== undefined ? settings.useAutoLocation : false + weatherEnabled = settings.weatherEnabled !== undefined ? settings.weatherEnabled : true + showLauncherButton = settings.showLauncherButton !== undefined ? settings.showLauncherButton : true + showWorkspaceSwitcher = settings.showWorkspaceSwitcher !== undefined ? settings.showWorkspaceSwitcher : true + showFocusedWindow = settings.showFocusedWindow !== undefined ? settings.showFocusedWindow : true + showWeather = settings.showWeather !== undefined ? settings.showWeather : true + showMusic = settings.showMusic !== undefined ? settings.showMusic : true + showClipboard = settings.showClipboard !== undefined ? settings.showClipboard : true + showCpuUsage = settings.showCpuUsage !== undefined ? settings.showCpuUsage : true + showMemUsage = settings.showMemUsage !== undefined ? settings.showMemUsage : true + showCpuTemp = settings.showCpuTemp !== undefined ? settings.showCpuTemp : true + showGpuTemp = settings.showGpuTemp !== undefined ? settings.showGpuTemp : true + selectedGpuIndex = settings.selectedGpuIndex !== undefined ? settings.selectedGpuIndex : 0 + enabledGpuPciIds = settings.enabledGpuPciIds !== undefined ? settings.enabledGpuPciIds : [] + showSystemTray = settings.showSystemTray !== undefined ? settings.showSystemTray : true + showClock = settings.showClock !== undefined ? settings.showClock : true + showNotificationButton = settings.showNotificationButton !== undefined ? settings.showNotificationButton : true + showBattery = settings.showBattery !== undefined ? settings.showBattery : true + showControlCenterButton = settings.showControlCenterButton !== undefined ? settings.showControlCenterButton : true + controlCenterShowNetworkIcon = settings.controlCenterShowNetworkIcon !== undefined ? settings.controlCenterShowNetworkIcon : true + controlCenterShowBluetoothIcon = settings.controlCenterShowBluetoothIcon !== undefined ? settings.controlCenterShowBluetoothIcon : true + controlCenterShowAudioIcon = settings.controlCenterShowAudioIcon !== undefined ? settings.controlCenterShowAudioIcon : true + showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false + showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false + showWorkspaceApps = settings.showWorkspaceApps !== undefined ? settings.showWorkspaceApps : false + maxWorkspaceIcons = settings.maxWorkspaceIcons !== undefined ? settings.maxWorkspaceIcons : 3 + workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({}) + workspacesPerMonitor = settings.workspacesPerMonitor !== undefined ? settings.workspacesPerMonitor : true + clockCompactMode = settings.clockCompactMode !== undefined ? settings.clockCompactMode : false + focusedWindowCompactMode = settings.focusedWindowCompactMode !== undefined ? settings.focusedWindowCompactMode : false + runningAppsCompactMode = settings.runningAppsCompactMode !== undefined ? settings.runningAppsCompactMode : true + runningAppsCurrentWorkspace = settings.runningAppsCurrentWorkspace !== undefined ? settings.runningAppsCurrentWorkspace : false + clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : "" + lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "" + mediaSize = settings.mediaSize !== undefined ? settings.mediaSize : (settings.mediaCompactMode !== undefined ? (settings.mediaCompactMode ? 0 : 1) : 1) + if (settings.topBarWidgetOrder) { + topBarLeftWidgets = settings.topBarWidgetOrder.filter(w => { + return ["launcherButton", "workspaceSwitcher", "focusedWindow"].includes(w) + }) + topBarCenterWidgets = settings.topBarWidgetOrder.filter(w => { + return ["clock", "music", "weather"].includes(w) + }) + topBarRightWidgets = settings.topBarWidgetOrder.filter(w => { + return ["systemTray", "clipboard", "systemResources", "notificationButton", "battery", "controlCenterButton"].includes(w) + }) + } else { + var leftWidgets = settings.topBarLeftWidgets !== undefined ? settings.topBarLeftWidgets : ["launcherButton", "workspaceSwitcher", "focusedWindow"] + var centerWidgets = settings.topBarCenterWidgets !== undefined ? settings.topBarCenterWidgets : ["music", "clock", "weather"] + var rightWidgets = settings.topBarRightWidgets !== undefined ? settings.topBarRightWidgets : ["systemTray", "clipboard", "cpuUsage", "memUsage", "notificationButton", "battery", "controlCenterButton"] + topBarLeftWidgets = leftWidgets + topBarCenterWidgets = centerWidgets + topBarRightWidgets = rightWidgets + updateListModel(leftWidgetsModel, leftWidgets) + updateListModel(centerWidgetsModel, centerWidgets) + updateListModel(rightWidgetsModel, rightWidgets) + } + appLauncherViewMode = settings.appLauncherViewMode !== undefined ? settings.appLauncherViewMode : "list" + spotlightModalViewMode = settings.spotlightModalViewMode !== undefined ? settings.spotlightModalViewMode : "list" + networkPreference = settings.networkPreference !== undefined ? settings.networkPreference : "auto" + iconTheme = settings.iconTheme !== undefined ? settings.iconTheme : "System Default" + useOSLogo = settings.useOSLogo !== undefined ? settings.useOSLogo : false + osLogoColorOverride = settings.osLogoColorOverride !== undefined ? settings.osLogoColorOverride : "" + osLogoBrightness = settings.osLogoBrightness !== undefined ? settings.osLogoBrightness : 0.5 + osLogoContrast = settings.osLogoContrast !== undefined ? settings.osLogoContrast : 1 + wallpaperDynamicTheming = settings.wallpaperDynamicTheming !== undefined ? settings.wallpaperDynamicTheming : true + fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily + monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily + fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal + fontScale = settings.fontScale !== undefined ? settings.fontScale : 1.0 + gtkThemingEnabled = settings.gtkThemingEnabled !== undefined ? settings.gtkThemingEnabled : false + qtThemingEnabled = settings.qtThemingEnabled !== undefined ? settings.qtThemingEnabled : false + showDock = settings.showDock !== undefined ? settings.showDock : false + dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false + cornerRadius = settings.cornerRadius !== undefined ? settings.cornerRadius : 12 + notificationOverlayEnabled = settings.notificationOverlayEnabled !== undefined ? settings.notificationOverlayEnabled : false + topBarAutoHide = settings.topBarAutoHide !== undefined ? settings.topBarAutoHide : false + topBarOpenOnOverview = settings.topBarOpenOnOverview !== undefined ? settings.topBarOpenOnOverview : false + topBarVisible = settings.topBarVisible !== undefined ? settings.topBarVisible : true + notificationTimeoutLow = settings.notificationTimeoutLow !== undefined ? settings.notificationTimeoutLow : 5000 + notificationTimeoutNormal = settings.notificationTimeoutNormal !== undefined ? settings.notificationTimeoutNormal : 5000 + notificationTimeoutCritical = settings.notificationTimeoutCritical !== undefined ? settings.notificationTimeoutCritical : 0 + topBarSpacing = settings.topBarSpacing !== undefined ? settings.topBarSpacing : 4 + topBarBottomGap = settings.topBarBottomGap !== undefined ? settings.topBarBottomGap : 0 + topBarInnerPadding = settings.topBarInnerPadding !== undefined ? settings.topBarInnerPadding : 8 + topBarSquareCorners = settings.topBarSquareCorners !== undefined ? settings.topBarSquareCorners : false + topBarNoBackground = settings.topBarNoBackground !== undefined ? settings.topBarNoBackground : false + lockScreenShowPowerActions = settings.lockScreenShowPowerActions !== undefined ? settings.lockScreenShowPowerActions : true + hideBrightnessSlider = settings.hideBrightnessSlider !== undefined ? settings.hideBrightnessSlider : false + screenPreferences = settings.screenPreferences !== undefined ? settings.screenPreferences : ({}) + applyStoredTheme() + detectAvailableIconThemes() + detectQtTools() + updateGtkIconTheme(iconTheme) + applyStoredIconTheme() + } else { + applyStoredTheme() + } + } catch (e) { + applyStoredTheme() + } + } + + function saveSettings() { + settingsFile.setText(JSON.stringify({ + "currentThemeName": currentThemeName, + "customThemeFile": customThemeFile, + "topBarTransparency": topBarTransparency, + "topBarWidgetTransparency": topBarWidgetTransparency, + "popupTransparency": popupTransparency, + "dockTransparency": dockTransparency, + "use24HourClock": use24HourClock, + "useFahrenheit": useFahrenheit, + "nightModeEnabled": nightModeEnabled, + "weatherLocation": weatherLocation, + "weatherCoordinates": weatherCoordinates, + "useAutoLocation": useAutoLocation, + "weatherEnabled": weatherEnabled, + "showLauncherButton": showLauncherButton, + "showWorkspaceSwitcher": showWorkspaceSwitcher, + "showFocusedWindow": showFocusedWindow, + "showWeather": showWeather, + "showMusic": showMusic, + "showClipboard": showClipboard, + "showCpuUsage": showCpuUsage, + "showMemUsage": showMemUsage, + "showCpuTemp": showCpuTemp, + "showGpuTemp": showGpuTemp, + "selectedGpuIndex": selectedGpuIndex, + "enabledGpuPciIds": enabledGpuPciIds, + "showSystemTray": showSystemTray, + "showClock": showClock, + "showNotificationButton": showNotificationButton, + "showBattery": showBattery, + "showControlCenterButton": showControlCenterButton, + "controlCenterShowNetworkIcon": controlCenterShowNetworkIcon, + "controlCenterShowBluetoothIcon": controlCenterShowBluetoothIcon, + "controlCenterShowAudioIcon": controlCenterShowAudioIcon, + "showWorkspaceIndex": showWorkspaceIndex, + "showWorkspacePadding": showWorkspacePadding, + "showWorkspaceApps": showWorkspaceApps, + "maxWorkspaceIcons": maxWorkspaceIcons, + "workspacesPerMonitor": workspacesPerMonitor, + "workspaceNameIcons": workspaceNameIcons, + "clockCompactMode": clockCompactMode, + "focusedWindowCompactMode": focusedWindowCompactMode, + "runningAppsCompactMode": runningAppsCompactMode, + "runningAppsCurrentWorkspace": runningAppsCurrentWorkspace, + "clockDateFormat": clockDateFormat, + "lockDateFormat": lockDateFormat, + "mediaSize": mediaSize, + "topBarLeftWidgets": topBarLeftWidgets, + "topBarCenterWidgets": topBarCenterWidgets, + "topBarRightWidgets": topBarRightWidgets, + "appLauncherViewMode": appLauncherViewMode, + "spotlightModalViewMode": spotlightModalViewMode, + "networkPreference": networkPreference, + "iconTheme": iconTheme, + "useOSLogo": useOSLogo, + "osLogoColorOverride": osLogoColorOverride, + "osLogoBrightness": osLogoBrightness, + "osLogoContrast": osLogoContrast, + "wallpaperDynamicTheming": wallpaperDynamicTheming, + "fontFamily": fontFamily, + "monoFontFamily": monoFontFamily, + "fontWeight": fontWeight, + "fontScale": fontScale, + "gtkThemingEnabled": gtkThemingEnabled, + "qtThemingEnabled": qtThemingEnabled, + "showDock": showDock, + "dockAutoHide": dockAutoHide, + "cornerRadius": cornerRadius, + "notificationOverlayEnabled": notificationOverlayEnabled, + "topBarAutoHide": topBarAutoHide, + "topBarOpenOnOverview": topBarOpenOnOverview, + "topBarVisible": topBarVisible, + "topBarSpacing": topBarSpacing, + "topBarBottomGap": topBarBottomGap, + "topBarInnerPadding": topBarInnerPadding, + "topBarSquareCorners": topBarSquareCorners, + "topBarNoBackground": topBarNoBackground, + "lockScreenShowPowerActions": lockScreenShowPowerActions, + "hideBrightnessSlider": hideBrightnessSlider, + "notificationTimeoutLow": notificationTimeoutLow, + "notificationTimeoutNormal": notificationTimeoutNormal, + "notificationTimeoutCritical": notificationTimeoutCritical, + "screenPreferences": screenPreferences + }, null, 2)) + } + + function setShowWorkspaceIndex(enabled) { + showWorkspaceIndex = enabled + saveSettings() + } + + function setShowWorkspacePadding(enabled) { + showWorkspacePadding = enabled + saveSettings() + } + + function setShowWorkspaceApps(enabled) { + showWorkspaceApps = enabled + saveSettings() + } + + function setMaxWorkspaceIcons(maxIcons) { + maxWorkspaceIcons = maxIcons + saveSettings() + } + + function setWorkspacesPerMonitor(enabled) { + workspacesPerMonitor = enabled + saveSettings() + } + + function setWorkspaceNameIcon(workspaceName, iconData) { + var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons)) + iconMap[workspaceName] = iconData + workspaceNameIcons = iconMap + saveSettings() + workspaceIconsUpdated() + } + + function removeWorkspaceNameIcon(workspaceName) { + var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons)) + delete iconMap[workspaceName] + workspaceNameIcons = iconMap + saveSettings() + workspaceIconsUpdated() + } + + function getWorkspaceNameIcon(workspaceName) { + return workspaceNameIcons[workspaceName] || null + } + + function hasNamedWorkspaces() { + if (typeof NiriService === "undefined" || !CompositorService.isNiri) + return false + + for (var i = 0; i < NiriService.allWorkspaces.length; i++) { + var ws = NiriService.allWorkspaces[i] + if (ws.name && ws.name.trim() !== "") + return true + } + return false + } + + function getNamedWorkspaces() { + var namedWorkspaces = [] + if (typeof NiriService === "undefined" || !CompositorService.isNiri) + return namedWorkspaces + + for (const ws of NiriService.allWorkspaces) { + if (ws.name && ws.name.trim() !== "") { + namedWorkspaces.push(ws.name) + } + } + return namedWorkspaces + } + + function setClockCompactMode(enabled) { + clockCompactMode = enabled + saveSettings() + } + + function setFocusedWindowCompactMode(enabled) { + focusedWindowCompactMode = enabled + saveSettings() + } + + function setRunningAppsCompactMode(enabled) { + runningAppsCompactMode = enabled + saveSettings() + } + + function setRunningAppsCurrentWorkspace(enabled) { + runningAppsCurrentWorkspace = enabled + saveSettings() + } + + function setClockDateFormat(format) { + clockDateFormat = format || "" + saveSettings() + } + + function setLockDateFormat(format) { + lockDateFormat = format || "" + saveSettings() + } + + function setMediaSize(size) { + mediaSize = size + saveSettings() + } + + function applyStoredTheme() { + if (typeof Theme !== "undefined") + Theme.switchTheme(currentThemeName, false) + else + Qt.callLater(() => { + if (typeof Theme !== "undefined") + Theme.switchTheme(currentThemeName, false) + }) + } + + function setTheme(themeName) { + currentThemeName = themeName + saveSettings() + } + + function setCustomThemeFile(filePath) { + customThemeFile = filePath + saveSettings() + } + + function setTopBarTransparency(transparency) { + topBarTransparency = transparency + saveSettings() + } + + function setTopBarWidgetTransparency(transparency) { + topBarWidgetTransparency = transparency + saveSettings() + } + + function setPopupTransparency(transparency) { + popupTransparency = transparency + saveSettings() + } + + function setDockTransparency(transparency) { + dockTransparency = transparency + saveSettings() + } + + // New preference setters + function setClockFormat(use24Hour) { + use24HourClock = use24Hour + saveSettings() + } + + function setTemperatureUnit(fahrenheit) { + useFahrenheit = fahrenheit + saveSettings() + } + + function setNightModeEnabled(enabled) { + nightModeEnabled = enabled + saveSettings() + } + + // Widget visibility setters + function setShowLauncherButton(enabled) { + showLauncherButton = enabled + saveSettings() + } + + function setShowWorkspaceSwitcher(enabled) { + showWorkspaceSwitcher = enabled + saveSettings() + } + + function setShowFocusedWindow(enabled) { + showFocusedWindow = enabled + saveSettings() + } + + function setShowWeather(enabled) { + showWeather = enabled + saveSettings() + } + + function setShowMusic(enabled) { + showMusic = enabled + saveSettings() + } + + function setShowClipboard(enabled) { + showClipboard = enabled + saveSettings() + } + + function setShowCpuUsage(enabled) { + showCpuUsage = enabled + saveSettings() + } + + function setShowMemUsage(enabled) { + showMemUsage = enabled + saveSettings() + } + + function setShowCpuTemp(enabled) { + showCpuTemp = enabled + saveSettings() + } + + function setShowGpuTemp(enabled) { + showGpuTemp = enabled + saveSettings() + } + + function setSelectedGpuIndex(index) { + selectedGpuIndex = index + saveSettings() + } + + function setEnabledGpuPciIds(pciIds) { + enabledGpuPciIds = pciIds + saveSettings() + } + + function setShowSystemTray(enabled) { + showSystemTray = enabled + saveSettings() + } + + function setShowClock(enabled) { + showClock = enabled + saveSettings() + } + + function setShowNotificationButton(enabled) { + showNotificationButton = enabled + saveSettings() + } + + function setShowBattery(enabled) { + showBattery = enabled + saveSettings() + } + + function setShowControlCenterButton(enabled) { + showControlCenterButton = enabled + saveSettings() + } + + function setControlCenterShowNetworkIcon(enabled) { + controlCenterShowNetworkIcon = enabled + saveSettings() + } + + function setControlCenterShowBluetoothIcon(enabled) { + controlCenterShowBluetoothIcon = enabled + saveSettings() + } + + function setControlCenterShowAudioIcon(enabled) { + controlCenterShowAudioIcon = enabled + saveSettings() + } + + function setTopBarWidgetOrder(order) { + topBarWidgetOrder = order + saveSettings() + } + + function setTopBarLeftWidgets(order) { + topBarLeftWidgets = order + updateListModel(leftWidgetsModel, order) + saveSettings() + } + + function setTopBarCenterWidgets(order) { + topBarCenterWidgets = order + updateListModel(centerWidgetsModel, order) + saveSettings() + } + + function setTopBarRightWidgets(order) { + topBarRightWidgets = order + updateListModel(rightWidgetsModel, order) + saveSettings() + } + + function updateListModel(listModel, order) { + listModel.clear() + for (var i = 0; i < order.length; i++) { + var widgetId = typeof order[i] === "string" ? order[i] : order[i].id + var enabled = typeof order[i] === "string" ? true : order[i].enabled + var size = typeof order[i] === "string" ? undefined : order[i].size + var selectedGpuIndex = typeof order[i] === "string" ? undefined : order[i].selectedGpuIndex + var pciId = typeof order[i] === "string" ? undefined : order[i].pciId + var item = { + "widgetId": widgetId, + "enabled": enabled + } + if (size !== undefined) + item.size = size + if (selectedGpuIndex !== undefined) + item.selectedGpuIndex = selectedGpuIndex + if (pciId !== undefined) + item.pciId = pciId + + listModel.append(item) + } + // Emit signal to notify widgets that data has changed + widgetDataChanged() + } + + function resetTopBarWidgetsToDefault() { + var defaultLeft = ["launcherButton", "workspaceSwitcher", "focusedWindow"] + var defaultCenter = ["music", "clock", "weather"] + var defaultRight = ["systemTray", "clipboard", "notificationButton", "battery", "controlCenterButton"] + topBarLeftWidgets = defaultLeft + topBarCenterWidgets = defaultCenter + topBarRightWidgets = defaultRight + updateListModel(leftWidgetsModel, defaultLeft) + updateListModel(centerWidgetsModel, defaultCenter) + updateListModel(rightWidgetsModel, defaultRight) + showLauncherButton = true + showWorkspaceSwitcher = true + showFocusedWindow = true + showWeather = true + showMusic = true + showClipboard = true + showCpuUsage = true + showMemUsage = true + showCpuTemp = true + showGpuTemp = true + showSystemTray = true + showClock = true + showNotificationButton = true + showBattery = true + showControlCenterButton = true + saveSettings() + } + + // View mode setters + function setAppLauncherViewMode(mode) { + appLauncherViewMode = mode + saveSettings() + } + + function setSpotlightModalViewMode(mode) { + spotlightModalViewMode = mode + saveSettings() + } + + // Weather location setter + function setWeatherLocation(displayName, coordinates) { + weatherLocation = displayName + weatherCoordinates = coordinates + saveSettings() + } + + function setAutoLocation(enabled) { + useAutoLocation = enabled + saveSettings() + } + + function setWeatherEnabled(enabled) { + weatherEnabled = enabled + saveSettings() + } + + // Network preference setter + function setNetworkPreference(preference) { + networkPreference = preference + saveSettings() + } + + function detectAvailableIconThemes() { + // First detect system default, then available themes + systemDefaultDetectionProcess.running = true + } + + function detectQtTools() { + qtToolsDetectionProcess.running = true + } + + function setIconTheme(themeName) { + iconTheme = themeName + updateGtkIconTheme(themeName) + updateQtIconTheme(themeName) + saveSettings() + if (typeof Theme !== "undefined" && Theme.currentTheme === Theme.dynamic) + Theme.generateSystemThemes() + } + + function updateGtkIconTheme(themeName) { + var gtkThemeName = (themeName === "System Default") ? systemDefaultIconTheme : themeName + if (gtkThemeName !== "System Default" && gtkThemeName !== "") { + var script = "if command -v gsettings >/dev/null 2>&1 && gsettings list-schemas | grep -q org.gnome.desktop.interface; then\n" + + " gsettings set org.gnome.desktop.interface icon-theme '" + gtkThemeName + "'\n" + " echo 'Updated via gsettings'\n" + "elif command -v dconf >/dev/null 2>&1; then\n" + " dconf write /org/gnome/desktop/interface/icon-theme \\\"" + gtkThemeName + "\\\"\n" + + " echo 'Updated via dconf'\n" + "fi\n" + "\n" + "# Ensure config directories exist\n" + "mkdir -p " + _configDir + "/gtk-3.0 " + _configDir + + "/gtk-4.0\n" + "\n" + "# Update settings.ini files (keep existing gtk-theme-name)\n" + "for config_dir in " + _configDir + "/gtk-3.0 " + _configDir + "/gtk-4.0; do\n" + + " settings_file=\"$config_dir/settings.ini\"\n" + " if [ -f \"$settings_file\" ]; then\n" + " # Update existing icon-theme-name line or add it\n" + " if grep -q '^gtk-icon-theme-name=' \"$settings_file\"; then\n" + " sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=" + gtkThemeName + "/' \"$settings_file\"\n" + " else\n" + + " # Add icon theme setting to [Settings] section or create it\n" + " if grep -q '\\[Settings\\]' \"$settings_file\"; then\n" + " sed -i '/\\[Settings\\]/a gtk-icon-theme-name=" + gtkThemeName + "' \"$settings_file\"\n" + " else\n" + " echo -e '\\n[Settings]\\ngtk-icon-theme-name=" + gtkThemeName + + "' >> \"$settings_file\"\n" + " fi\n" + " fi\n" + " else\n" + " # Create new settings.ini file\n" + " echo -e '[Settings]\\ngtk-icon-theme-name=" + gtkThemeName + "' > \"$settings_file\"\n" + + " fi\n" + " echo \"Updated $settings_file\"\n" + "done\n" + "\n" + "# Clear icon cache and force refresh\n" + "rm -rf ~/.cache/icon-cache ~/.cache/thumbnails 2>/dev/null || true\n" + "# Send SIGHUP to running GTK applications to reload themes (Fedora-specific)\n" + "pkill -HUP -f 'gtk' 2>/dev/null || true\n" + Quickshell.execDetached(["sh", "-lc", script]) + } + } + + function updateQtIconTheme(themeName) { + var qtThemeName = (themeName === "System Default") ? "" : themeName + var home = _shq(root._homeUrl.replace("file://", "")) + if (!qtThemeName) { + // When "System Default" is selected, don't modify the config files at all + // This preserves the user's existing qt6ct configuration + return + } + var script = "mkdir -p " + _configDir + "/qt5ct " + _configDir + "/qt6ct " + _configDir + "/environment.d 2>/dev/null || true\n" + "update_qt_icon_theme() {\n" + " local config_file=\"$1\"\n" + + " local theme_name=\"$2\"\n" + " if [ -f \"$config_file\" ]; then\n" + " if grep -q '^\\[Appearance\\]' \"$config_file\"; then\n" + " if grep -q '^icon_theme=' \"$config_file\"; then\n" + " sed -i \"s/^icon_theme=.*/icon_theme=$theme_name/\" \"$config_file\"\n" + " else\n" + " sed -i \"/^\\[Appearance\\]/a icon_theme=$theme_name\" \"$config_file\"\n" + " fi\n" + + " else\n" + " printf '\\n[Appearance]\\nicon_theme=%s\\n' \"$theme_name\" >> \"$config_file\"\n" + " fi\n" + " else\n" + " printf '[Appearance]\\nicon_theme=%s\\n' \"$theme_name\" > \"$config_file\"\n" + " fi\n" + "}\n" + "update_qt_icon_theme " + _configDir + "/qt5ct/qt5ct.conf " + _shq( + qtThemeName) + "\n" + "update_qt_icon_theme " + _configDir + "/qt6ct/qt6ct.conf " + _shq(qtThemeName) + "\n" + "rm -rf " + home + "/.cache/icon-cache " + home + "/.cache/thumbnails 2>/dev/null || true\n" + Quickshell.execDetached(["sh", "-lc", script]) + } + + function applyStoredIconTheme() { + updateGtkIconTheme(iconTheme) + updateQtIconTheme(iconTheme) + } + + function setUseOSLogo(enabled) { + useOSLogo = enabled + saveSettings() + } + + function setOSLogoColorOverride(color) { + osLogoColorOverride = color + saveSettings() + } + + function setOSLogoBrightness(brightness) { + osLogoBrightness = brightness + saveSettings() + } + + function setOSLogoContrast(contrast) { + osLogoContrast = contrast + saveSettings() + } + + function setWallpaperDynamicTheming(enabled) { + wallpaperDynamicTheming = enabled + saveSettings() + } + + function setFontFamily(family) { + fontFamily = family + saveSettings() + } + + function setFontWeight(weight) { + fontWeight = weight + saveSettings() + } + + function setMonoFontFamily(family) { + monoFontFamily = family + saveSettings() + } + + function setFontScale(scale) { + fontScale = scale + saveSettings() + } + + function setGtkThemingEnabled(enabled) { + gtkThemingEnabled = enabled + saveSettings() + if (enabled && typeof Theme !== "undefined") { + Theme.generateSystemThemesFromCurrentTheme() + } + } + + function setQtThemingEnabled(enabled) { + qtThemingEnabled = enabled + saveSettings() + if (enabled && typeof Theme !== "undefined") { + Theme.generateSystemThemesFromCurrentTheme() + } + } + + function setShowDock(enabled) { + showDock = enabled + saveSettings() + } + + function setDockAutoHide(enabled) { + dockAutoHide = enabled + saveSettings() + } + + function setCornerRadius(radius) { + cornerRadius = radius + saveSettings() + } + + function setNotificationOverlayEnabled(enabled) { + notificationOverlayEnabled = enabled + saveSettings() + } + + function setTopBarAutoHide(enabled) { + topBarAutoHide = enabled + saveSettings() + } + + function setTopBarOpenOnOverview(enabled) { + topBarOpenOnOverview = enabled + saveSettings() + } + + function setTopBarVisible(visible) { + topBarVisible = visible + saveSettings() + } + + function toggleTopBarVisible() { + topBarVisible = !topBarVisible + saveSettings() + } + + function setNotificationTimeoutLow(timeout) { + notificationTimeoutLow = timeout + saveSettings() + } + + function setNotificationTimeoutNormal(timeout) { + notificationTimeoutNormal = timeout + saveSettings() + } + + function setNotificationTimeoutCritical(timeout) { + notificationTimeoutCritical = timeout + saveSettings() + } + + function setTopBarSpacing(spacing) { + topBarSpacing = spacing + saveSettings() + } + + function setTopBarBottomGap(gap) { + topBarBottomGap = gap + saveSettings() + } + + function setTopBarInnerPadding(padding) { + topBarInnerPadding = padding + saveSettings() + } + + function setTopBarSquareCorners(enabled) { + topBarSquareCorners = enabled + saveSettings() + } + + function setTopBarNoBackground(enabled) { + topBarNoBackground = enabled + saveSettings() + } + + function setLockScreenShowPowerActions(enabled) { + lockScreenShowPowerActions = enabled + saveSettings() + } + + function setHideBrightnessSlider(enabled) { + hideBrightnessSlider = enabled + saveSettings() + } + + function setScreenPreferences(prefs) { + screenPreferences = prefs + saveSettings() + } + + function getFilteredScreens(componentId) { + var prefs = screenPreferences && screenPreferences[componentId] || ["all"] + if (prefs.includes("all")) { + return Quickshell.screens + } + return Quickshell.screens.filter(screen => prefs.includes(screen.name)) + } + + function _shq(s) { + return "'" + String(s).replace(/'/g, "'\\''") + "'" + } + + Component.onCompleted: { + loadSettings() + fontCheckTimer.start() + initializeListModels() + } + + ListModel { + id: leftWidgetsModel + } + + ListModel { + id: centerWidgetsModel + } + + ListModel { + id: rightWidgetsModel + } + + Timer { + id: fontCheckTimer + + interval: 3000 + repeat: false + onTriggered: { + var availableFonts = Qt.fontFamilies() + var missingFonts = [] + if (fontFamily === defaultFontFamily && !availableFonts.includes(defaultFontFamily)) + missingFonts.push(defaultFontFamily) + + if (monoFontFamily === defaultMonoFontFamily && !availableFonts.includes(defaultMonoFontFamily)) + missingFonts.push(defaultMonoFontFamily) + + if (missingFonts.length > 0) { + var message = "Missing fonts: " + missingFonts.join(", ") + ". Using system defaults." + ToastService.showWarning(message) + } + } + } + + property bool hasTriedDefaultSettings: false + + FileView { + id: settingsFile + + path: StandardPaths.writableLocation(StandardPaths.ConfigLocation) + "/DankMaterialShell/settings.json" + blockLoading: true + blockWrites: true + watchChanges: true + onLoaded: { + parseSettings(settingsFile.text()) + hasTriedDefaultSettings = false + } + onLoadFailed: error => { + if (!hasTriedDefaultSettings) { + hasTriedDefaultSettings = true + defaultSettingsCheckProcess.running = true + } else { + applyStoredTheme() + } + } + } + + Process { + id: systemDefaultDetectionProcess + + command: ["sh", "-c", "gsettings get org.gnome.desktop.interface icon-theme 2>/dev/null | sed \"s/'//g\" || echo ''"] + running: false + onExited: exitCode => { + if (exitCode === 0 && stdout && stdout.length > 0) + systemDefaultIconTheme = stdout.trim() + else + systemDefaultIconTheme = "" + iconThemeDetectionProcess.running = true + } + } + + Process { + id: iconThemeDetectionProcess + + command: ["sh", "-c", "find /usr/share/icons ~/.local/share/icons ~/.icons -maxdepth 1 -type d 2>/dev/null | sed 's|.*/||' | grep -v '^icons$' | sort -u"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + var detectedThemes = ["System Default"] + if (text && text.trim()) { + var themes = text.trim().split('\n') + for (var i = 0; i < themes.length; i++) { + var theme = themes[i].trim() + if (theme && theme !== "" && theme !== "default" && theme !== "hicolor" && theme !== "locolor") + detectedThemes.push(theme) + } + } + availableIconThemes = detectedThemes + } + } + } + + Process { + id: qtToolsDetectionProcess + + command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"] + running: false + + stdout: StdioCollector { + onStreamFinished: { + if (text && text.trim()) { + var lines = text.trim().split('\n') + for (var i = 0; i < lines.length; i++) { + var line = lines[i] + if (line.startsWith('qt5ct:')) + qt5ctAvailable = line.split(':')[1] === 'true' + else if (line.startsWith('qt6ct:')) + qt6ctAvailable = line.split(':')[1] === 'true' + else if (line.startsWith('gtk:')) + gtkAvailable = line.split(':')[1] === 'true' + } + } + } + } + } + + Process { + id: defaultSettingsCheckProcess + + command: ["sh", "-c", "CONFIG_DIR=\"" + _configDir + + "/DankMaterialShell\"; if [ -f \"$CONFIG_DIR/default-settings.json\" ] && [ ! -f \"$CONFIG_DIR/settings.json\" ]; then cp \"$CONFIG_DIR/default-settings.json\" \"$CONFIG_DIR/settings.json\" && echo 'copied'; else echo 'not_found'; fi"] + running: false + onExited: exitCode => { + if (exitCode === 0) { + console.log("Copied default-settings.json to settings.json") + settingsFile.reload() + } else { + // No default settings file found, just apply stored theme + applyStoredTheme() + } + } + } + + IpcHandler { + function reveal(): string { + root.setTopBarVisible(true) + return "BAR_SHOW_SUCCESS" + } + + function hide(): string { + root.setTopBarVisible(false) + return "BAR_HIDE_SUCCESS" + } + + function toggle(): string { + root.toggleTopBarVisible() + return topBarVisible ? "BAR_SHOW_SUCCESS" : "BAR_HIDE_SUCCESS" + } + + function status(): string { + return topBarVisible ? "visible" : "hidden" + } + + target: "bar" + } +} diff --git a/quickshell/.config/quickshell/Common/StockThemes.js b/quickshell/.config/quickshell/Common/StockThemes.js new file mode 100644 index 0000000..4bdb8e4 --- /dev/null +++ b/quickshell/.config/quickshell/Common/StockThemes.js @@ -0,0 +1,380 @@ +// Stock theme definitions for DankMaterialShell +// Separated from Theme.qml to keep that file clean + +const StockThemes = { + DARK: { + blue: { + name: "Blue", + primary: "#42a5f5", + primaryText: "#ffffff", + primaryContainer: "#1976d2", + secondary: "#8ab4f8", + surface: "#1a1c1e", + surfaceText: "#e3e8ef", + surfaceVariant: "#44464f", + surfaceVariantText: "#c4c7c5", + surfaceTint: "#8ab4f8", + background: "#1a1c1e", + backgroundText: "#e3e8ef", + outline: "#8e918f", + surfaceContainer: "#1e2023", + surfaceContainerHigh: "#292b2f" + }, + deepBlue: { + name: "Deep Blue", + primary: "#0061a4", + primaryText: "#ffffff", + primaryContainer: "#004881", + secondary: "#42a5f5", + surface: "#1a1c1e", + surfaceText: "#e3e8ef", + surfaceVariant: "#44464f", + surfaceVariantText: "#c4c7c5", + surfaceTint: "#8ab4f8", + background: "#1a1c1e", + backgroundText: "#e3e8ef", + outline: "#8e918f", + surfaceContainer: "#1e2023", + surfaceContainerHigh: "#292b2f" + }, + purple: { + name: "Purple", + primary: "#D0BCFF", + primaryText: "#381E72", + primaryContainer: "#4F378B", + secondary: "#CCC2DC", + surface: "#10121E", + surfaceText: "#E6E0E9", + surfaceVariant: "#49454F", + surfaceVariantText: "#CAC4D0", + surfaceTint: "#D0BCFF", + background: "#10121E", + backgroundText: "#E6E0E9", + outline: "#938F99", + surfaceContainer: "#1D1B20", + surfaceContainerHigh: "#2B2930" + }, + green: { + name: "Green", + primary: "#4caf50", + primaryText: "#ffffff", + primaryContainer: "#388e3c", + secondary: "#81c995", + surface: "#0f1411", + surfaceText: "#e1f5e3", + surfaceVariant: "#404943", + surfaceVariantText: "#c1cbc4", + surfaceTint: "#81c995", + background: "#0f1411", + backgroundText: "#e1f5e3", + outline: "#8b938c", + surfaceContainer: "#1a1f1b", + surfaceContainerHigh: "#252a26" + }, + orange: { + name: "Orange", + primary: "#ff6d00", + primaryText: "#ffffff", + primaryContainer: "#e65100", + secondary: "#ffb74d", + surface: "#1c1410", + surfaceText: "#f5f1ea", + surfaceVariant: "#4a453a", + surfaceVariantText: "#cbc5b8", + surfaceTint: "#ffb74d", + background: "#1c1410", + backgroundText: "#f5f1ea", + outline: "#958f84", + surfaceContainer: "#211e17", + surfaceContainerHigh: "#2c291f" + }, + red: { + name: "Red", + primary: "#f44336", + primaryText: "#ffffff", + primaryContainer: "#d32f2f", + secondary: "#f28b82", + surface: "#1c1011", + surfaceText: "#f5e8ea", + surfaceVariant: "#4a3f41", + surfaceVariantText: "#cbc2c4", + surfaceTint: "#f28b82", + background: "#1c1011", + backgroundText: "#f5e8ea", + outline: "#958b8d", + surfaceContainer: "#211b1c", + surfaceContainerHigh: "#2c2426" + }, + cyan: { + name: "Cyan", + primary: "#00bcd4", + primaryText: "#ffffff", + primaryContainer: "#0097a7", + secondary: "#4dd0e1", + surface: "#0f1617", + surfaceText: "#e8f4f5", + surfaceVariant: "#3f474a", + surfaceVariantText: "#c2c9cb", + surfaceTint: "#4dd0e1", + background: "#0f1617", + backgroundText: "#e8f4f5", + outline: "#8c9194", + surfaceContainer: "#1a1f20", + surfaceContainerHigh: "#252b2c" + }, + pink: { + name: "Pink", + primary: "#e91e63", + primaryText: "#ffffff", + primaryContainer: "#c2185b", + secondary: "#f8bbd9", + surface: "#1a1014", + surfaceText: "#f3e8ee", + surfaceVariant: "#483f45", + surfaceVariantText: "#c9c2c7", + surfaceTint: "#f8bbd9", + background: "#1a1014", + backgroundText: "#f3e8ee", + outline: "#938a90", + surfaceContainer: "#1f1b1e", + surfaceContainerHigh: "#2a2428" + }, + amber: { + name: "Amber", + primary: "#ffc107", + primaryText: "#000000", + primaryContainer: "#ff8f00", + secondary: "#ffd54f", + surface: "#1a1710", + surfaceText: "#f3f0e8", + surfaceVariant: "#49453a", + surfaceVariantText: "#cac5b8", + surfaceTint: "#ffd54f", + background: "#1a1710", + backgroundText: "#f3f0e8", + outline: "#949084", + surfaceContainer: "#1f1e17", + surfaceContainerHigh: "#2a281f" + }, + coral: { + name: "Coral", + primary: "#ffb4ab", + primaryText: "#5f1412", + primaryContainer: "#8c1d18", + secondary: "#f9dedc", + surface: "#1a1110", + surfaceText: "#f1e8e7", + surfaceVariant: "#4a4142", + surfaceVariantText: "#cdc2c1", + surfaceTint: "#ffb4ab", + background: "#1a1110", + backgroundText: "#f1e8e7", + outline: "#968b8a", + surfaceContainer: "#201a19", + surfaceContainerHigh: "#2b2221" + } + }, + LIGHT: { + blue: { + name: "Blue Light", + primary: "#1976d2", + primaryText: "#ffffff", + primaryContainer: "#e3f2fd", + secondary: "#42a5f5", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#1976d2", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + deepBlue: { + name: "Deep Blue Light", + primary: "#0061a4", + primaryText: "#ffffff", + primaryContainer: "#cfe5ff", + secondary: "#1976d2", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#0061a4", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + purple: { + name: "Purple Light", + primary: "#6750A4", + primaryText: "#ffffff", + primaryContainer: "#EADDFF", + secondary: "#625B71", + surface: "#FFFBFE", + surfaceText: "#1C1B1F", + surfaceVariant: "#E7E0EC", + surfaceVariantText: "#49454F", + surfaceTint: "#6750A4", + background: "#FFFBFE", + backgroundText: "#1C1B1F", + outline: "#79747E", + surfaceContainer: "#F3EDF7", + surfaceContainerHigh: "#ECE6F0" + }, + green: { + name: "Green Light", + primary: "#2e7d32", + primaryText: "#ffffff", + primaryContainer: "#e8f5e8", + secondary: "#4caf50", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#2e7d32", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + orange: { + name: "Orange Light", + primary: "#e65100", + primaryText: "#ffffff", + primaryContainer: "#ffecb3", + secondary: "#ff9800", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#e65100", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + red: { + name: "Red Light", + primary: "#d32f2f", + primaryText: "#ffffff", + primaryContainer: "#ffebee", + secondary: "#f44336", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#d32f2f", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + cyan: { + name: "Cyan Light", + primary: "#0097a7", + primaryText: "#ffffff", + primaryContainer: "#e0f2f1", + secondary: "#00bcd4", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#0097a7", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + pink: { + name: "Pink Light", + primary: "#c2185b", + primaryText: "#ffffff", + primaryContainer: "#fce4ec", + secondary: "#e91e63", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#c2185b", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + amber: { + name: "Amber Light", + primary: "#ff8f00", + primaryText: "#000000", + primaryContainer: "#fff8e1", + secondary: "#ffc107", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#ff8f00", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + }, + coral: { + name: "Coral Light", + primary: "#8c1d18", + primaryText: "#ffffff", + primaryContainer: "#ffdad6", + secondary: "#ff5449", + surface: "#fefefe", + surfaceText: "#1a1c1e", + surfaceVariant: "#e7e0ec", + surfaceVariantText: "#49454f", + surfaceTint: "#8c1d18", + background: "#fefefe", + backgroundText: "#1a1c1e", + outline: "#79747e", + surfaceContainer: "#f3f3f3", + surfaceContainerHigh: "#ececec" + } + } +} + +const ThemeNames = { + BLUE: "blue", + DEEP_BLUE: "deepBlue", + PURPLE: "purple", + GREEN: "green", + ORANGE: "orange", + RED: "red", + CYAN: "cyan", + PINK: "pink", + AMBER: "amber", + CORAL: "coral", + DYNAMIC: "dynamic" +} + +function isStockTheme(themeName) { + return Object.keys(StockThemes.DARK).includes(themeName) +} + +function getAvailableThemes(isLight = false) { + return isLight ? StockThemes.LIGHT : StockThemes.DARK +} + +function getThemeByName(themeName, isLight = false) { + const themes = getAvailableThemes(isLight) + return themes[themeName] || themes.blue +} + +function getAllThemeNames() { + return Object.keys(StockThemes.DARK) +} \ No newline at end of file diff --git a/quickshell/.config/quickshell/Common/Theme.qml b/quickshell/.config/quickshell/Common/Theme.qml new file mode 100644 index 0000000..f786890 --- /dev/null +++ b/quickshell/.config/quickshell/Common/Theme.qml @@ -0,0 +1,819 @@ +pragma Singleton + +pragma ComponentBehavior: Bound + +import QtCore +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.UPower +import qs.Services +import "StockThemes.js" as StockThemes + +Singleton { + id: root + + property string currentTheme: "blue" + property bool isLightMode: false + + readonly property string dynamic: "dynamic" + + readonly property string homeDir: { + const url = StandardPaths.writableLocation(StandardPaths.HomeLocation).toString() + return url.startsWith("file://") ? url.substring(7) : url + } + readonly property string configDir: { + const url = StandardPaths.writableLocation(StandardPaths.ConfigLocation).toString() + return url.startsWith("file://") ? url.substring(7) : url + } + readonly property string shellDir: Qt.resolvedUrl(".").toString().replace("file://", "").replace("/Common/", "") + readonly property string wallpaperPath: { + if (typeof SessionData === "undefined") return "" + + if (SessionData.perMonitorWallpaper) { + // Use first monitor's wallpaper for dynamic theming + var screens = Quickshell.screens + if (screens.length > 0) { + var firstMonitorWallpaper = SessionData.getMonitorWallpaper(screens[0].name) + return firstMonitorWallpaper || SessionData.wallpaperPath + } + } + + return SessionData.wallpaperPath + } + + property bool matugenAvailable: false + property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false + property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false + property var workerRunning: false + property var matugenColors: ({}) + property bool extractionRequested: false + property int colorUpdateTrigger: 0 + property var customThemeData: null + + readonly property string stateDir: { + const cacheHome = StandardPaths.writableLocation(StandardPaths.CacheLocation).toString() + const path = cacheHome.startsWith("file://") ? cacheHome.substring(7) : cacheHome + return path + "/dankshell" + } + + Component.onCompleted: { + Quickshell.execDetached(["mkdir", "-p", stateDir]) + matugenCheck.running = true + if (typeof SessionData !== "undefined") + SessionData.isLightModeChanged.connect(root.onLightModeChanged) + + if (typeof SettingsData !== "undefined" && SettingsData.currentThemeName) { + switchTheme(SettingsData.currentThemeName, false) + } + } + + function getMatugenColor(path, fallback) { + colorUpdateTrigger + const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" + let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode] + for (const part of path.split(".")) { + if (!cur || typeof cur !== "object" || !(part in cur)) + return fallback + cur = cur[part] + } + return cur || fallback + } + + readonly property var currentThemeData: { + if (currentTheme === "custom") { + return customThemeData || StockThemes.getThemeByName("blue", isLightMode) + } else if (currentTheme === dynamic) { + return { + "primary": getMatugenColor("primary", "#42a5f5"), + "primaryText": getMatugenColor("on_primary", "#ffffff"), + "primaryContainer": getMatugenColor("primary_container", "#1976d2"), + "secondary": getMatugenColor("secondary", "#8ab4f8"), + "surface": getMatugenColor("surface", "#1a1c1e"), + "surfaceText": getMatugenColor("on_background", "#e3e8ef"), + "surfaceVariant": getMatugenColor("surface_variant", "#44464f"), + "surfaceVariantText": getMatugenColor("on_surface_variant", "#c4c7c5"), + "surfaceTint": getMatugenColor("surface_tint", "#8ab4f8"), + "background": getMatugenColor("background", "#1a1c1e"), + "backgroundText": getMatugenColor("on_background", "#e3e8ef"), + "outline": getMatugenColor("outline", "#8e918f"), + "surfaceContainer": getMatugenColor("surface_container", "#1e2023"), + "surfaceContainerHigh": getMatugenColor("surface_container_high", "#292b2f"), + "error": "#F2B8B5", + "warning": "#FF9800", + "info": "#2196F3", + "success": "#4CAF50" + } + } else { + return StockThemes.getThemeByName(currentTheme, isLightMode) + } + } + + property color primary: currentThemeData.primary + property color primaryText: currentThemeData.primaryText + property color primaryContainer: currentThemeData.primaryContainer + property color secondary: currentThemeData.secondary + property color surface: currentThemeData.surface + property color surfaceText: currentThemeData.surfaceText + property color surfaceVariant: currentThemeData.surfaceVariant + property color surfaceVariantText: currentThemeData.surfaceVariantText + property color surfaceTint: currentThemeData.surfaceTint + property color background: currentThemeData.background + property color backgroundText: currentThemeData.backgroundText + property color outline: currentThemeData.outline + property color surfaceContainer: currentThemeData.surfaceContainer + property color surfaceContainerHigh: currentThemeData.surfaceContainerHigh + + property color error: currentThemeData.error || "#F2B8B5" + property color warning: currentThemeData.warning || "#FF9800" + property color info: currentThemeData.info || "#2196F3" + property color tempWarning: "#ff9933" + property color tempDanger: "#ff5555" + property color success: currentThemeData.success || "#4CAF50" + + property color primaryHover: Qt.rgba(primary.r, primary.g, primary.b, 0.12) + property color primaryHoverLight: Qt.rgba(primary.r, primary.g, primary.b, 0.08) + property color primaryPressed: Qt.rgba(primary.r, primary.g, primary.b, 0.16) + property color primarySelected: Qt.rgba(primary.r, primary.g, primary.b, 0.3) + property color primaryBackground: Qt.rgba(primary.r, primary.g, primary.b, 0.04) + + property color secondaryHover: Qt.rgba(secondary.r, secondary.g, secondary.b, 0.08) + + property color surfaceHover: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.08) + property color surfacePressed: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.12) + property color surfaceSelected: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.15) + property color surfaceLight: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.1) + property color surfaceVariantAlpha: Qt.rgba(surfaceVariant.r, surfaceVariant.g, surfaceVariant.b, 0.2) + property color surfaceTextHover: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.08) + property color surfaceTextAlpha: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.3) + property color surfaceTextLight: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.06) + property color surfaceTextMedium: Qt.rgba(surfaceText.r, surfaceText.g, surfaceText.b, 0.7) + + property color outlineButton: Qt.rgba(outline.r, outline.g, outline.b, 0.5) + property color outlineLight: Qt.rgba(outline.r, outline.g, outline.b, 0.05) + property color outlineMedium: Qt.rgba(outline.r, outline.g, outline.b, 0.08) + property color outlineStrong: Qt.rgba(outline.r, outline.g, outline.b, 0.12) + + property color errorHover: Qt.rgba(error.r, error.g, error.b, 0.12) + + property color shadowMedium: Qt.rgba(0, 0, 0, 0.08) + property color shadowStrong: Qt.rgba(0, 0, 0, 0.3) + + property int shorterDuration: 100 + property int shortDuration: 150 + property int mediumDuration: 300 + property int longDuration: 500 + property int extraLongDuration: 1000 + property int standardEasing: Easing.OutCubic + property int emphasizedEasing: Easing.OutQuart + + property real cornerRadius: typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12 + property real spacingXS: 4 + property real spacingS: 8 + property real spacingM: 12 + property real spacingL: 16 + property real spacingXL: 24 + property real fontSizeSmall: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 12 + property real fontSizeMedium: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 14 + property real fontSizeLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 16 + property real fontSizeXLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 20 + property real barHeight: 48 + property real iconSize: 24 + property real iconSizeSmall: 16 + property real iconSizeLarge: 32 + + property real panelTransparency: 0.85 + property real widgetTransparency: typeof SettingsData !== "undefined" && SettingsData.topBarWidgetTransparency !== undefined ? SettingsData.topBarWidgetTransparency : 0.85 + property real popupTransparency: typeof SettingsData !== "undefined" && SettingsData.popupTransparency !== undefined ? SettingsData.popupTransparency : 0.92 + + function switchTheme(themeName, savePrefs = true) { + if (themeName === dynamic) { + currentTheme = dynamic + extractColors() + } else if (themeName === "custom") { + currentTheme = "custom" + if (typeof SettingsData !== "undefined" && SettingsData.customThemeFile) { + loadCustomThemeFromFile(SettingsData.customThemeFile) + } + } else { + currentTheme = themeName + } + if (savePrefs && typeof SettingsData !== "undefined") + SettingsData.setTheme(currentTheme) + + generateSystemThemesFromCurrentTheme() + } + + function setLightMode(light, savePrefs = true) { + isLightMode = light + if (savePrefs && typeof SessionData !== "undefined") + SessionData.setLightMode(isLightMode) + PortalService.setLightMode(isLightMode) + generateSystemThemesFromCurrentTheme() + } + + function toggleLightMode(savePrefs = true) { + setLightMode(!isLightMode, savePrefs) + } + + function forceGenerateSystemThemes() { + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.showWarning("matugen not available - cannot generate system themes") + } + return + } + generateSystemThemesFromCurrentTheme() + } + + function getAvailableThemes() { + return StockThemes.getAllThemeNames() + } + + function getThemeDisplayName(themeName) { + const themeData = StockThemes.getThemeByName(themeName, isLightMode) + return themeData.name + } + + function getThemeColors(themeName) { + if (themeName === "custom" && customThemeData) { + return customThemeData + } + return StockThemes.getThemeByName(themeName, isLightMode) + } + + function loadCustomTheme(themeData) { + if (themeData.dark || themeData.light) { + const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark" + const selectedTheme = themeData[colorMode] || themeData.dark || themeData.light + customThemeData = selectedTheme + } else { + customThemeData = themeData + } + + generateSystemThemesFromCurrentTheme() + } + + function loadCustomThemeFromFile(filePath) { + customThemeFileView.path = filePath + } + + property alias availableThemeNames: root._availableThemeNames + readonly property var _availableThemeNames: StockThemes.getAllThemeNames() + property string currentThemeName: currentTheme + + function popupBackground() { + return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, popupTransparency) + } + + function contentBackground() { + return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, popupTransparency) + } + + function panelBackground() { + return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, panelTransparency) + } + + function widgetBackground() { + return Qt.rgba(surfaceContainer.r, surfaceContainer.g, surfaceContainer.b, widgetTransparency) + } + + function getPopupBackgroundAlpha() { + return popupTransparency + } + + function getContentBackgroundAlpha() { + return popupTransparency + } + + function isColorDark(c) { + return (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) < 0.5 + } + + function getBatteryIcon(level, isCharging, batteryAvailable) { + if (!batteryAvailable) + return _getBatteryPowerProfileIcon() + + if (isCharging) { + if (level >= 90) + return "battery_charging_full" + if (level >= 80) + return "battery_charging_90" + if (level >= 60) + return "battery_charging_80" + if (level >= 50) + return "battery_charging_60" + if (level >= 30) + return "battery_charging_50" + if (level >= 20) + return "battery_charging_30" + return "battery_charging_20" + } else { + if (level >= 95) + return "battery_full" + if (level >= 85) + return "battery_6_bar" + if (level >= 70) + return "battery_5_bar" + if (level >= 55) + return "battery_4_bar" + if (level >= 40) + return "battery_3_bar" + if (level >= 25) + return "battery_2_bar" + if (level >= 10) + return "battery_1_bar" + return "battery_alert" + } + } + + function _getBatteryPowerProfileIcon() { + if (typeof PowerProfiles === "undefined") + return "balance" + + switch (PowerProfiles.profile) { + case PowerProfile.PowerSaver: + return "energy_savings_leaf" + case PowerProfile.Performance: + return "rocket_launch" + default: + return "balance" + } + } + + function getPowerProfileIcon(profile) { + switch (profile) { + case PowerProfile.PowerSaver: + return "battery_saver" + case PowerProfile.Balanced: + return "battery_std" + case PowerProfile.Performance: + return "flash_on" + default: + return "settings" + } + } + + function getPowerProfileLabel(profile) { + switch (profile) { + case PowerProfile.PowerSaver: + return "Power Saver" + case PowerProfile.Balanced: + return "Balanced" + case PowerProfile.Performance: + return "Performance" + default: + return profile.charAt(0).toUpperCase() + profile.slice(1) + } + } + + function getPowerProfileDescription(profile) { + switch (profile) { + case PowerProfile.PowerSaver: + return "Extend battery life" + case PowerProfile.Balanced: + return "Balance power and performance" + case PowerProfile.Performance: + return "Prioritize performance" + default: + return "Custom power profile" + } + } + + function extractColors() { + extractionRequested = true + if (matugenAvailable) + fileChecker.running = true + else + matugenCheck.running = true + } + + function onLightModeChanged() { + if (matugenColors && Object.keys(matugenColors).length > 0) { + colorUpdateTrigger++ + } + + if (currentTheme === "custom" && customThemeFileView.path) { + customThemeFileView.reload() + } + + generateSystemThemesFromCurrentTheme() + } + + function setDesiredTheme(kind, value, isLight, iconTheme) { + if (!matugenAvailable) { + console.warn("matugen not available - cannot set system theme") + return + } + + const desired = { + "kind": kind, + "value": value, + "mode": isLight ? "light" : "dark", + "iconTheme": iconTheme || "System Default" + } + + const json = JSON.stringify(desired) + const desiredPath = stateDir + "/matugen.desired.json" + + Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]) + workerRunning = true + systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, "--run"] + systemThemeGenerator.running = true + } + + function generateSystemThemesFromCurrentTheme() { + if (!matugenAvailable) + return + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) + const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default" + + if (currentTheme === dynamic) { + if (!wallpaperPath) { + return + } + if (wallpaperPath.startsWith("#")) { + setDesiredTheme("hex", wallpaperPath, isLight, iconTheme) + } else { + setDesiredTheme("image", wallpaperPath, isLight, iconTheme) + } + } else { + let primaryColor + if (currentTheme === "custom") { + if (!customThemeData || !customThemeData.primary) { + console.warn("Custom theme data not available for system theme generation") + return + } + primaryColor = customThemeData.primary + } else { + primaryColor = currentThemeData.primary + } + + if (!primaryColor) { + console.warn("No primary color available for theme:", currentTheme) + return + } + setDesiredTheme("hex", primaryColor, isLight, iconTheme) + } + } + + function applyGtkColors() { + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.showError("matugen not available - cannot apply GTK colors") + } + return + } + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "true" : "false" + gtkApplier.command = [shellDir + "/scripts/gtk.sh", configDir, isLight, shellDir] + gtkApplier.running = true + } + + function applyQtColors() { + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.showError("matugen not available - cannot apply Qt colors") + } + return + } + + qtApplier.command = [shellDir + "/scripts/qt.sh", configDir] + qtApplier.running = true + } + + function extractJsonFromText(text) { + if (!text) + return null + + const start = text.search(/[{\[]/) + if (start === -1) + return null + + const open = text[start] + const pairs = { + "{": '}', + "[": ']' + } + const close = pairs[open] + if (!close) + return null + + let inString = false + let escape = false + const stack = [open] + + for (var i = start + 1; i < text.length; i++) { + const ch = text[i] + + if (inString) { + if (escape) { + escape = false + } else if (ch === '\\') { + escape = true + } else if (ch === '"') { + inString = false + } + continue + } + + if (ch === '"') { + inString = true + continue + } + if (ch === '{' || ch === '[') { + stack.push(ch) + continue + } + if (ch === '}' || ch === ']') { + const last = stack.pop() + if (!last || pairs[last] !== ch) { + return null + } + if (stack.length === 0) { + return text.slice(start, i + 1) + } + } + } + return null + } + + Process { + id: matugenCheck + command: ["which", "matugen"] + onExited: code => { + matugenAvailable = (code === 0) + if (!matugenAvailable) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "matugen_missing" + ToastService.showWarning("matugen not found - dynamic theming disabled") + } + return + } + if (extractionRequested) { + fileChecker.running = true + } + + const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode) + const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default" + + if (currentTheme === dynamic) { + if (wallpaperPath) { + Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"]) + if (wallpaperPath.startsWith("#")) { + setDesiredTheme("hex", wallpaperPath, isLight, iconTheme) + } else { + setDesiredTheme("image", wallpaperPath, isLight, iconTheme) + } + } + } else { + let primaryColor + if (currentTheme === "custom") { + if (customThemeData && customThemeData.primary) { + primaryColor = customThemeData.primary + } + } else { + primaryColor = currentThemeData.primary + } + + if (primaryColor) { + Quickshell.execDetached(["rm", "-f", stateDir + "/matugen.key"]) + setDesiredTheme("hex", primaryColor, isLight, iconTheme) + } + } + } + } + + Process { + id: fileChecker + command: ["test", "-r", wallpaperPath] + onExited: code => { + if (code === 0) { + matugenProcess.running = true + } else if (wallpaperPath.startsWith("#")) { + colorMatugenProcess.running = true + } + } + } + + Process { + id: matugenProcess + command: ["matugen", "image", wallpaperPath, "--json", "hex"] + + stdout: StdioCollector { + id: matugenCollector + onStreamFinished: { + if (!matugenCollector.text) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Wallpaper Processing Failed: Empty JSON extracted from matugen output.") + } + return + } + const extractedJson = extractJsonFromText(matugenCollector.text) + if (!extractedJson) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Wallpaper Processing Failed: Invalid JSON extracted from matugen output.") + } + console.log("Raw matugen output:", matugenCollector.text) + return + } + try { + root.matugenColors = JSON.parse(extractedJson) + root.colorUpdateTrigger++ + if (typeof ToastService !== "undefined") { + ToastService.clearWallpaperError() + } + } catch (e) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Wallpaper processing failed (JSON parse error after extraction)") + } + } + } + } + + onExited: code => { + if (code !== 0) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Matugen command failed with exit code " + code) + } + } + } + } + + Process { + id: colorMatugenProcess + command: ["matugen", "color", "hex", wallpaperPath, "--json", "hex"] + + stdout: StdioCollector { + id: colorMatugenCollector + onStreamFinished: { + if (!colorMatugenCollector.text) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Color Processing Failed: Empty JSON extracted from matugen output.") + } + return + } + const extractedJson = extractJsonFromText(colorMatugenCollector.text) + if (!extractedJson) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Color Processing Failed: Invalid JSON extracted from matugen output.") + } + console.log("Raw matugen output:", colorMatugenCollector.text) + return + } + try { + root.matugenColors = JSON.parse(extractedJson) + root.colorUpdateTrigger++ + if (typeof ToastService !== "undefined") { + ToastService.clearWallpaperError() + } + } catch (e) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Color processing failed (JSON parse error after extraction)") + } + } + } + } + + onExited: code => { + if (code !== 0) { + if (typeof ToastService !== "undefined") { + ToastService.wallpaperErrorStatus = "error" + ToastService.showError("Matugen color command failed with exit code " + code) + } + } + } + } + + Process { + id: ensureStateDir + } + + Process { + id: systemThemeGenerator + running: false + + onExited: exitCode => { + workerRunning = false + + if (exitCode === 2) { + // Exit code 2 means wallpaper/color not found - this is expected on first run + console.log("Theme worker: wallpaper/color not found, skipping theme generation") + } else if (exitCode !== 0) { + if (typeof ToastService !== "undefined") { + ToastService.showError("Theme worker failed (" + exitCode + ")") + } + console.warn("Theme worker failed with exit code:", exitCode) + } + } + } + + Process { + id: gtkApplier + running: false + + stdout: StdioCollector { + id: gtkStdout + } + + stderr: StdioCollector { + id: gtkStderr + } + + onExited: exitCode => { + if (exitCode === 0) { + if (typeof ToastService !== "undefined") { + ToastService.showInfo("GTK colors applied successfully") + } + } else { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to apply GTK colors: " + gtkStderr.text) + } + } + } + } + + Process { + id: qtApplier + running: false + + stdout: StdioCollector { + id: qtStdout + } + + stderr: StdioCollector { + id: qtStderr + } + + onExited: exitCode => { + if (exitCode === 0) { + if (typeof ToastService !== "undefined") { + ToastService.showInfo("Qt colors applied successfully") + } + } else { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to apply Qt colors: " + qtStderr.text) + } + } + } + } + + FileView { + id: customThemeFileView + watchChanges: currentTheme === "custom" + + function parseAndLoadTheme() { + try { + var themeData = JSON.parse(customThemeFileView.text()) + loadCustomTheme(themeData) + } catch (e) { + ToastService.showError("Invalid JSON format: " + e.message) + } + } + + onLoaded: { + parseAndLoadTheme() + } + + onFileChanged: { + customThemeFileView.reload() + } + + onLoadFailed: function (error) { + if (typeof ToastService !== "undefined") { + ToastService.showError("Failed to read theme file: " + error) + } + } + } + + IpcHandler { + target: "theme" + + function toggle(): string { + root.toggleLightMode() + return root.isLightMode ? "light" : "dark" + } + + function light(): string { + root.setLightMode(true) + return "light" + } + + function dark(): string { + root.setLightMode(false) + return "dark" + } + + function getMode(): string { + return root.isLightMode ? "light" : "dark" + } + } +} diff --git a/quickshell/.config/quickshell/Common/fzf.js b/quickshell/.config/quickshell/Common/fzf.js new file mode 100644 index 0000000..43bf341 --- /dev/null +++ b/quickshell/.config/quickshell/Common/fzf.js @@ -0,0 +1,1307 @@ +.pragma library + +/* +https://github.com/ajitid/fzf-for-js + +BSD 3-Clause License + +Copyright (c) 2021, Ajit +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +const normalized = { + 216: "O", + 223: "s", + 248: "o", + 273: "d", + 295: "h", + 305: "i", + 320: "l", + 322: "l", + 359: "t", + 383: "s", + 384: "b", + 385: "B", + 387: "b", + 390: "O", + 392: "c", + 393: "D", + 394: "D", + 396: "d", + 398: "E", + 400: "E", + 402: "f", + 403: "G", + 407: "I", + 409: "k", + 410: "l", + 412: "M", + 413: "N", + 414: "n", + 415: "O", + 421: "p", + 427: "t", + 429: "t", + 430: "T", + 434: "V", + 436: "y", + 438: "z", + 477: "e", + 485: "g", + 544: "N", + 545: "d", + 549: "z", + 564: "l", + 565: "n", + 566: "t", + 567: "j", + 570: "A", + 571: "C", + 572: "c", + 573: "L", + 574: "T", + 575: "s", + 576: "z", + 579: "B", + 580: "U", + 581: "V", + 582: "E", + 583: "e", + 584: "J", + 585: "j", + 586: "Q", + 587: "q", + 588: "R", + 589: "r", + 590: "Y", + 591: "y", + 592: "a", + 593: "a", + 595: "b", + 596: "o", + 597: "c", + 598: "d", + 599: "d", + 600: "e", + 603: "e", + 604: "e", + 605: "e", + 606: "e", + 607: "j", + 608: "g", + 609: "g", + 610: "G", + 613: "h", + 614: "h", + 616: "i", + 618: "I", + 619: "l", + 620: "l", + 621: "l", + 623: "m", + 624: "m", + 625: "m", + 626: "n", + 627: "n", + 628: "N", + 629: "o", + 633: "r", + 634: "r", + 635: "r", + 636: "r", + 637: "r", + 638: "r", + 639: "r", + 640: "R", + 641: "R", + 642: "s", + 647: "t", + 648: "t", + 649: "u", + 651: "v", + 652: "v", + 653: "w", + 654: "y", + 655: "Y", + 656: "z", + 657: "z", + 663: "c", + 665: "B", + 666: "e", + 667: "G", + 668: "H", + 669: "j", + 670: "k", + 671: "L", + 672: "q", + 686: "h", + 867: "a", + 868: "e", + 869: "i", + 870: "o", + 871: "u", + 872: "c", + 873: "d", + 874: "h", + 875: "m", + 876: "r", + 877: "t", + 878: "v", + 879: "x", + 7424: "A", + 7427: "B", + 7428: "C", + 7429: "D", + 7431: "E", + 7432: "e", + 7433: "i", + 7434: "J", + 7435: "K", + 7436: "L", + 7437: "M", + 7438: "N", + 7439: "O", + 7440: "O", + 7441: "o", + 7442: "o", + 7443: "o", + 7446: "o", + 7447: "o", + 7448: "P", + 7449: "R", + 7450: "R", + 7451: "T", + 7452: "U", + 7453: "u", + 7454: "u", + 7455: "m", + 7456: "V", + 7457: "W", + 7458: "Z", + 7522: "i", + 7523: "r", + 7524: "u", + 7525: "v", + 7834: "a", + 7835: "s", + 8305: "i", + 8341: "h", + 8342: "k", + 8343: "l", + 8344: "m", + 8345: "n", + 8346: "p", + 8347: "s", + 8348: "t", + 8580: "c" +}; +for (let i = "\u0300".codePointAt(0); i <= "\u036F".codePointAt(0); ++i) { + const diacritic = String.fromCodePoint(i); + for (const asciiChar of "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") { + const withDiacritic = (asciiChar + diacritic).normalize(); + const withDiacriticCodePoint = withDiacritic.codePointAt(0); + if (withDiacriticCodePoint > 126) { + normalized[withDiacriticCodePoint] = asciiChar; + } + } +} +const ranges = { + a: [7844, 7863], + e: [7870, 7879], + o: [7888, 7907], + u: [7912, 7921] +}; +for (const lowerChar of Object.keys(ranges)) { + const upperChar = lowerChar.toUpperCase(); + for (let i = ranges[lowerChar][0]; i <= ranges[lowerChar][1]; ++i) { + normalized[i] = i % 2 === 0 ? upperChar : lowerChar; + } +} +function normalizeRune(rune) { + if (rune < 192 || rune > 8580) { + return rune; + } + const normalizedChar = normalized[rune]; + if (normalizedChar !== void 0) + return normalizedChar.codePointAt(0); + return rune; +} +function toShort(number) { + return number; +} +function toInt(number) { + return number; +} +function maxInt16(num1, num2) { + return num1 > num2 ? num1 : num2; +} +const strToRunes = (str) => str.split("").map((s) => s.codePointAt(0)); +const runesToStr = (runes) => runes.map((r) => String.fromCodePoint(r)).join(""); +const whitespaceRunes = new Set( + " \f\n\r \v\xA0\u1680\u2028\u2029\u202F\u205F\u3000\uFEFF".split("").map((v) => v.codePointAt(0)) +); +for (let codePoint = "\u2000".codePointAt(0); codePoint <= "\u200A".codePointAt(0); codePoint++) { + whitespaceRunes.add(codePoint); +} +const isWhitespace = (rune) => whitespaceRunes.has(rune); +const whitespacesAtStart = (runes) => { + let whitespaces = 0; + for (const rune of runes) { + if (isWhitespace(rune)) + whitespaces++; + else + break; + } + return whitespaces; +}; +const whitespacesAtEnd = (runes) => { + let whitespaces = 0; + for (let i = runes.length - 1; i >= 0; i--) { + if (isWhitespace(runes[i])) + whitespaces++; + else + break; + } + return whitespaces; +}; +const MAX_ASCII = "\x7F".codePointAt(0); +const CAPITAL_A_RUNE = "A".codePointAt(0); +const CAPITAL_Z_RUNE = "Z".codePointAt(0); +const SMALL_A_RUNE = "a".codePointAt(0); +const SMALL_Z_RUNE = "z".codePointAt(0); +const NUMERAL_ZERO_RUNE = "0".codePointAt(0); +const NUMERAL_NINE_RUNE = "9".codePointAt(0); +function indexAt(index, max, forward) { + if (forward) { + return index; + } + return max - index - 1; +} +const SCORE_MATCH = 16, SCORE_GAP_START = -3, SCORE_GAP_EXTENTION = -1, BONUS_BOUNDARY = SCORE_MATCH / 2, BONUS_NON_WORD = SCORE_MATCH / 2, BONUS_CAMEL_123 = BONUS_BOUNDARY + SCORE_GAP_EXTENTION, BONUS_CONSECUTIVE = -(SCORE_GAP_START + SCORE_GAP_EXTENTION), BONUS_FIRST_CHAR_MULTIPLIER = 2; +function createPosSet(withPos) { + if (withPos) { + return /* @__PURE__ */ new Set(); + } + return null; +} +function alloc16(offset, slab2, size) { + if (slab2 !== null && slab2.i16.length > offset + size) { + const subarray = slab2.i16.subarray(offset, offset + size); + return [offset + size, subarray]; + } + return [offset, new Int16Array(size)]; +} +function alloc32(offset, slab2, size) { + if (slab2 !== null && slab2.i32.length > offset + size) { + const subarray = slab2.i32.subarray(offset, offset + size); + return [offset + size, subarray]; + } + return [offset, new Int32Array(size)]; +} +function charClassOfAscii(rune) { + if (rune >= SMALL_A_RUNE && rune <= SMALL_Z_RUNE) { + return 1; + } else if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) { + return 2; + } else if (rune >= NUMERAL_ZERO_RUNE && rune <= NUMERAL_NINE_RUNE) { + return 4; + } else { + return 0; + } +} +function charClassOfNonAscii(rune) { + const char = String.fromCodePoint(rune); + if (char !== char.toUpperCase()) { + return 1; + } else if (char !== char.toLowerCase()) { + return 2; + } else if (char.match(/\p{Number}/gu) !== null) { + return 4; + } else if (char.match(/\p{Letter}/gu) !== null) { + return 3; + } + return 0; +} +function charClassOf(rune) { + if (rune <= MAX_ASCII) { + return charClassOfAscii(rune); + } + return charClassOfNonAscii(rune); +} +function bonusFor(prevClass, currClass) { + if (prevClass === 0 && currClass !== 0) { + return BONUS_BOUNDARY; + } else if (prevClass === 1 && currClass === 2 || prevClass !== 4 && currClass === 4) { + return BONUS_CAMEL_123; + } else if (currClass === 0) { + return BONUS_NON_WORD; + } + return 0; +} +function bonusAt(input, idx) { + if (idx === 0) { + return BONUS_BOUNDARY; + } + return bonusFor(charClassOf(input[idx - 1]), charClassOf(input[idx])); +} +function trySkip(input, caseSensitive, char, from) { + let rest = input.slice(from); + let idx = rest.indexOf(char); + if (idx === 0) { + return from; + } + if (!caseSensitive && char >= SMALL_A_RUNE && char <= SMALL_Z_RUNE) { + if (idx > 0) { + rest = rest.slice(0, idx); + } + const uidx = rest.indexOf(char - 32); + if (uidx >= 0) { + idx = uidx; + } + } + if (idx < 0) { + return -1; + } + return from + idx; +} +function isAscii(runes) { + for (const rune of runes) { + if (rune >= 128) { + return false; + } + } + return true; +} +function asciiFuzzyIndex(input, pattern, caseSensitive) { + if (!isAscii(input)) { + return 0; + } + if (!isAscii(pattern)) { + return -1; + } + let firstIdx = 0, idx = 0; + for (let pidx = 0; pidx < pattern.length; pidx++) { + idx = trySkip(input, caseSensitive, pattern[pidx], idx); + if (idx < 0) { + return -1; + } + if (pidx === 0 && idx > 0) { + firstIdx = idx - 1; + } + idx++; + } + return firstIdx; +} +const fuzzyMatchV2 = (caseSensitive, normalize, forward, input, pattern, withPos, slab2) => { + const M = pattern.length; + if (M === 0) { + return [{ start: 0, end: 0, score: 0 }, createPosSet(withPos)]; + } + const N = input.length; + if (slab2 !== null && N * M > slab2.i16.length) { + return fuzzyMatchV1(caseSensitive, normalize, forward, input, pattern, withPos); + } + const idx = asciiFuzzyIndex(input, pattern, caseSensitive); + if (idx < 0) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + let offset16 = 0, offset32 = 0, H0 = null, C0 = null, B = null, F = null; + [offset16, H0] = alloc16(offset16, slab2, N); + [offset16, C0] = alloc16(offset16, slab2, N); + [offset16, B] = alloc16(offset16, slab2, N); + [offset32, F] = alloc32(offset32, slab2, M); + const [, T] = alloc32(offset32, slab2, N); + for (let i = 0; i < T.length; i++) { + T[i] = input[i]; + } + let maxScore = toShort(0), maxScorePos = 0; + let pidx = 0, lastIdx = 0; + const pchar0 = pattern[0]; + let pchar = pattern[0], prevH0 = toShort(0), prevCharClass = 0, inGap = false; + let Tsub = T.subarray(idx); + let H0sub = H0.subarray(idx).subarray(0, Tsub.length), C0sub = C0.subarray(idx).subarray(0, Tsub.length), Bsub = B.subarray(idx).subarray(0, Tsub.length); + for (let [off, char] of Tsub.entries()) { + let charClass = null; + if (char <= MAX_ASCII) { + charClass = charClassOfAscii(char); + if (!caseSensitive && charClass === 2) { + char += 32; + } + } else { + charClass = charClassOfNonAscii(char); + if (!caseSensitive && charClass === 2) { + char = String.fromCodePoint(char).toLowerCase().codePointAt(0); + } + if (normalize) { + char = normalizeRune(char); + } + } + Tsub[off] = char; + const bonus = bonusFor(prevCharClass, charClass); + Bsub[off] = bonus; + prevCharClass = charClass; + if (char === pchar) { + if (pidx < M) { + F[pidx] = toInt(idx + off); + pidx++; + pchar = pattern[Math.min(pidx, M - 1)]; + } + lastIdx = idx + off; + } + if (char === pchar0) { + const score = SCORE_MATCH + bonus * BONUS_FIRST_CHAR_MULTIPLIER; + H0sub[off] = score; + C0sub[off] = 1; + if (M === 1 && (forward && score > maxScore || !forward && score >= maxScore)) { + maxScore = score; + maxScorePos = idx + off; + if (forward && bonus === BONUS_BOUNDARY) { + break; + } + } + inGap = false; + } else { + if (inGap) { + H0sub[off] = maxInt16(prevH0 + SCORE_GAP_EXTENTION, 0); + } else { + H0sub[off] = maxInt16(prevH0 + SCORE_GAP_START, 0); + } + C0sub[off] = 0; + inGap = true; + } + prevH0 = H0sub[off]; + } + if (pidx !== M) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + if (M === 1) { + const result = { + start: maxScorePos, + end: maxScorePos + 1, + score: maxScore + }; + if (!withPos) { + return [result, null]; + } + const pos2 = /* @__PURE__ */ new Set(); + pos2.add(maxScorePos); + return [result, pos2]; + } + const f0 = F[0]; + const width = lastIdx - f0 + 1; + let H = null; + [offset16, H] = alloc16(offset16, slab2, width * M); + { + const toCopy = H0.subarray(f0, lastIdx + 1); + for (const [i, v] of toCopy.entries()) { + H[i] = v; + } + } + let [, C] = alloc16(offset16, slab2, width * M); + { + const toCopy = C0.subarray(f0, lastIdx + 1); + for (const [i, v] of toCopy.entries()) { + C[i] = v; + } + } + const Fsub = F.subarray(1); + const Psub = pattern.slice(1).slice(0, Fsub.length); + for (const [off, f] of Fsub.entries()) { + let inGap2 = false; + const pchar2 = Psub[off], pidx2 = off + 1, row = pidx2 * width, Tsub2 = T.subarray(f, lastIdx + 1), Bsub2 = B.subarray(f).subarray(0, Tsub2.length), Csub = C.subarray(row + f - f0).subarray(0, Tsub2.length), Cdiag = C.subarray(row + f - f0 - 1 - width).subarray(0, Tsub2.length), Hsub = H.subarray(row + f - f0).subarray(0, Tsub2.length), Hdiag = H.subarray(row + f - f0 - 1 - width).subarray(0, Tsub2.length), Hleft = H.subarray(row + f - f0 - 1).subarray(0, Tsub2.length); + Hleft[0] = 0; + for (const [off2, char] of Tsub2.entries()) { + const col = off2 + f; + let s1 = 0, s2 = 0, consecutive = 0; + if (inGap2) { + s2 = Hleft[off2] + SCORE_GAP_EXTENTION; + } else { + s2 = Hleft[off2] + SCORE_GAP_START; + } + if (pchar2 === char) { + s1 = Hdiag[off2] + SCORE_MATCH; + let b = Bsub2[off2]; + consecutive = Cdiag[off2] + 1; + if (b === BONUS_BOUNDARY) { + consecutive = 1; + } else if (consecutive > 1) { + b = maxInt16(b, maxInt16(BONUS_CONSECUTIVE, B[col - consecutive + 1])); + } + if (s1 + b < s2) { + s1 += Bsub2[off2]; + consecutive = 0; + } else { + s1 += b; + } + } + Csub[off2] = consecutive; + inGap2 = s1 < s2; + const score = maxInt16(maxInt16(s1, s2), 0); + if (pidx2 === M - 1 && (forward && score > maxScore || !forward && score >= maxScore)) { + maxScore = score; + maxScorePos = col; + } + Hsub[off2] = score; + } + } + const pos = createPosSet(withPos); + let j = f0; + if (withPos && pos !== null) { + let i = M - 1; + j = maxScorePos; + let preferMatch = true; + while (true) { + const I = i * width, j0 = j - f0, s = H[I + j0]; + let s1 = 0, s2 = 0; + if (i > 0 && j >= F[i]) { + s1 = H[I - width + j0 - 1]; + } + if (j > F[i]) { + s2 = H[I + j0 - 1]; + } + if (s > s1 && (s > s2 || s === s2 && preferMatch)) { + pos.add(j); + if (i === 0) { + break; + } + i--; + } + preferMatch = C[I + j0] > 1 || I + width + j0 + 1 < C.length && C[I + width + j0 + 1] > 0; + j--; + } + } + return [{ start: j, end: maxScorePos + 1, score: maxScore }, pos]; +}; +function calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, withPos) { + let pidx = 0, score = 0, inGap = false, consecutive = 0, firstBonus = toShort(0); + const pos = createPosSet(withPos); + let prevCharClass = 0; + if (sidx > 0) { + prevCharClass = charClassOf(text[sidx - 1]); + } + for (let idx = sidx; idx < eidx; idx++) { + let rune = text[idx]; + const charClass = charClassOf(rune); + if (!caseSensitive) { + if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) { + rune += 32; + } else if (rune > MAX_ASCII) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + } + if (normalize) { + rune = normalizeRune(rune); + } + if (rune === pattern[pidx]) { + if (withPos && pos !== null) { + pos.add(idx); + } + score += SCORE_MATCH; + let bonus = bonusFor(prevCharClass, charClass); + if (consecutive === 0) { + firstBonus = bonus; + } else { + if (bonus === BONUS_BOUNDARY) { + firstBonus = bonus; + } + bonus = maxInt16(maxInt16(bonus, firstBonus), BONUS_CONSECUTIVE); + } + if (pidx === 0) { + score += bonus * BONUS_FIRST_CHAR_MULTIPLIER; + } else { + score += bonus; + } + inGap = false; + consecutive++; + pidx++; + } else { + if (inGap) { + score += SCORE_GAP_EXTENTION; + } else { + score += SCORE_GAP_START; + } + inGap = true; + consecutive = 0; + firstBonus = 0; + } + prevCharClass = charClass; + } + return [score, pos]; +} +function fuzzyMatchV1(caseSensitive, normalize, forward, text, pattern, withPos, slab2) { + if (pattern.length === 0) { + return [{ start: 0, end: 0, score: 0 }, null]; + } + if (asciiFuzzyIndex(text, pattern, caseSensitive) < 0) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + let pidx = 0, sidx = -1, eidx = -1; + const lenRunes = text.length; + const lenPattern = pattern.length; + for (let index = 0; index < lenRunes; index++) { + let rune = text[indexAt(index, lenRunes, forward)]; + if (!caseSensitive) { + if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) { + rune += 32; + } else if (rune > MAX_ASCII) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + } + if (normalize) { + rune = normalizeRune(rune); + } + const pchar = pattern[indexAt(pidx, lenPattern, forward)]; + if (rune === pchar) { + if (sidx < 0) { + sidx = index; + } + pidx++; + if (pidx === lenPattern) { + eidx = index + 1; + break; + } + } + } + if (sidx >= 0 && eidx >= 0) { + pidx--; + for (let index = eidx - 1; index >= sidx; index--) { + const tidx = indexAt(index, lenRunes, forward); + let rune = text[tidx]; + if (!caseSensitive) { + if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) { + rune += 32; + } else if (rune > MAX_ASCII) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + } + const pidx_ = indexAt(pidx, lenPattern, forward); + const pchar = pattern[pidx_]; + if (rune === pchar) { + pidx--; + if (pidx < 0) { + sidx = index; + break; + } + } + } + if (!forward) { + const sidxTemp = sidx; + sidx = lenRunes - eidx; + eidx = lenRunes - sidxTemp; + } + const [score, pos] = calculateScore( + caseSensitive, + normalize, + text, + pattern, + sidx, + eidx, + withPos + ); + return [{ start: sidx, end: eidx, score }, pos]; + } + return [{ start: -1, end: -1, score: 0 }, null]; +}; +const exactMatchNaive = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => { + if (pattern.length === 0) { + return [{ start: 0, end: 0, score: 0 }, null]; + } + const lenRunes = text.length; + const lenPattern = pattern.length; + if (lenRunes < lenPattern) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + if (asciiFuzzyIndex(text, pattern, caseSensitive) < 0) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + let pidx = 0; + let bestPos = -1, bonus = toShort(0), bestBonus = toShort(-1); + for (let index = 0; index < lenRunes; index++) { + const index_ = indexAt(index, lenRunes, forward); + let rune = text[index_]; + if (!caseSensitive) { + if (rune >= CAPITAL_A_RUNE && rune <= CAPITAL_Z_RUNE) { + rune += 32; + } else if (rune > MAX_ASCII) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + } + if (normalize) { + rune = normalizeRune(rune); + } + const pidx_ = indexAt(pidx, lenPattern, forward); + const pchar = pattern[pidx_]; + if (pchar === rune) { + if (pidx_ === 0) { + bonus = bonusAt(text, index_); + } + pidx++; + if (pidx === lenPattern) { + if (bonus > bestBonus) { + bestPos = index; + bestBonus = bonus; + } + if (bonus === BONUS_BOUNDARY) { + break; + } + index -= pidx - 1; + pidx = 0; + bonus = 0; + } + } else { + index -= pidx; + pidx = 0; + bonus = 0; + } + } + if (bestPos >= 0) { + let sidx = 0, eidx = 0; + if (forward) { + sidx = bestPos - lenPattern + 1; + eidx = bestPos + 1; + } else { + sidx = lenRunes - (bestPos + 1); + eidx = lenRunes - (bestPos - lenPattern + 1); + } + const [score] = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false); + return [{ start: sidx, end: eidx, score }, null]; + } + return [{ start: -1, end: -1, score: 0 }, null]; +}; +const prefixMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => { + if (pattern.length === 0) { + return [{ start: 0, end: 0, score: 0 }, null]; + } + let trimmedLen = 0; + if (!isWhitespace(pattern[0])) { + trimmedLen = whitespacesAtStart(text); + } + if (text.length - trimmedLen < pattern.length) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + for (const [index, r] of pattern.entries()) { + let rune = text[trimmedLen + index]; + if (!caseSensitive) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + if (normalize) { + rune = normalizeRune(rune); + } + if (rune !== r) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + } + const lenPattern = pattern.length; + const [score] = calculateScore( + caseSensitive, + normalize, + text, + pattern, + trimmedLen, + trimmedLen + lenPattern, + false + ); + return [{ start: trimmedLen, end: trimmedLen + lenPattern, score }, null]; +}; +const suffixMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => { + const lenRunes = text.length; + let trimmedLen = lenRunes; + if (pattern.length === 0 || !isWhitespace(pattern[pattern.length - 1])) { + trimmedLen -= whitespacesAtEnd(text); + } + if (pattern.length === 0) { + return [{ start: trimmedLen, end: trimmedLen, score: 0 }, null]; + } + const diff = trimmedLen - pattern.length; + if (diff < 0) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + for (const [index, r] of pattern.entries()) { + let rune = text[index + diff]; + if (!caseSensitive) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + if (normalize) { + rune = normalizeRune(rune); + } + if (rune !== r) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + } + const lenPattern = pattern.length; + const sidx = trimmedLen - lenPattern; + const eidx = trimmedLen; + const [score] = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false); + return [{ start: sidx, end: eidx, score }, null]; +}; +const equalMatch = (caseSensitive, normalize, forward, text, pattern, withPos, slab2) => { + const lenPattern = pattern.length; + if (lenPattern === 0) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + let trimmedLen = 0; + if (!isWhitespace(pattern[0])) { + trimmedLen = whitespacesAtStart(text); + } + let trimmedEndLen = 0; + if (!isWhitespace(pattern[lenPattern - 1])) { + trimmedEndLen = whitespacesAtEnd(text); + } + if (text.length - trimmedLen - trimmedEndLen != lenPattern) { + return [{ start: -1, end: -1, score: 0 }, null]; + } + let match = true; + if (normalize) { + const runes = text; + for (const [idx, pchar] of pattern.entries()) { + let rune = runes[trimmedLen + idx]; + if (!caseSensitive) { + rune = String.fromCodePoint(rune).toLowerCase().codePointAt(0); + } + if (normalizeRune(pchar) !== normalizeRune(rune)) { + match = false; + break; + } + } + } else { + let runesStr = runesToStr(text).substring(trimmedLen, text.length - trimmedEndLen); + if (!caseSensitive) { + runesStr = runesStr.toLowerCase(); + } + match = runesStr === runesToStr(pattern); + } + if (match) { + return [ + { + start: trimmedLen, + end: trimmedLen + lenPattern, + score: (SCORE_MATCH + BONUS_BOUNDARY) * lenPattern + (BONUS_FIRST_CHAR_MULTIPLIER - 1) * BONUS_BOUNDARY + }, + null + ]; + } + return [{ start: -1, end: -1, score: 0 }, null]; +}; +const SLAB_16_SIZE = 100 * 1024; +const SLAB_32_SIZE = 2048; +function makeSlab(size16, size32) { + return { + i16: new Int16Array(size16), + i32: new Int32Array(size32) + }; +} +const slab = makeSlab(SLAB_16_SIZE, SLAB_32_SIZE); +var TermType = /* @__PURE__ */ ((TermType2) => { + TermType2[TermType2["Fuzzy"] = 0] = "Fuzzy"; + TermType2[TermType2["Exact"] = 1] = "Exact"; + TermType2[TermType2["Prefix"] = 2] = "Prefix"; + TermType2[TermType2["Suffix"] = 3] = "Suffix"; + TermType2[TermType2["Equal"] = 4] = "Equal"; + return TermType2; +})(TermType || {}); +const termTypeMap = { + [0]: fuzzyMatchV2, + [1]: exactMatchNaive, + [2]: prefixMatch, + [3]: suffixMatch, + [4]: equalMatch +}; +function buildPatternForExtendedMatch(fuzzy, caseMode, normalize, str) { + let cacheable = true; + str = str.trimLeft(); + { + const trimmedAtRightStr = str.trimRight(); + if (trimmedAtRightStr.endsWith("\\") && str[trimmedAtRightStr.length] === " ") { + str = trimmedAtRightStr + " "; + } else { + str = trimmedAtRightStr; + } + } + let sortable = false; + let termSets = []; + termSets = parseTerms(fuzzy, caseMode, normalize, str); + Loop: + for (const termSet of termSets) { + for (const [idx, term] of termSet.entries()) { + if (!term.inv) { + sortable = true; + } + if (!cacheable || idx > 0 || term.inv || fuzzy && term.typ !== 0 || !fuzzy && term.typ !== 1) { + cacheable = false; + if (sortable) { + break Loop; + } + } + } + } + return { + str, + termSets, + sortable, + cacheable, + fuzzy + }; +} +function parseTerms(fuzzy, caseMode, normalize, str) { + str = str.replace(/\\ /g, " "); + const tokens = str.split(/ +/); + const sets = []; + let set = []; + let switchSet = false; + let afterBar = false; + for (const token of tokens) { + let typ = 0, inv = false, text = token.replace(/\t/g, " "); + const lowerText = text.toLowerCase(); + const caseSensitive = caseMode === "case-sensitive" || caseMode === "smart-case" && text !== lowerText; + const normalizeTerm = normalize && lowerText === runesToStr(strToRunes(lowerText).map(normalizeRune)); + if (!caseSensitive) { + text = lowerText; + } + if (!fuzzy) { + typ = 1; + } + if (set.length > 0 && !afterBar && text === "|") { + switchSet = false; + afterBar = true; + continue; + } + afterBar = false; + if (text.startsWith("!")) { + inv = true; + typ = 1; + text = text.substring(1); + } + if (text !== "$" && text.endsWith("$")) { + typ = 3; + text = text.substring(0, text.length - 1); + } + if (text.startsWith("'")) { + if (fuzzy && !inv) { + typ = 1; + } else { + typ = 0; + } + text = text.substring(1); + } else if (text.startsWith("^")) { + if (typ === 3) { + typ = 4; + } else { + typ = 2; + } + text = text.substring(1); + } + if (text.length > 0) { + if (switchSet) { + sets.push(set); + set = []; + } + let textRunes = strToRunes(text); + if (normalizeTerm) { + textRunes = textRunes.map(normalizeRune); + } + set.push({ + typ, + inv, + text: textRunes, + caseSensitive, + normalize: normalizeTerm + }); + switchSet = true; + } + } + if (set.length > 0) { + sets.push(set); + } + return sets; +} +const buildPatternForBasicMatch = (query, casing, normalize) => { + let caseSensitive = false; + switch (casing) { + case "smart-case": + if (query.toLowerCase() !== query) { + caseSensitive = true; + } + break; + case "case-sensitive": + caseSensitive = true; + break; + case "case-insensitive": + query = query.toLowerCase(); + caseSensitive = false; + break; + } + let queryRunes = strToRunes(query); + if (normalize) { + queryRunes = queryRunes.map(normalizeRune); + } + return { + queryRunes, + caseSensitive + }; +}; +function iter(algoFn, tokens, caseSensitive, normalize, forward, pattern, slab2) { + for (const part of tokens) { + const [res, pos] = algoFn(caseSensitive, normalize, forward, part.text, pattern, true, slab2); + if (res.start >= 0) { + const sidx = res.start + part.prefixLength; + const eidx = res.end + part.prefixLength; + if (pos !== null) { + const newPos = /* @__PURE__ */ new Set(); + pos.forEach((v) => newPos.add(part.prefixLength + v)); + return [[sidx, eidx], res.score, newPos]; + } + return [[sidx, eidx], res.score, pos]; + } + } + return [[-1, -1], 0, null]; +} +function computeExtendedMatch(text, pattern, fuzzyAlgo, forward) { + const input = [ + { + text, + prefixLength: 0 + } + ]; + const offsets = []; + let totalScore = 0; + const allPos = /* @__PURE__ */ new Set(); + for (const termSet of pattern.termSets) { + let offset = [0, 0]; + let currentScore = 0; + let matched = false; + for (const term of termSet) { + let algoFn = termTypeMap[term.typ]; + if (term.typ === TermType.Fuzzy) { + algoFn = fuzzyAlgo; + } + const [off, score, pos] = iter( + algoFn, + input, + term.caseSensitive, + term.normalize, + forward, + term.text, + slab + ); + const sidx = off[0]; + if (sidx >= 0) { + if (term.inv) { + continue; + } + offset = off; + currentScore = score; + matched = true; + if (pos !== null) { + pos.forEach((v) => allPos.add(v)); + } else { + for (let idx = off[0]; idx < off[1]; ++idx) { + allPos.add(idx); + } + } + break; + } else if (term.inv) { + offset = [0, 0]; + currentScore = 0; + matched = true; + continue; + } + } + if (matched) { + offsets.push(offset); + totalScore += currentScore; + } + } + return { offsets, totalScore, allPos }; +} +function getResultFromScoreMap(scoreMap, limit) { + const scoresInDesc = Object.keys(scoreMap).map((v) => parseInt(v, 10)).sort((a, b) => b - a); + let result = []; + for (const score of scoresInDesc) { + result = result.concat(scoreMap[score]); + if (result.length >= limit) { + break; + } + } + return result; +} +function getBasicMatchIter(scoreMap, queryRunes, caseSensitive) { + return (idx) => { + const itemRunes = this.runesList[idx]; + if (queryRunes.length > itemRunes.length) + return; + let [match, positions] = this.algoFn( + caseSensitive, + this.opts.normalize, + this.opts.forward, + itemRunes, + queryRunes, + true, + slab + ); + if (match.start === -1) + return; + if (this.opts.fuzzy === false) { + positions = /* @__PURE__ */ new Set(); + for (let position = match.start; position < match.end; ++position) { + positions.add(position); + } + } + const scoreKey = this.opts.sort ? match.score : 0; + if (scoreMap[scoreKey] === void 0) { + scoreMap[scoreKey] = []; + } + scoreMap[scoreKey].push(Object.assign({ + item: this.items[idx], + positions: positions != null ? positions : /* @__PURE__ */ new Set() + }, match)); + }; +} +function getExtendedMatchIter(scoreMap, pattern) { + return (idx) => { + const runes = this.runesList[idx]; + const match = computeExtendedMatch(runes, pattern, this.algoFn, this.opts.forward); + if (match.offsets.length !== pattern.termSets.length) + return; + let sidx = -1, eidx = -1; + if (match.allPos.size > 0) { + sidx = Math.min(...match.allPos); + eidx = Math.max(...match.allPos) + 1; + } + const scoreKey = this.opts.sort ? match.totalScore : 0; + if (scoreMap[scoreKey] === void 0) { + scoreMap[scoreKey] = []; + } + scoreMap[scoreKey].push({ + score: match.totalScore, + item: this.items[idx], + positions: match.allPos, + start: sidx, + end: eidx + }); + }; +} +function basicMatch(query) { + const { queryRunes, caseSensitive } = buildPatternForBasicMatch( + query, + this.opts.casing, + this.opts.normalize + ); + const scoreMap = {}; + const iter2 = getBasicMatchIter.bind(this)( + scoreMap, + queryRunes, + caseSensitive + ); + for (let i = 0, len = this.runesList.length; i < len; ++i) { + iter2(i); + } + return getResultFromScoreMap(scoreMap, this.opts.limit); +} +function extendedMatch(query) { + const pattern = buildPatternForExtendedMatch( + Boolean(this.opts.fuzzy), + this.opts.casing, + this.opts.normalize, + query + ); + const scoreMap = {}; + const iter2 = getExtendedMatchIter.bind(this)(scoreMap, pattern); + for (let i = 0, len = this.runesList.length; i < len; ++i) { + iter2(i); + } + return getResultFromScoreMap(scoreMap, this.opts.limit); +} +const defaultOpts = { + limit: Infinity, + selector: (v) => v, + casing: "smart-case", + normalize: true, + fuzzy: "v2", + tiebreakers: [], + sort: true, + forward: true, + match: basicMatch +}; +class Finder { + constructor(list, ...optionsTuple) { + this.opts = Object.assign(defaultOpts, optionsTuple[0]); + this.items = list; + this.runesList = list.map((item) => strToRunes(this.opts.selector(item).normalize())); + this.algoFn = exactMatchNaive; + switch (this.opts.fuzzy) { + case "v2": + this.algoFn = fuzzyMatchV2; + break; + case "v1": + this.algoFn = fuzzyMatchV1; + break; + } + } + find(query) { + if (query.length === 0 || this.items.length === 0) + return this.items.slice(0, this.opts.limit).map(createResultItemWithEmptyPos); + query = query.normalize(); + let result = this.opts.match.bind(this)(query); + return postProcessResultItems(result, this.opts); + } +} +function createResultItemWithEmptyPos(item) { + return ({ + item, + start: -1, + end: -1, + score: 0, + positions: /* @__PURE__ */ new Set() + }) +}; +function postProcessResultItems(result, opts) { + if (opts.sort) { + const { selector } = opts; + result.sort((a, b) => { + if (a.score === b.score) { + for (const tiebreaker of opts.tiebreakers) { + const diff = tiebreaker(a, b, selector); + if (diff !== 0) { + return diff; + } + } + } + return 0; + }); + } + if (Number.isFinite(opts.limit)) { + result.splice(opts.limit); + } + return result; +} +function byLengthAsc(a, b, selector) { + return selector(a.item).length - selector(b.item).length; +} +function byStartAsc(a, b) { + return a.start - b.start; +} \ No newline at end of file diff --git a/quickshell/.config/quickshell/Common/markdown2html.js b/quickshell/.config/quickshell/Common/markdown2html.js new file mode 100644 index 0000000..0a62b3f --- /dev/null +++ b/quickshell/.config/quickshell/Common/markdown2html.js @@ -0,0 +1,106 @@ +.pragma library +// This exists only beacause I haven't been able to get linkColor to work with MarkdownText +// May not be necessary if that's possible tbh. +function markdownToHtml(text) { + if (!text) return ""; + + // Store code blocks and inline code to protect them from further processing + const codeBlocks = []; + const inlineCode = []; + let blockIndex = 0; + let inlineIndex = 0; + + // First, extract and replace code blocks with placeholders + let html = text.replace(/```([\s\S]*?)```/g, (match, code) => { + // Trim leading and trailing blank lines only + const trimmedCode = code.replace(/^\n+|\n+$/g, ''); + // Escape HTML entities in code + const escapedCode = trimmedCode.replace(/&/g, '&') + .replace(//g, '>'); + codeBlocks.push(`
${escapedCode}
`); + return `\x00CODEBLOCK${blockIndex++}\x00`; + }); + + // Extract and replace inline code + html = html.replace(/`([^`]+)`/g, (match, code) => { + // Escape HTML entities in code + const escapedCode = code.replace(/&/g, '&') + .replace(//g, '>'); + inlineCode.push(`${escapedCode}`); + return `\x00INLINECODE${inlineIndex++}\x00`; + }); + + // Now process everything else + // Escape HTML entities (but not in code blocks) + html = html.replace(/&/g, '&') + .replace(//g, '>'); + + // Headers + html = html.replace(/^### (.*?)$/gm, '

$1

'); + html = html.replace(/^## (.*?)$/gm, '

$1

'); + html = html.replace(/^# (.*?)$/gm, '

$1

'); + + // Bold and italic (order matters!) + html = html.replace(/\*\*\*(.*?)\*\*\*/g, '$1'); + html = html.replace(/\*\*(.*?)\*\*/g, '$1'); + html = html.replace(/\*(.*?)\*/g, '$1'); + html = html.replace(/___(.*?)___/g, '$1'); + html = html.replace(/__(.*?)__/g, '$1'); + html = html.replace(/_(.*?)_/g, '$1'); + + // Links + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // Lists + html = html.replace(/^\* (.*?)$/gm, '
  • $1
  • '); + html = html.replace(/^- (.*?)$/gm, '
  • $1
  • '); + html = html.replace(/^\d+\. (.*?)$/gm, '
  • $1
  • '); + + // Wrap consecutive list items in ul/ol tags + html = html.replace(/(
  • [\s\S]*?<\/li>\s*)+/g, function(match) { + return '
      ' + match + '
    '; + }); + + // Detect plain URLs and wrap them in anchor tags (but not inside existing or markdown links) + html = html.replace(/(^|[^"'>])((https?|file):\/\/[^\s<]+)/g, '$1$2'); + + // Restore code blocks and inline code BEFORE line break processing + html = html.replace(/\x00CODEBLOCK(\d+)\x00/g, (match, index) => { + return codeBlocks[parseInt(index)]; + }); + + html = html.replace(/\x00INLINECODE(\d+)\x00/g, (match, index) => { + return inlineCode[parseInt(index)]; + }); + + // Line breaks (after code blocks are restored) + html = html.replace(/\n\n/g, '

    '); + html = html.replace(/\n/g, '
    '); + + // Wrap in paragraph tags if not already wrapped + if (!html.startsWith('<')) { + html = '

    ' + html + '

    '; + } + + // Clean up the final HTML + // Remove
    tags immediately before block elements + html = html.replace(/\s*
    /g, '
    ');
    +    html = html.replace(/\s*