Repository: end-4/dots-hyprland Branch: main Commit: c1b37bc46766 Files: 841 Total size: 3.1 MB Directory structure: gitextract_ce6i6hav/ ├── .github/ │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── 1-issue.yml │ │ ├── 2-feature_request.yml │ │ └── config.yml │ ├── README.md │ ├── pull_request_template.md │ └── workflows/ │ ├── auto-close-issue.yml │ ├── dist-update-notification.yml │ ├── dump-github-context.yml │ └── moderator.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── diagnose ├── dots/ │ ├── .config/ │ │ ├── Kvantum/ │ │ │ ├── Colloid/ │ │ │ │ ├── Colloid.kvconfig │ │ │ │ └── ColloidDark.kvconfig │ │ │ ├── MaterialAdw/ │ │ │ │ └── MaterialAdw.kvconfig │ │ │ └── kvantum.kvconfig │ │ ├── chrome-flags.conf │ │ ├── code-flags.conf │ │ ├── darklyrc │ │ ├── dolphinrc │ │ ├── fish/ │ │ │ ├── auto-Hypr.fish │ │ │ ├── config.fish │ │ │ └── fish_variables │ │ ├── fontconfig/ │ │ │ └── fonts.conf │ │ ├── foot/ │ │ │ └── foot.ini │ │ ├── fuzzel/ │ │ │ ├── fuzzel.ini │ │ │ └── fuzzel_theme.ini │ │ ├── hypr/ │ │ │ ├── custom/ │ │ │ │ ├── env.lua │ │ │ │ ├── execs.lua │ │ │ │ ├── general.lua │ │ │ │ ├── keybinds.lua │ │ │ │ ├── rules.lua │ │ │ │ ├── scripts/ │ │ │ │ │ └── __restore_video_wallpaper.sh │ │ │ │ └── variables.lua │ │ │ ├── hypridle.conf │ │ │ ├── hyprland/ │ │ │ │ ├── colors.lua │ │ │ │ ├── env.lua │ │ │ │ ├── execs.lua │ │ │ │ ├── general.lua │ │ │ │ ├── keybinds.lua │ │ │ │ ├── lib/ │ │ │ │ │ └── init.lua │ │ │ │ ├── rules.lua │ │ │ │ ├── scripts/ │ │ │ │ │ ├── ai/ │ │ │ │ │ │ ├── license_show-loaded-ollama-models.txt │ │ │ │ │ │ ├── primary-buffer-query.sh │ │ │ │ │ │ └── show-loaded-ollama-models.sh │ │ │ │ │ ├── fuzzel-emoji.sh │ │ │ │ │ ├── launch_first_available.sh │ │ │ │ │ ├── snip_to_search.sh │ │ │ │ │ └── start_geoclue_agent.sh │ │ │ │ ├── services/ │ │ │ │ │ ├── create_custom_config.lua │ │ │ │ │ └── init.lua │ │ │ │ ├── shellOverrides/ │ │ │ │ │ └── main.lua │ │ │ │ └── variables.lua │ │ │ ├── hyprland.lua │ │ │ ├── hyprlock/ │ │ │ │ ├── check-capslock.sh │ │ │ │ ├── colors.conf │ │ │ │ └── status.sh │ │ │ └── hyprlock.conf │ │ ├── kde-material-you-colors/ │ │ │ └── config.conf │ │ ├── kdeglobals │ │ ├── kitty/ │ │ │ ├── kitty.conf │ │ │ ├── scroll_mark.py │ │ │ └── search.py │ │ ├── konsolerc │ │ ├── matugen/ │ │ │ ├── config.toml │ │ │ └── templates/ │ │ │ ├── ags/ │ │ │ │ ├── _material.scss │ │ │ │ ├── sourceviewtheme-light.xml │ │ │ │ └── sourceviewtheme.xml │ │ │ ├── colors.json │ │ │ ├── fuzzel/ │ │ │ │ └── fuzzel_theme.ini │ │ │ ├── gtk-3.0/ │ │ │ │ └── gtk.css │ │ │ ├── gtk-4.0/ │ │ │ │ └── gtk.css │ │ │ ├── hyprland/ │ │ │ │ ├── colors.lua │ │ │ │ └── hyprlock-colors.conf │ │ │ ├── kde/ │ │ │ │ ├── color.txt │ │ │ │ └── kde-material-you-colors-wrapper.sh │ │ │ └── wallpaper.txt │ │ ├── mpv/ │ │ │ └── mpv.conf │ │ ├── quickshell/ │ │ │ └── ii/ │ │ │ ├── .qmlformat.ini │ │ │ ├── GlobalStates.qml │ │ │ ├── ReloadPopup.qml │ │ │ ├── assets/ │ │ │ │ └── icons/ │ │ │ │ └── fluent/ │ │ │ │ └── README.md │ │ │ ├── defaults/ │ │ │ │ └── ai/ │ │ │ │ ├── README.md │ │ │ │ └── prompts/ │ │ │ │ ├── NoPrompt.md │ │ │ │ ├── ii-Default.md │ │ │ │ ├── ii-Imouto.md │ │ │ │ ├── nyarch-Acchan.md │ │ │ │ ├── w-FourPointedSparkle.md │ │ │ │ └── w-OpenMechanicalFlower.md │ │ │ ├── killDialog.qml │ │ │ ├── modules/ │ │ │ │ ├── common/ │ │ │ │ │ ├── Appearance.qml │ │ │ │ │ ├── Config.qml │ │ │ │ │ ├── Directories.qml │ │ │ │ │ ├── Icons.qml │ │ │ │ │ ├── Images.qml │ │ │ │ │ ├── Persistent.qml │ │ │ │ │ ├── functions/ │ │ │ │ │ │ ├── ColorUtils.qml │ │ │ │ │ │ ├── DateUtils.qml │ │ │ │ │ │ ├── FileUtils.qml │ │ │ │ │ │ ├── Fuzzy.qml │ │ │ │ │ │ ├── Levendist.qml │ │ │ │ │ │ ├── NotificationUtils.qml │ │ │ │ │ │ ├── ObjectUtils.qml │ │ │ │ │ │ ├── Session.qml │ │ │ │ │ │ ├── StringUtils.qml │ │ │ │ │ │ ├── fuzzysort.js │ │ │ │ │ │ └── levendist.js │ │ │ │ │ ├── models/ │ │ │ │ │ │ ├── AdaptedMaterialScheme.qml │ │ │ │ │ │ ├── AnimatedTabIndexPair.qml │ │ │ │ │ │ ├── FolderListModelWithHistory.qml │ │ │ │ │ │ ├── IndexModel.qml │ │ │ │ │ │ ├── LauncherSearchResult.qml │ │ │ │ │ │ ├── NestableObject.qml │ │ │ │ │ │ ├── gCloud/ │ │ │ │ │ │ │ ├── GCloudApi.qml │ │ │ │ │ │ │ ├── GCloudTranslate.qml │ │ │ │ │ │ │ ├── GCloudVision.qml │ │ │ │ │ │ │ └── GCloudVisionResult.qml │ │ │ │ │ │ ├── hyprland/ │ │ │ │ │ │ │ └── HyprlandConfigOption.qml │ │ │ │ │ │ └── quickToggles/ │ │ │ │ │ │ ├── AntiFlashbangToggle.qml │ │ │ │ │ │ ├── AudioToggle.qml │ │ │ │ │ │ ├── BluetoothToggle.qml │ │ │ │ │ │ ├── CloudflareWarpToggle.qml │ │ │ │ │ │ ├── ColorPickerToggle.qml │ │ │ │ │ │ ├── DarkModeToggle.qml │ │ │ │ │ │ ├── EasyEffectsToggle.qml │ │ │ │ │ │ ├── GameModeToggle.qml │ │ │ │ │ │ ├── IdleInhibitorToggle.qml │ │ │ │ │ │ ├── MicToggle.qml │ │ │ │ │ │ ├── MusicRecognitionToggle.qml │ │ │ │ │ │ ├── NetworkToggle.qml │ │ │ │ │ │ ├── NightLightToggle.qml │ │ │ │ │ │ ├── NotificationToggle.qml │ │ │ │ │ │ ├── OnScreenKeyboardToggle.qml │ │ │ │ │ │ ├── PowerProfilesToggle.qml │ │ │ │ │ │ ├── QuickToggleModel.qml │ │ │ │ │ │ └── ScreenSnipToggle.qml │ │ │ │ │ ├── panels/ │ │ │ │ │ │ └── lock/ │ │ │ │ │ │ ├── LockContext.qml │ │ │ │ │ │ ├── LockScreen.qml │ │ │ │ │ │ └── pam/ │ │ │ │ │ │ └── fprintd.conf │ │ │ │ │ ├── utils/ │ │ │ │ │ │ ├── ImageDownloaderProcess.qml │ │ │ │ │ │ ├── MultiTurnProcess.qml │ │ │ │ │ │ ├── ScreenshotAction.qml │ │ │ │ │ │ └── TempScreenshotProcess.qml │ │ │ │ │ └── widgets/ │ │ │ │ │ ├── AddressBar.qml │ │ │ │ │ ├── AddressBreadcrumb.qml │ │ │ │ │ ├── ButtonGroup.qml │ │ │ │ │ ├── CalendarView.qml │ │ │ │ │ ├── Circle.qml │ │ │ │ │ ├── CircularProgress.qml │ │ │ │ │ ├── CliphistImage.qml │ │ │ │ │ ├── ClippedFilledCircularProgress.qml │ │ │ │ │ ├── ClippedProgressBar.qml │ │ │ │ │ ├── ConfigRow.qml │ │ │ │ │ ├── ConfigSelectionArray.qml │ │ │ │ │ ├── ConfigSlider.qml │ │ │ │ │ ├── ConfigSpinBox.qml │ │ │ │ │ ├── ConfigSwitch.qml │ │ │ │ │ ├── ContentPage.qml │ │ │ │ │ ├── ContentSection.qml │ │ │ │ │ ├── ContentSubsection.qml │ │ │ │ │ ├── ContentSubsectionLabel.qml │ │ │ │ │ ├── CustomIcon.qml │ │ │ │ │ ├── DashedBorder.qml │ │ │ │ │ ├── DialogButton.qml │ │ │ │ │ ├── DialogListItem.qml │ │ │ │ │ ├── DirectoryIcon.qml │ │ │ │ │ ├── DragManager.qml │ │ │ │ │ ├── ErrorShakeAnimation.qml │ │ │ │ │ ├── FadeLoader.qml │ │ │ │ │ ├── Favicon.qml │ │ │ │ │ ├── FloatingActionButton.qml │ │ │ │ │ ├── FlowButtonGroup.qml │ │ │ │ │ ├── FocusedScrollMouseArea.qml │ │ │ │ │ ├── FullscreenPolkitWindow.qml │ │ │ │ │ ├── Graph.qml │ │ │ │ │ ├── GroupButton.qml │ │ │ │ │ ├── IconAndTextToolbarButton.qml │ │ │ │ │ ├── IconToolbarButton.qml │ │ │ │ │ ├── KeyboardKey.qml │ │ │ │ │ ├── LightDarkPreferenceButton.qml │ │ │ │ │ ├── MaskMultiEffect.qml │ │ │ │ │ ├── MaterialCookie.qml │ │ │ │ │ ├── MaterialLoadingIndicator.qml │ │ │ │ │ ├── MaterialShape.qml │ │ │ │ │ ├── MaterialShapeWrappedMaterialSymbol.qml │ │ │ │ │ ├── MaterialSymbol.qml │ │ │ │ │ ├── MaterialTextArea.qml │ │ │ │ │ ├── MaterialTextField.qml │ │ │ │ │ ├── MenuButton.qml │ │ │ │ │ ├── NavigationRail.qml │ │ │ │ │ ├── NavigationRailButton.qml │ │ │ │ │ ├── NavigationRailExpandButton.qml │ │ │ │ │ ├── NavigationRailTabArray.qml │ │ │ │ │ ├── NoticeBox.qml │ │ │ │ │ ├── NotificationActionButton.qml │ │ │ │ │ ├── NotificationAppIcon.qml │ │ │ │ │ ├── NotificationGroup.qml │ │ │ │ │ ├── NotificationGroupExpandButton.qml │ │ │ │ │ ├── NotificationItem.qml │ │ │ │ │ ├── NotificationListView.qml │ │ │ │ │ ├── OptionalMaterialSymbol.qml │ │ │ │ │ ├── PagePlaceholder.qml │ │ │ │ │ ├── PointingHandInteraction.qml │ │ │ │ │ ├── PointingHandLinkHover.qml │ │ │ │ │ ├── PopupToolTip.qml │ │ │ │ │ ├── Revealer.qml │ │ │ │ │ ├── RippleButton.qml │ │ │ │ │ ├── RippleButtonWithIcon.qml │ │ │ │ │ ├── RoundCorner.qml │ │ │ │ │ ├── ScrollEdgeFade.qml │ │ │ │ │ ├── SecondaryTabBar.qml │ │ │ │ │ ├── SecondaryTabButton.qml │ │ │ │ │ ├── SelectionDialog.qml │ │ │ │ │ ├── SelectionGroupButton.qml │ │ │ │ │ ├── SineCookie.qml │ │ │ │ │ ├── SqueezedAnnotationStyledText.qml │ │ │ │ │ ├── StyledBlurEffect.qml │ │ │ │ │ ├── StyledComboBox.qml │ │ │ │ │ ├── StyledDropShadow.qml │ │ │ │ │ ├── StyledFlickable.qml │ │ │ │ │ ├── StyledImage.qml │ │ │ │ │ ├── StyledIndeterminateProgressBar.qml │ │ │ │ │ ├── StyledListView.qml │ │ │ │ │ ├── StyledProgressBar.qml │ │ │ │ │ ├── StyledRadioButton.qml │ │ │ │ │ ├── StyledRectangularShadow.qml │ │ │ │ │ ├── StyledScrollBar.qml │ │ │ │ │ ├── StyledSlider.qml │ │ │ │ │ ├── StyledSpinBox.qml │ │ │ │ │ ├── StyledSwitch.qml │ │ │ │ │ ├── StyledText.qml │ │ │ │ │ ├── StyledTextArea.qml │ │ │ │ │ ├── StyledTextInput.qml │ │ │ │ │ ├── StyledToolTip.qml │ │ │ │ │ ├── StyledToolTipContent.qml │ │ │ │ │ ├── ThumbnailImage.qml │ │ │ │ │ ├── Toolbar.qml │ │ │ │ │ ├── ToolbarButton.qml │ │ │ │ │ ├── ToolbarPairedFab.qml │ │ │ │ │ ├── ToolbarTabBar.qml │ │ │ │ │ ├── ToolbarTabButton.qml │ │ │ │ │ ├── ToolbarTextField.qml │ │ │ │ │ ├── VerticalButtonGroup.qml │ │ │ │ │ ├── VibrantToolbarButton.qml │ │ │ │ │ ├── WaveVisualizer.qml │ │ │ │ │ ├── WavyLine.qml │ │ │ │ │ ├── WeekRow.qml │ │ │ │ │ ├── WindowDialog.qml │ │ │ │ │ ├── WindowDialogButtonRow.qml │ │ │ │ │ ├── WindowDialogParagraph.qml │ │ │ │ │ ├── WindowDialogSectionHeader.qml │ │ │ │ │ ├── WindowDialogSeparator.qml │ │ │ │ │ ├── WindowDialogSlider.qml │ │ │ │ │ ├── WindowDialogTitle.qml │ │ │ │ │ └── widgetCanvas/ │ │ │ │ │ ├── AbstractOverlayWidget.qml │ │ │ │ │ ├── AbstractWidget.qml │ │ │ │ │ └── WidgetCanvas.qml │ │ │ │ ├── ii/ │ │ │ │ │ ├── background/ │ │ │ │ │ │ ├── Background.qml │ │ │ │ │ │ └── widgets/ │ │ │ │ │ │ ├── AbstractBackgroundWidget.qml │ │ │ │ │ │ ├── clock/ │ │ │ │ │ │ │ ├── ClockText.qml │ │ │ │ │ │ │ ├── ClockWidget.qml │ │ │ │ │ │ │ ├── CookieClock.qml │ │ │ │ │ │ │ ├── CookieQuote.qml │ │ │ │ │ │ │ ├── DigitalClock.qml │ │ │ │ │ │ │ ├── HourHand.qml │ │ │ │ │ │ │ ├── HourMarks.qml │ │ │ │ │ │ │ ├── MinuteHand.qml │ │ │ │ │ │ │ ├── SecondHand.qml │ │ │ │ │ │ │ ├── TimeColumn.qml │ │ │ │ │ │ │ ├── dateIndicator/ │ │ │ │ │ │ │ │ ├── BubbleDate.qml │ │ │ │ │ │ │ │ ├── DateIndicator.qml │ │ │ │ │ │ │ │ ├── RectangleDate.qml │ │ │ │ │ │ │ │ └── RotatingDate.qml │ │ │ │ │ │ │ └── minuteMarks/ │ │ │ │ │ │ │ ├── BigHourNumbers.qml │ │ │ │ │ │ │ ├── Dots.qml │ │ │ │ │ │ │ ├── Lines.qml │ │ │ │ │ │ │ └── MinuteMarks.qml │ │ │ │ │ │ └── weather/ │ │ │ │ │ │ └── WeatherWidget.qml │ │ │ │ │ ├── bar/ │ │ │ │ │ │ ├── ActiveWindow.qml │ │ │ │ │ │ ├── Bar.qml │ │ │ │ │ │ ├── BarContent.qml │ │ │ │ │ │ ├── BarGroup.qml │ │ │ │ │ │ ├── BatteryIndicator.qml │ │ │ │ │ │ ├── BatteryPopup.qml │ │ │ │ │ │ ├── CircleUtilButton.qml │ │ │ │ │ │ ├── ClockWidget.qml │ │ │ │ │ │ ├── ClockWidgetPopup.qml │ │ │ │ │ │ ├── HyprlandXkbIndicator.qml │ │ │ │ │ │ ├── LeftSidebarButton.qml │ │ │ │ │ │ ├── Media.qml │ │ │ │ │ │ ├── NotificationUnreadCount.qml │ │ │ │ │ │ ├── Resource.qml │ │ │ │ │ │ ├── Resources.qml │ │ │ │ │ │ ├── ResourcesPopup.qml │ │ │ │ │ │ ├── ScrollHint.qml │ │ │ │ │ │ ├── StyledPopup.qml │ │ │ │ │ │ ├── StyledPopupHeaderRow.qml │ │ │ │ │ │ ├── StyledPopupValueRow.qml │ │ │ │ │ │ ├── SysTray.qml │ │ │ │ │ │ ├── SysTrayItem.qml │ │ │ │ │ │ ├── SysTrayMenu.qml │ │ │ │ │ │ ├── SysTrayMenuEntry.qml │ │ │ │ │ │ ├── UtilButtons.qml │ │ │ │ │ │ ├── Workspaces.qml │ │ │ │ │ │ └── weather/ │ │ │ │ │ │ ├── WeatherBar.qml │ │ │ │ │ │ ├── WeatherCard.qml │ │ │ │ │ │ └── WeatherPopup.qml │ │ │ │ │ ├── cheatsheet/ │ │ │ │ │ │ ├── Cheatsheet.qml │ │ │ │ │ │ ├── CheatsheetKeybinds.qml │ │ │ │ │ │ ├── CheatsheetKeybindsCategory.qml │ │ │ │ │ │ ├── CheatsheetPeriodicTable.qml │ │ │ │ │ │ ├── ElementTile.qml │ │ │ │ │ │ └── periodic_table.js │ │ │ │ │ ├── dock/ │ │ │ │ │ │ ├── Dock.qml │ │ │ │ │ │ ├── DockAppButton.qml │ │ │ │ │ │ ├── DockApps.qml │ │ │ │ │ │ ├── DockButton.qml │ │ │ │ │ │ └── DockSeparator.qml │ │ │ │ │ ├── lock/ │ │ │ │ │ │ ├── Lock.qml │ │ │ │ │ │ ├── LockSurface.qml │ │ │ │ │ │ └── PasswordChars.qml │ │ │ │ │ ├── mediaControls/ │ │ │ │ │ │ ├── MediaControls.qml │ │ │ │ │ │ └── PlayerControl.qml │ │ │ │ │ ├── notificationPopup/ │ │ │ │ │ │ └── NotificationPopup.qml │ │ │ │ │ ├── onScreenDisplay/ │ │ │ │ │ │ ├── OnScreenDisplay.qml │ │ │ │ │ │ ├── OsdValueIndicator.qml │ │ │ │ │ │ └── indicators/ │ │ │ │ │ │ ├── BrightnessIndicator.qml │ │ │ │ │ │ ├── GammaIndicator.qml │ │ │ │ │ │ └── VolumeIndicator.qml │ │ │ │ │ ├── onScreenKeyboard/ │ │ │ │ │ │ ├── OnScreenKeyboard.qml │ │ │ │ │ │ ├── OskContent.qml │ │ │ │ │ │ ├── OskKey.qml │ │ │ │ │ │ └── layouts.js │ │ │ │ │ ├── overlay/ │ │ │ │ │ │ ├── Overlay.qml │ │ │ │ │ │ ├── OverlayBackground.qml │ │ │ │ │ │ ├── OverlayContent.qml │ │ │ │ │ │ ├── OverlayContext.qml │ │ │ │ │ │ ├── OverlayTaskbar.qml │ │ │ │ │ │ ├── OverlayWidgetDelegateChooser.qml │ │ │ │ │ │ ├── StyledOverlayWidget.qml │ │ │ │ │ │ ├── crosshair/ │ │ │ │ │ │ │ ├── Crosshair.qml │ │ │ │ │ │ │ └── CrosshairContent.qml │ │ │ │ │ │ ├── floatingImage/ │ │ │ │ │ │ │ └── FloatingImage.qml │ │ │ │ │ │ ├── fpsLimiter/ │ │ │ │ │ │ │ ├── FpsLimiter.qml │ │ │ │ │ │ │ └── FpsLimiterContent.qml │ │ │ │ │ │ ├── notes/ │ │ │ │ │ │ │ ├── Notes.qml │ │ │ │ │ │ │ └── NotesContent.qml │ │ │ │ │ │ ├── recorder/ │ │ │ │ │ │ │ └── Recorder.qml │ │ │ │ │ │ ├── resources/ │ │ │ │ │ │ │ └── Resources.qml │ │ │ │ │ │ └── volumeMixer/ │ │ │ │ │ │ └── VolumeMixer.qml │ │ │ │ │ ├── overview/ │ │ │ │ │ │ ├── Overview.qml │ │ │ │ │ │ ├── OverviewWidget.qml │ │ │ │ │ │ ├── OverviewWindow.qml │ │ │ │ │ │ ├── SearchBar.qml │ │ │ │ │ │ ├── SearchItem.qml │ │ │ │ │ │ └── SearchWidget.qml │ │ │ │ │ ├── polkit/ │ │ │ │ │ │ ├── Polkit.qml │ │ │ │ │ │ └── PolkitContent.qml │ │ │ │ │ ├── regionSelector/ │ │ │ │ │ │ ├── CircleSelectionDetails.qml │ │ │ │ │ │ ├── CursorGuide.qml │ │ │ │ │ │ ├── OptionsToolbar.qml │ │ │ │ │ │ ├── RectCornersSelectionDetails.qml │ │ │ │ │ │ ├── RegionFunctions.qml │ │ │ │ │ │ ├── RegionSelection.qml │ │ │ │ │ │ ├── RegionSelector.qml │ │ │ │ │ │ └── TargetRegion.qml │ │ │ │ │ ├── screenCorners/ │ │ │ │ │ │ └── ScreenCorners.qml │ │ │ │ │ ├── screenTranslator/ │ │ │ │ │ │ ├── ScreenTextOverlay.qml │ │ │ │ │ │ ├── ScreenTranslator.qml │ │ │ │ │ │ └── ScreenTranslatorPanel.qml │ │ │ │ │ ├── sessionScreen/ │ │ │ │ │ │ ├── SessionActionButton.qml │ │ │ │ │ │ └── SessionScreen.qml │ │ │ │ │ ├── sidebarLeft/ │ │ │ │ │ │ ├── AiChat.qml │ │ │ │ │ │ ├── Anime.qml │ │ │ │ │ │ ├── ApiCommandButton.qml │ │ │ │ │ │ ├── ApiInputBoxIndicator.qml │ │ │ │ │ │ ├── DescriptionBox.qml │ │ │ │ │ │ ├── ScrollToBottomButton.qml │ │ │ │ │ │ ├── SidebarLeft.qml │ │ │ │ │ │ ├── SidebarLeftContent.qml │ │ │ │ │ │ ├── Translator.qml │ │ │ │ │ │ ├── aiChat/ │ │ │ │ │ │ │ ├── AiMessage.qml │ │ │ │ │ │ │ ├── AiMessageControlButton.qml │ │ │ │ │ │ │ ├── AnnotationSourceButton.qml │ │ │ │ │ │ │ ├── AttachedFileIndicator.qml │ │ │ │ │ │ │ ├── MessageCodeBlock.qml │ │ │ │ │ │ │ ├── MessageTextBlock.qml │ │ │ │ │ │ │ ├── MessageThinkBlock.qml │ │ │ │ │ │ │ └── SearchQueryButton.qml │ │ │ │ │ │ ├── anime/ │ │ │ │ │ │ │ ├── BooruImage.qml │ │ │ │ │ │ │ └── BooruResponse.qml │ │ │ │ │ │ └── translator/ │ │ │ │ │ │ ├── LanguageSelectorButton.qml │ │ │ │ │ │ └── TextCanvas.qml │ │ │ │ │ ├── sidebarRight/ │ │ │ │ │ │ ├── BottomWidgetGroup.qml │ │ │ │ │ │ ├── CenterWidgetGroup.qml │ │ │ │ │ │ ├── QuickSliders.qml │ │ │ │ │ │ ├── SidebarRight.qml │ │ │ │ │ │ ├── SidebarRightContent.qml │ │ │ │ │ │ ├── bluetoothDevices/ │ │ │ │ │ │ │ ├── BluetoothDeviceItem.qml │ │ │ │ │ │ │ └── BluetoothDialog.qml │ │ │ │ │ │ ├── calendar/ │ │ │ │ │ │ │ ├── CalendarDayButton.qml │ │ │ │ │ │ │ ├── CalendarHeaderButton.qml │ │ │ │ │ │ │ ├── CalendarWidget.qml │ │ │ │ │ │ │ └── calendar_layout.js │ │ │ │ │ │ ├── nightLight/ │ │ │ │ │ │ │ └── NightLightDialog.qml │ │ │ │ │ │ ├── notifications/ │ │ │ │ │ │ │ ├── NotificationList.qml │ │ │ │ │ │ │ └── NotificationStatusButton.qml │ │ │ │ │ │ ├── pomodoro/ │ │ │ │ │ │ │ ├── PomodoroTimer.qml │ │ │ │ │ │ │ ├── PomodoroWidget.qml │ │ │ │ │ │ │ └── Stopwatch.qml │ │ │ │ │ │ ├── quickToggles/ │ │ │ │ │ │ │ ├── AbstractQuickPanel.qml │ │ │ │ │ │ │ ├── AndroidQuickPanel.qml │ │ │ │ │ │ │ ├── ClassicQuickPanel.qml │ │ │ │ │ │ │ ├── androidStyle/ │ │ │ │ │ │ │ │ ├── AndroidAntiFlashbangToggle.qml │ │ │ │ │ │ │ │ ├── AndroidAudioToggle.qml │ │ │ │ │ │ │ │ ├── AndroidBluetoothToggle.qml │ │ │ │ │ │ │ │ ├── AndroidCloudflareWarpToggle.qml │ │ │ │ │ │ │ │ ├── AndroidColorPickerToggle.qml │ │ │ │ │ │ │ │ ├── AndroidDarkModeToggle.qml │ │ │ │ │ │ │ │ ├── AndroidEasyEffectsToggle.qml │ │ │ │ │ │ │ │ ├── AndroidGameModeToggle.qml │ │ │ │ │ │ │ │ ├── AndroidIdleInhibitorToggle.qml │ │ │ │ │ │ │ │ ├── AndroidMicToggle.qml │ │ │ │ │ │ │ │ ├── AndroidMusicRecognition.qml │ │ │ │ │ │ │ │ ├── AndroidNetworkToggle.qml │ │ │ │ │ │ │ │ ├── AndroidNightLightToggle.qml │ │ │ │ │ │ │ │ ├── AndroidNotificationToggle.qml │ │ │ │ │ │ │ │ ├── AndroidOnScreenKeyboardToggle.qml │ │ │ │ │ │ │ │ ├── AndroidPowerProfileToggle.qml │ │ │ │ │ │ │ │ ├── AndroidQuickToggleButton.qml │ │ │ │ │ │ │ │ ├── AndroidScreenSnipToggle.qml │ │ │ │ │ │ │ │ └── AndroidToggleDelegateChooser.qml │ │ │ │ │ │ │ └── classicStyle/ │ │ │ │ │ │ │ ├── BluetoothToggle.qml │ │ │ │ │ │ │ ├── CloudflareWarp.qml │ │ │ │ │ │ │ ├── EasyEffectsToggle.qml │ │ │ │ │ │ │ ├── GameMode.qml │ │ │ │ │ │ │ ├── IdleInhibitor.qml │ │ │ │ │ │ │ ├── NetworkToggle.qml │ │ │ │ │ │ │ ├── NightLight.qml │ │ │ │ │ │ │ └── QuickToggleButton.qml │ │ │ │ │ │ ├── todo/ │ │ │ │ │ │ │ ├── TaskList.qml │ │ │ │ │ │ │ ├── TodoItemActionButton.qml │ │ │ │ │ │ │ └── TodoWidget.qml │ │ │ │ │ │ ├── volumeMixer/ │ │ │ │ │ │ │ ├── AudioDeviceSelectorButton.qml │ │ │ │ │ │ │ ├── VolumeDialog.qml │ │ │ │ │ │ │ ├── VolumeDialogContent.qml │ │ │ │ │ │ │ └── VolumeMixerEntry.qml │ │ │ │ │ │ └── wifiNetworks/ │ │ │ │ │ │ ├── WifiDialog.qml │ │ │ │ │ │ └── WifiNetworkItem.qml │ │ │ │ │ ├── verticalBar/ │ │ │ │ │ │ ├── BatteryIndicator.qml │ │ │ │ │ │ ├── Resource.qml │ │ │ │ │ │ ├── Resources.qml │ │ │ │ │ │ ├── VerticalBar.qml │ │ │ │ │ │ ├── VerticalBarContent.qml │ │ │ │ │ │ ├── VerticalClockWidget.qml │ │ │ │ │ │ └── VerticalMedia.qml │ │ │ │ │ └── wallpaperSelector/ │ │ │ │ │ ├── WallpaperDirectoryItem.qml │ │ │ │ │ ├── WallpaperSelector.qml │ │ │ │ │ └── WallpaperSelectorContent.qml │ │ │ │ ├── settings/ │ │ │ │ │ ├── About.qml │ │ │ │ │ ├── AdvancedConfig.qml │ │ │ │ │ ├── BackgroundConfig.qml │ │ │ │ │ ├── BarConfig.qml │ │ │ │ │ ├── GeneralConfig.qml │ │ │ │ │ ├── InterfaceConfig.qml │ │ │ │ │ ├── QuickConfig.qml │ │ │ │ │ └── ServicesConfig.qml │ │ │ │ └── waffle/ │ │ │ │ ├── README.md │ │ │ │ ├── actionCenter/ │ │ │ │ │ ├── ActionCenterContent.qml │ │ │ │ │ ├── ActionCenterContext.qml │ │ │ │ │ ├── ExpandableChoiceButton.qml │ │ │ │ │ ├── HeaderRow.qml │ │ │ │ │ ├── MediaPaneContent.qml │ │ │ │ │ ├── SectionText.qml │ │ │ │ │ ├── ToggleItem.qml │ │ │ │ │ ├── WaffleActionCenter.qml │ │ │ │ │ ├── bluetooth/ │ │ │ │ │ │ ├── BluetoothControl.qml │ │ │ │ │ │ └── BluetoothDeviceItem.qml │ │ │ │ │ ├── mainPage/ │ │ │ │ │ │ ├── MainPageBody.qml │ │ │ │ │ │ ├── MainPageBodySliders.qml │ │ │ │ │ │ ├── MainPageBodyToggles.qml │ │ │ │ │ │ └── MainPageFooter.qml │ │ │ │ │ ├── nightLight/ │ │ │ │ │ │ └── NightLightControl.qml │ │ │ │ │ ├── toggles/ │ │ │ │ │ │ ├── ActionCenterToggleButton.qml │ │ │ │ │ │ ├── ActionCenterTogglesDelegateChooser.qml │ │ │ │ │ │ └── WNetworkToggle.qml │ │ │ │ │ ├── volumeControl/ │ │ │ │ │ │ ├── VolumeControl.qml │ │ │ │ │ │ └── VolumeEntry.qml │ │ │ │ │ └── wifi/ │ │ │ │ │ ├── WWifiNetworkItem.qml │ │ │ │ │ └── WifiControl.qml │ │ │ │ ├── background/ │ │ │ │ │ └── WaffleBackground.qml │ │ │ │ ├── bar/ │ │ │ │ │ ├── AppButton.qml │ │ │ │ │ ├── BarButton.qml │ │ │ │ │ ├── BarIconButton.qml │ │ │ │ │ ├── BarMenu.qml │ │ │ │ │ ├── BarPopup.qml │ │ │ │ │ ├── BarToolTip.qml │ │ │ │ │ ├── SearchButton.qml │ │ │ │ │ ├── StartButton.qml │ │ │ │ │ ├── SystemButton.qml │ │ │ │ │ ├── TaskViewButton.qml │ │ │ │ │ ├── TimeButton.qml │ │ │ │ │ ├── UpdatesButton.qml │ │ │ │ │ ├── WaffleBar.qml │ │ │ │ │ ├── WaffleBarContent.qml │ │ │ │ │ ├── WidgetsButton.qml │ │ │ │ │ ├── tasks/ │ │ │ │ │ │ ├── TaskAppButton.qml │ │ │ │ │ │ ├── TaskPreview.qml │ │ │ │ │ │ ├── Tasks.qml │ │ │ │ │ │ └── WindowPreview.qml │ │ │ │ │ └── tray/ │ │ │ │ │ ├── Tray.qml │ │ │ │ │ ├── TrayButton.qml │ │ │ │ │ └── TrayOverflowMenu.qml │ │ │ │ ├── lock/ │ │ │ │ │ └── WaffleLock.qml │ │ │ │ ├── looks/ │ │ │ │ │ ├── AcrylicButton.qml │ │ │ │ │ ├── AcrylicRectangle.qml │ │ │ │ │ ├── BodyRectangle.qml │ │ │ │ │ ├── CloseButton.qml │ │ │ │ │ ├── FluentIcon.qml │ │ │ │ │ ├── FooterRectangle.qml │ │ │ │ │ ├── Looks.qml │ │ │ │ │ ├── VerticalPageIndicator.qml │ │ │ │ │ ├── WAmbientShadow.qml │ │ │ │ │ ├── WAppIcon.qml │ │ │ │ │ ├── WBarAttachedPanelContent.qml │ │ │ │ │ ├── WBorderedButton.qml │ │ │ │ │ ├── WBorderlessButton.qml │ │ │ │ │ ├── WButton.qml │ │ │ │ │ ├── WChoiceButton.qml │ │ │ │ │ ├── WFadeLoader.qml │ │ │ │ │ ├── WIcons.qml │ │ │ │ │ ├── WIndeterminateProgressBar.qml │ │ │ │ │ ├── WListView.qml │ │ │ │ │ ├── WMenu.qml │ │ │ │ │ ├── WMenuItem.qml │ │ │ │ │ ├── WMouseAreaButton.qml │ │ │ │ │ ├── WPane.qml │ │ │ │ │ ├── WPanelIconButton.qml │ │ │ │ │ ├── WPanelPageColumn.qml │ │ │ │ │ ├── WPanelSeparator.qml │ │ │ │ │ ├── WPopupToolTip.qml │ │ │ │ │ ├── WProgressBar.qml │ │ │ │ │ ├── WRectangularShadow.qml │ │ │ │ │ ├── WRectangularShadowThis.qml │ │ │ │ │ ├── WScrollBar.qml │ │ │ │ │ ├── WSlider.qml │ │ │ │ │ ├── WStackView.qml │ │ │ │ │ ├── WSwitch.qml │ │ │ │ │ ├── WText.qml │ │ │ │ │ ├── WTextButton.qml │ │ │ │ │ ├── WTextField.qml │ │ │ │ │ ├── WTextInput.qml │ │ │ │ │ ├── WTextWithFixedWidth.qml │ │ │ │ │ ├── WToolTip.qml │ │ │ │ │ ├── WToolTipContent.qml │ │ │ │ │ ├── WToolbar.qml │ │ │ │ │ ├── WToolbarButton.qml │ │ │ │ │ ├── WToolbarIconButton.qml │ │ │ │ │ ├── WToolbarIconTabButton.qml │ │ │ │ │ ├── WToolbarSeparator.qml │ │ │ │ │ ├── WToolbarTabBar.qml │ │ │ │ │ └── WUserAvatar.qml │ │ │ │ ├── notificationCenter/ │ │ │ │ │ ├── CalendarWidget.qml │ │ │ │ │ ├── DateHeader.qml │ │ │ │ │ ├── FocusFooter.qml │ │ │ │ │ ├── NotificationCenterContent.qml │ │ │ │ │ ├── NotificationHeaderButton.qml │ │ │ │ │ ├── NotificationPaneContent.qml │ │ │ │ │ ├── SmallBorderedIconAndTextButton.qml │ │ │ │ │ ├── SmallBorderedIconButton.qml │ │ │ │ │ ├── WNotificationAppIcon.qml │ │ │ │ │ ├── WNotificationDismissAnim.qml │ │ │ │ │ ├── WNotificationGroup.qml │ │ │ │ │ ├── WSingleNotification.qml │ │ │ │ │ └── WaffleNotificationCenter.qml │ │ │ │ ├── notificationPopup/ │ │ │ │ │ └── WaffleNotificationPopup.qml │ │ │ │ ├── onScreenDisplay/ │ │ │ │ │ ├── BrightnessOSD.qml │ │ │ │ │ ├── OSDValue.qml │ │ │ │ │ ├── VolumeOSD.qml │ │ │ │ │ └── WaffleOSD.qml │ │ │ │ ├── polkit/ │ │ │ │ │ ├── WPolkitContent.qml │ │ │ │ │ └── WafflePolkit.qml │ │ │ │ ├── screenSnip/ │ │ │ │ │ ├── WRectangularSelection.qml │ │ │ │ │ ├── WRegionSelectionPanel.qml │ │ │ │ │ └── WScreenSnip.qml │ │ │ │ ├── sessionScreen/ │ │ │ │ │ ├── PowerButton.qml │ │ │ │ │ ├── SessionScreenContent.qml │ │ │ │ │ ├── WSessionScreenTextButton.qml │ │ │ │ │ └── WaffleSessionScreen.qml │ │ │ │ ├── startMenu/ │ │ │ │ │ ├── SearchBar.qml │ │ │ │ │ ├── StartMenuContent.qml │ │ │ │ │ ├── StartMenuContext.qml │ │ │ │ │ ├── WaffleStartMenu.qml │ │ │ │ │ ├── searchPage/ │ │ │ │ │ │ ├── SearchEntryIcon.qml │ │ │ │ │ │ ├── SearchPageContent.qml │ │ │ │ │ │ ├── SearchResultButton.qml │ │ │ │ │ │ ├── SearchResults.qml │ │ │ │ │ │ └── TagStrip.qml │ │ │ │ │ └── startPage/ │ │ │ │ │ ├── AggregatedAppCategoryModel.qml │ │ │ │ │ ├── AllAppsGrid.qml │ │ │ │ │ ├── AppCategoryGrid.qml │ │ │ │ │ ├── BigAppGrid.qml │ │ │ │ │ ├── StartAppButton.qml │ │ │ │ │ ├── StartPageApps.qml │ │ │ │ │ ├── StartPageContent.qml │ │ │ │ │ └── StartUserButton.qml │ │ │ │ └── taskView/ │ │ │ │ ├── TaskViewContent.qml │ │ │ │ ├── TaskViewWindow.qml │ │ │ │ ├── TaskViewWorkspace.qml │ │ │ │ ├── WaffleTaskView.qml │ │ │ │ └── window-layout.js │ │ │ ├── panelFamilies/ │ │ │ │ ├── IllogicalImpulseFamily.qml │ │ │ │ ├── PanelLoader.qml │ │ │ │ └── WaffleFamily.qml │ │ │ ├── scripts/ │ │ │ │ ├── ai/ │ │ │ │ │ ├── gemini-categorize-wallpaper.sh │ │ │ │ │ ├── gemini-translate.sh │ │ │ │ │ └── show-installed-ollama-models.sh │ │ │ │ ├── cava/ │ │ │ │ │ └── raw_output_config.txt │ │ │ │ ├── colors/ │ │ │ │ │ ├── applycolor.sh │ │ │ │ │ ├── code/ │ │ │ │ │ │ └── material-code-set-color.sh │ │ │ │ │ ├── generate_colors_material.py │ │ │ │ │ ├── random/ │ │ │ │ │ │ ├── random_konachan_wall.sh │ │ │ │ │ │ └── random_osu_wall.sh │ │ │ │ │ ├── scheme_for_image.py │ │ │ │ │ ├── switchwall.sh │ │ │ │ │ └── terminal/ │ │ │ │ │ ├── kitty-theme.conf │ │ │ │ │ ├── scheme-base.json │ │ │ │ │ └── sequences.txt │ │ │ │ ├── hyprland/ │ │ │ │ │ └── hyprconfigurator.py │ │ │ │ ├── images/ │ │ │ │ │ ├── find-regions-venv.sh │ │ │ │ │ ├── find_regions.py │ │ │ │ │ ├── least-busy-region-venv.sh │ │ │ │ │ ├── least_busy_region.py │ │ │ │ │ ├── text-color-venv.sh │ │ │ │ │ └── text_color.py │ │ │ │ ├── keyring/ │ │ │ │ │ ├── is_unlocked.sh │ │ │ │ │ ├── try_lookup.sh │ │ │ │ │ └── unlock.sh │ │ │ │ ├── musicRecognition/ │ │ │ │ │ └── recognize-music.sh │ │ │ │ ├── thumbnails/ │ │ │ │ │ ├── generate-thumbnails-magick.sh │ │ │ │ │ ├── thumbgen-venv.sh │ │ │ │ │ └── thumbgen.py │ │ │ │ └── videos/ │ │ │ │ └── record.sh │ │ │ ├── services/ │ │ │ │ ├── Ai.qml │ │ │ │ ├── AppSearch.qml │ │ │ │ ├── Audio.qml │ │ │ │ ├── Battery.qml │ │ │ │ ├── BluetoothStatus.qml │ │ │ │ ├── Booru.qml │ │ │ │ ├── BooruResponseData.qml │ │ │ │ ├── Brightness.qml │ │ │ │ ├── Cliphist.qml │ │ │ │ ├── ConflictKiller.qml │ │ │ │ ├── DateTime.qml │ │ │ │ ├── EasyEffects.qml │ │ │ │ ├── Emojis.qml │ │ │ │ ├── FirstRunExperience.qml │ │ │ │ ├── GlobalFocusGrab.qml │ │ │ │ ├── GoogleCloud.qml │ │ │ │ ├── HyprlandAntiFlashbangShader.qml │ │ │ │ ├── HyprlandConfig.qml │ │ │ │ ├── HyprlandData.qml │ │ │ │ ├── HyprlandKeybinds.qml │ │ │ │ ├── HyprlandXkb.qml │ │ │ │ ├── Hyprsunset.qml │ │ │ │ ├── Idle.qml │ │ │ │ ├── KeyringStorage.qml │ │ │ │ ├── LatexRenderer.qml │ │ │ │ ├── LauncherApps.qml │ │ │ │ ├── LauncherSearch.qml │ │ │ │ ├── MaterialThemeLoader.qml │ │ │ │ ├── MprisController.qml │ │ │ │ ├── Network.qml │ │ │ │ ├── Notifications.qml │ │ │ │ ├── PolkitService.qml │ │ │ │ ├── Privacy.qml │ │ │ │ ├── ResourceUsage.qml │ │ │ │ ├── SessionWarnings.qml │ │ │ │ ├── SongRec.qml │ │ │ │ ├── SystemInfo.qml │ │ │ │ ├── TaskbarApps.qml │ │ │ │ ├── TimerService.qml │ │ │ │ ├── Todo.qml │ │ │ │ ├── Translation.qml │ │ │ │ ├── TrayService.qml │ │ │ │ ├── Updates.qml │ │ │ │ ├── Wallpapers.qml │ │ │ │ ├── Weather.qml │ │ │ │ ├── Ydotool.qml │ │ │ │ ├── ai/ │ │ │ │ │ ├── AiMessageData.qml │ │ │ │ │ ├── AiModel.qml │ │ │ │ │ ├── ApiStrategy.qml │ │ │ │ │ ├── GeminiApiStrategy.qml │ │ │ │ │ ├── MistralApiStrategy.qml │ │ │ │ │ └── OpenAiApiStrategy.qml │ │ │ │ ├── gCloud/ │ │ │ │ │ ├── token-from-key-venv.sh │ │ │ │ │ └── token_from_key.py │ │ │ │ ├── hyprlandAntiFlashbangShader/ │ │ │ │ │ └── anti-flashbang.glsl │ │ │ │ └── network/ │ │ │ │ └── WifiAccessPoint.qml │ │ │ ├── settings.qml │ │ │ ├── shell.qml │ │ │ ├── translations/ │ │ │ │ ├── de_DE.json │ │ │ │ ├── en_US.json │ │ │ │ ├── es_MX.json │ │ │ │ ├── fr_FR.json │ │ │ │ ├── he_HE.json │ │ │ │ ├── id_ID.json │ │ │ │ ├── it_IT.json │ │ │ │ ├── ja_JP.json │ │ │ │ ├── pt_BR.json │ │ │ │ ├── ru_RU.json │ │ │ │ ├── tools/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── guide/ │ │ │ │ │ │ ├── translation-tools-guide-zh_CN.md │ │ │ │ │ │ └── translation-tools-guide.md │ │ │ │ │ ├── manage-translations.sh │ │ │ │ │ ├── translation-cleaner.py │ │ │ │ │ └── translation-manager.py │ │ │ │ ├── tr_TR.json │ │ │ │ ├── uk_UA.json │ │ │ │ ├── vi_VN.json │ │ │ │ └── zh_CN.json │ │ │ └── welcome.qml │ │ ├── starship.toml │ │ ├── thorium-flags.conf │ │ ├── wlogout/ │ │ │ ├── layout │ │ │ └── style.css │ │ ├── xdg-desktop-portal/ │ │ │ └── hyprland-portals.conf │ │ └── zshrc.d/ │ │ ├── auto-Hypr.sh │ │ ├── dots-hyprland.zsh │ │ └── shortcuts.zsh │ └── .local/ │ └── share/ │ └── konsole/ │ └── Profile 1.profile ├── dots-extra/ │ ├── emacs/ │ │ └── material-theme.el │ ├── fcitx5/ │ │ └── conf/ │ │ └── classicui.conf │ ├── fedora/ │ │ └── hypr/ │ │ └── hyprland/ │ │ └── execs.conf │ ├── fontsets/ │ │ └── ar/ │ │ └── fonts.conf │ ├── swaylock/ │ │ └── config │ └── via-nix/ │ ├── README.md │ └── hypridle.conf ├── licenses/ │ ├── LGPL-3.0.txt │ ├── MIT.txt │ └── README.md ├── sdata/ │ ├── README.md │ ├── deps-info.md │ ├── dist-arch/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── illogical-impulse-audio/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-backlight/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-basic/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-bibata-modern-classic-bin/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-fonts-themes/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-hyprland/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-kde/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-microtex-git/ │ │ │ ├── .gitignore │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-portal/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-python/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-quickshell-git/ │ │ │ ├── .gitignore │ │ │ ├── PKGBUILD │ │ │ └── quickshell-check.hook │ │ ├── illogical-impulse-screencapture/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-toolkit/ │ │ │ └── PKGBUILD │ │ ├── illogical-impulse-widgets/ │ │ │ └── PKGBUILD │ │ ├── install-deps.sh │ │ ├── outdate-detect-mode │ │ ├── previous_dependencies.conf │ │ └── uninstall-deps.sh │ ├── dist-fedora/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── feddeps.toml │ │ ├── install-deps.sh │ │ ├── outdate-detect-mode │ │ └── uninstall-deps.sh │ ├── dist-gentoo/ │ │ ├── README.md │ │ ├── additional-useflags │ │ ├── hyprland-qtutils-private.patch │ │ ├── illogical-impulse-audio/ │ │ │ └── illogical-impulse-audio-1.0-r1.ebuild │ │ ├── illogical-impulse-backlight/ │ │ │ └── illogical-impulse-backlight-1.0-r1.ebuild │ │ ├── illogical-impulse-basic/ │ │ │ └── illogical-impulse-basic-1.0-r2.ebuild │ │ ├── illogical-impulse-bibata-modern-classic-bin/ │ │ │ └── illogical-impulse-bibata-modern-classic-bin-2.0.6-r1.ebuild │ │ ├── illogical-impulse-fonts-themes/ │ │ │ └── illogical-impulse-fonts-themes-1.0-r2.ebuild │ │ ├── illogical-impulse-hyprland/ │ │ │ └── illogical-impulse-hyprland-1.0-r3.ebuild │ │ ├── illogical-impulse-kde/ │ │ │ └── illogical-impulse-kde-1.0-r1.ebuild │ │ ├── illogical-impulse-microtex-git/ │ │ │ └── illogical-impulse-microtex-git-1.0-r1.ebuild │ │ ├── illogical-impulse-portal/ │ │ │ └── illogical-impulse-portal-1.0-r1.ebuild │ │ ├── illogical-impulse-python/ │ │ │ └── illogical-impulse-python-1.1-r1.ebuild │ │ ├── illogical-impulse-quickshell-git/ │ │ │ └── illogical-impulse-quickshell-git-0.1.0-r6.ebuild │ │ ├── illogical-impulse-screencapture/ │ │ │ └── illogical-impulse-screencapture-1.0-r1.ebuild │ │ ├── illogical-impulse-toolkit/ │ │ │ └── illogical-impulse-toolkit-1.0-r1.ebuild │ │ ├── illogical-impulse-widgets/ │ │ │ └── illogical-impulse-widgets-1.0-r5.ebuild │ │ ├── import-local-pkgs.sh │ │ ├── install-deps.sh │ │ ├── keywords │ │ ├── local-pkgs/ │ │ │ └── fonts-and-themes/ │ │ │ ├── breeze-plus-6.2.5-r1.ebuild │ │ │ ├── darkly-0.5.24-r1.ebuild │ │ │ ├── material-symbols-variable-9999.ebuild │ │ │ ├── readex-pro-1.0-r1.ebuild │ │ │ ├── rubik-vf-1.0-r1.ebuild │ │ │ └── space-grotesk-1.1.4-r1.ebuild │ │ ├── outdate-detect-mode │ │ ├── uninstall-deps.sh │ │ └── useflags │ ├── dist-nix/ │ │ ├── README.md │ │ ├── home-manager/ │ │ │ ├── flake.nix │ │ │ ├── home.nix │ │ │ └── quickshell.nix │ │ ├── install-deps.sh │ │ └── outdate-detect-mode │ ├── lib/ │ │ ├── dist-determine.sh │ │ ├── environment-variables.sh │ │ ├── functions.sh │ │ └── package-installers.sh │ ├── subcmd-checkdeps/ │ │ ├── 0.run.sh │ │ └── options.sh │ ├── subcmd-exp-merge/ │ │ ├── 0.run.sh │ │ └── options.sh │ ├── subcmd-exp-update/ │ │ ├── 0.run.sh │ │ ├── exp-update-tester.sh │ │ └── options.sh │ ├── subcmd-install/ │ │ ├── 0.greeting.sh │ │ ├── 1.deps-router.sh │ │ ├── 2.setups.sh │ │ ├── 3.files-exp.sh │ │ ├── 3.files-exp.yaml │ │ ├── 3.files-legacy.sh │ │ ├── 3.files.sh │ │ └── options.sh │ ├── subcmd-resetfirstrun/ │ │ ├── 0.run.sh │ │ └── options.sh │ ├── subcmd-uninstall/ │ │ ├── 0.run.sh │ │ └── options.sh │ ├── subcmd-virtmon/ │ │ ├── 0.run.sh │ │ ├── hypr_mon_guard │ │ └── options.sh │ └── uv/ │ ├── README.md │ ├── requirements.in │ ├── requirements.txt │ └── shell.nix └── setup ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing - Please, please, please, make multiple PRs if you have many features/fixes, and don't shove your personal changes along with the PR, including changed defaults - We can accept features that we do not personally want, but in that case we will ask you to make it configurable/optionally loaded. - If you want to start working on something _big_ to contribute, it might be a good idea to ask first to not waste your effort (but if you've already done it for yourself, it doesn't hurt to submit). # Translations See `dots/.config/quickshell/ii/translations/tools` # Code ## Dynamic loading - If something's not always necessary, especially when guarded by a config option to enable/disable, put it in a `Loader` - Note that you will need to declare positioning properties (like `anchors`) in the `Loader`, not the `sourceComponent` - When something that's to be dynamically loaded doesn't affect its parent layout, you can have a fading animation by using FadeLoader and set the `shown` prop instead of `active` and `visible` ## Practical concerns - Make sure what you add does not require significant resources for a minor purpose or harm usability just for the sake of looking nice. The dotfiles must remain practical for daily driving. - If there is something really fancy and impractical anyway, add a config option for it and make sure it's disabled by default (example: constantly rotating background clock) ## Style - Spaces - Space properties and children data into meaningful groups. (but of course, don't use 2+ blanks in a row) - Put spaces between text and operators: `if (condition) { ... } else { ... }` instead of `if(condition){ ... }else{ ... }` - As you can see, it's pretty easy to use lots of nesting. There's no hard limit, end-4 himself nests a lot too, but avoid/mitigate that: - Prefer early return: Use something like `if (!condition) return; doStuff();` instead of `if (condition) { doStuff() }` - If you feel it's a bother to refractor something into a new file, remember there's `component` to declare reusable components in the same file. # Setting up The following instruction assumes that you have an Arch(-based) Linux system. ## Complete _Might not be necessary depending on what you change, but this is recommended._ - [Install](https://ii.clsty.link/en/ii-qs/01setup/) the dotfiles (if you don't wanna replace your stuff completely, do it on a new user). - Make changes, copy changes to a fork, create PR. ## Partially working shell _Most stuff in the shell will work but not everything._ - Install Hyprland and the development version of Quickshell (`yay -S hyprland quickshell-git`). - Copy `dots/.config/quickshell` folder to your home directory. ## Extra setup for Quickshell - Quickshell-specific LSP setup: Run `touch ~/.config/quickshell/ii/.qmlls.ini` for proper LSP support. - Hint for VSCode: Get the official "Qt Qml" extension, go to its settings and change custom exe path to `/usr/bin/qmlls6`. ## Python If your changes involves using python package or script, please use the virtual environment created by uv as described in `sdata/uv/README.md`. # Running - Launch Hyprland (not the "uwsm-managed" one) - For the shell: - Open `~/.config/quickshell/ii` in your code editor. - In a terminal run `pkill qs; qs -c ii` to start the shell in the terminal (for logs). - Make edits in the opened folder. Changes are reloaded live. ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: end-4 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username thanks_dev: # Replace with a single thanks.dev username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/ISSUE_TEMPLATE/1-issue.yml ================================================ name: Issue description: for reporting any issue labels: ["ISSUE"] body: - type: markdown attributes: value: "**Welcome to submit a new issue!**\n- Please search in [existing issues](https://github.com/end-4/dots-hyprland/issues?q=is%3Aissue) before continue.\n- It takes only 3 steps, so please be patient :)\n- NOTE 1: If your issue is not a feature request, and it does not fit into the following form, for example \"how can I edit some widget\", please use [Discussions](https://github.com/end-4/dots-hyprland/discussions) instead.\n- NOTE 2: If your problem is distro specific and you do not use Arch(-based) distros, please submit [Discussion at Extra Distros](https://github.com/end-4/dots-hyprland/discussions/new?category=extra-distros) instead." - type: checkboxes attributes: label: "Step 1. Before you submit" description: "Hint: The 2nd and 3rd checkbox is **not** forcely required as you may have failed to do so." options: - label: I have read the [Troubleshooting](https://ii.clsty.link/en/ii-qs/04troubleshooting/) and [Usage](https://ii.clsty.link/en/ii-qs/02usage/) pages. required: true - label: I've successfully updated to the latest version following the [guidance](https://ii.clsty.link/en/ii-qs/01setup/#updating). required: false # Not required cuz user may have failed to do so - label: I've successfully updated the system packages to the latest. required: false # Not required cuz user may have failed to do so - label: I've ticked the checkboxes without reading their contents required: false # Obviously # TODO: Use GitHub Action to auto add folding tag if the log contains more than 15 lines, instead of tell user to "paste here" cuz many users actually does not know its meaning (It's also not convenient anyway). - type: textarea attributes: label: "Step 2. Quick diagnose info" description: "Run `./diagnose` inside the repo, and paste the result (which is also saved as file `./diagnose.result`) below." value: "
Quick diagnose\n\n```\n\n```\n\n
" validations: required: true - type: markdown attributes: value: | **Tips for the following Step 3** 1. Use `LANG=C LC_ALL=C` to get the output of a command in English, eg. `LANG=C LC_ALL=C date` displays time in English. 2. If it throws errors, **PLEASE**, attach logs and describe in detail if possible. - Bar and widgets not showing? run `pkill qs; qs -c ii` for logs. - Installation failed? Run installation again for logs. - You may use more code blocks when needed. 3. In case you are confused, the `
`, ``, ``, `
` are HTML tags for folding the logs (typically very long) inside. Please do not touch them (unless you know what you are doing). 4. If the logs are suuuuuuper long, consider using an online pastebin service instead. - type: textarea attributes: label: "Step 3. Describe the issue" value: "\n\n\n
Logs\n\n```\n\n```\n\n
" validations: required: true - type: checkboxes attributes: label: Reminder options: - label: I agree that it's usually impossible for others to help me without my logs. required: true ================================================ FILE: .github/ISSUE_TEMPLATE/2-feature_request.yml ================================================ name: Feature request description: Suggest an idea for this project labels: ["FEATURE"] body: - type: markdown attributes: value: "NOTE:\n- Please search in [existing issues](https://github.com/end-4/dots-hyprland/issues?q=is%3Aissue) before continue.\n- Please write in **English**." - type: textarea attributes: label: "What would you like to be added?" description: "Can be a suggestion for an existing feature. You can suggest a widget, minor user interaction changes.. whatever." - type: textarea attributes: label: "How will it help?" description: "It's helpful to include examples (like in your use case)." - type: textarea attributes: label: "Extra info" description: "If you want a new widget, a pic of the inspiration (if available) would be awesome." ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/README.md ================================================

【 end_4's Hyprland dotfiles 】

![](https://img.shields.io/github/last-commit/end-4/dots-hyprland?&style=for-the-badge&color=8ad7eb&logo=git&logoColor=D9E0EE&labelColor=1E202B) ![](https://img.shields.io/github/stars/end-4/dots-hyprland?style=for-the-badge&logo=andela&color=86dbd7&logoColor=D9E0EE&labelColor=1E202B) ![](https://img.shields.io/github/repo-size/end-4/dots-hyprland?color=86dbce&label=SIZE&logo=protondrive&style=for-the-badge&logoColor=D9E0EE&labelColor=1E202B) Dynamic JSON Badge

• overview •

> [!WARNING] > Hyprland 0.55 update: > If your distro has not shipped Hyprland 0.55 and/or you're not ready for it, you should switch to the Pre-Hyprland Luaification release (or not update yet, if you're going to do that). See the wiki for more info: [Install](https://ii.clsty.link/en/ii-qs/01setup/#automated-installation) | [Update](https://ii.clsty.link/en/ii-qs/01setup/#updating)
What this is/isn't - Technically, configuration files - Realistically, mostly the custom graphical shell - NOT a system setup script: no graphic drivers, no zram setup, etc.
Notable features - **Overview**: Shows open apps with live previews - **AI**: Gemini, Ollama, and more - **QoL**: screen translation, anti-flashbang, Google Lens - **Material themes**: Choose your wallpaper, done, enjoy - **Transparent installation**: Every command is shown before it's run
Installation - **IMPORTANT: Hyprland 0.55 Update**: If your distro has not shipped Hyprland 0.55 and/or you're not ready for it, you should switch to the Pre-Hyprland Luaification release. See [the wiki](https://ii.clsty.link/en/ii-qs/01setup/) for more info - Just run `bash <(curl -s https://ii.clsty.link/get)` - Or, clone this repo and run `./setup install` - See [the wiki](https://ii.clsty.link/en/ii-qs/01setup/) for more details - **Keybinds**: Should be somewhat familiar to Windows or GNOME users. Important ones: - `Super`+`/` = keybind list - `Super`+`Enter` = terminal
Software overview | Software | Purpose | | ------------- | ------------- | | [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (manages and renders windows) | | [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, used for the status bar, sidebars, etc. | | Others | See [deps-info.md](https://github.com/end-4/dots-hyprland/blob/main/sdata/deps-info.md) |
Discord Server link | I hope this provides a friendlier environment for support without needing me to personally accept every friend request/DM. For real issues, prefer GitHub

• screenshots •

illogical-impulse logo
Widget system: Quickshell | Support: Yes [Showcase video](https://www.youtube.com/watch?v=RPwovTInagE) | AI, settings app | Some widgets | |:---|:---------------| | image | image | | Window management | wow look its orange | | image | image |

• thank you •

- [@clsty](https://github.com/clsty) for making the dotfiles accessible by taking care of the install script and many other things - [@midn8hustlr](https://github.com/midn8hustlr) for greatly improving the color generation system - [@outfoxxed](https://github.com/outfoxxed/) for being extremely supportive in my Quickshell journey - Quickshell: [Soramane](https://github.com/caelestia-dots/shell/), [FridayFaerie](https://github.com/FridayFaerie/quickshell), [nydragon](https://github.com/nydragon/nysh) - AGS: [Aylur](https://github.com/Aylur/dotfiles/tree/ags-pre-ts), [kotontrion](https://github.com/kotontrion/dotfiles) - EWW: [fufexan](https://github.com/fufexan/dotfiles)

• stonks •

- I promise not to attempt an +ULTRARICOSHOT irl... Coins can go here: https://github.com/sponsors/end-4 - Tentacle cat hub twinkle internet points [![Stargazers over time](https://starchart.cc/end-4/dots-hyprland.svg?variant=adaptive)](https://starchart.cc/end-4/dots-hyprland) ---

• previous styles •

- **Unsupported!** - **Source**: illogical-impulse AGS in `ii-ags` branch, others in `archive` branch. - List is in reverse chronological order ### illogical-impulse (AGS) Widget system: AGS | Support: No | AI | Common widgets | |:---|:---------------| | ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) | | Window management | Weeb power | | ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) | #### m3ww Widget system: EWW | Support: No Material Eww! #### NovelKnock Widget system: EWW | Support: No Desktop Preview #### Hybrid Widget system: EWW | Support: No click the circles! #### Windoes Widget system: EWW | Support: No Desktop Preview

• inspirations/copying •

- Inspiration: osu!lazer (Hybrid), Windows 11 (Windoes), AvdanOS (NovelKnock), Material Design 3 (m3ww & later) - Copying: Absolutely, feel free. Just follow the license and it's all good ================================================ FILE: .github/pull_request_template.md ================================================ ## Describe your changes ## Is it ready? Questions/feedback needed? ================================================ FILE: .github/workflows/auto-close-issue.yml ================================================ on: issues: types: [opened] name: Close issues when the "ticked without reading" checkbox is checked permissions: issues: write jobs: detect-and-close: runs-on: ubuntu-latest steps: - name: Detect checked "ticked without reading" checkbox, comment, close and lock env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} OWNER: ${{ github.repository_owner }} REPO: ${{ github.event.repository.name }} ISSUE_NUMBER: ${{ github.event.issue.number }} ISSUE_BODY: ${{ toJson(github.event.issue.body) }} ISSUE_USER: ${{ github.event.issue.user.login }} run: | set -euo pipefail # Normalize the JSON-encoded body into plain text BODY=$(printf '%s' "$ISSUE_BODY" | sed -E 's/^"(.*)"$/\1/' | sed 's/\\"/"/g' | sed 's/\\n/\n/g') echo "Checking issue #${ISSUE_NUMBER} for the target checked checkbox..." # Use -- to stop option parsing so the leading - in the pattern isn't treated as an option if printf '%s' "$BODY" | grep -Fiq -- "- [x] I've ticked the checkboxes without reading their contents"; then echo "Target checkbox is checked. Proceeding to comment, close and lock the issue." # --- Get issue node id via GraphQL --- QUERY='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { id } } }' GET_ID_PAYLOAD=$(jq -n --arg q "$QUERY" --arg owner "$OWNER" --arg name "$REPO" --argjson number "$ISSUE_NUMBER" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}') echo "GraphQL: fetching issue node id..." RES=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$GET_ID_PAYLOAD" https://api.github.com/graphql) echo "GraphQL response (get id):" printf '%s\n' "$RES" ISSUE_ID=$(printf '%s' "$RES" | jq -r '.data.repository.issue.id // empty') if [ -z "$ISSUE_ID" ]; then echo "Failed to get issue id from GraphQL response. Aborting." exit 1 fi echo "Issue node id: $ISSUE_ID" # --- Post a comment to the issue --- COMMENT_BODY="Hi @${ISSUE_USER} — I noticed you checked \"I've ticked the checkboxes without reading their contents\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now. If you create a new issue with the required information, we can re-evaluate. Thank you!" MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }' ADD_COMMENT_PAYLOAD=$(jq -n --arg q "$MUT_ADD_COMMENT" --arg id "$ISSUE_ID" --arg body "$COMMENT_BODY" '{query:$q, variables:{id:$id, body:$body}}') echo "GraphQL: adding comment..." RES_COMMENT=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$ADD_COMMENT_PAYLOAD" https://api.github.com/graphql) echo "GraphQL response (add comment):" printf '%s\n' "$RES_COMMENT" ERR_COMMENT=$(printf '%s' "$RES_COMMENT" | jq -r '.errors[]?.message // empty') if [ -n "$ERR_COMMENT" ]; then echo "addComment error: $ERR_COMMENT" exit 1 fi echo "Comment posted." # --- Attempt to close via GraphQL updateIssue --- MUT_UPDATE_ISSUE='mutation($id: ID!) { updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { issue { number, state, stateReason } } }' UPDATE_PAYLOAD=$(jq -n --arg q "$MUT_UPDATE_ISSUE" --arg id "$ISSUE_ID" '{query:$q, variables:{id:$id}}') echo "GraphQL: updating issue (close with NOT_PLANNED)..." RES_UPDATE=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$UPDATE_PAYLOAD" https://api.github.com/graphql) echo "GraphQL response (update issue):" printf '%s\n' "$RES_UPDATE" ERR_UPDATE=$(printf '%s' "$RES_UPDATE" | jq -r '.errors[]?.message // empty') UPDATED_STATE=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.state // empty') UPDATED_REASON=$(printf '%s' "$RES_UPDATE" | jq -r '.data.updateIssue.issue.stateReason // empty') CLOSED_OK=false if [ -n "$ERR_UPDATE" ]; then echo "GraphQL updateIssue returned errors: $ERR_UPDATE" fi if [ "$UPDATED_STATE" = "CLOSED" ]; then echo "Issue closed via GraphQL: state=$UPDATED_STATE, stateReason=$UPDATED_REASON" CLOSED_OK=true else echo "GraphQL update did not confirm the issue is closed. Falling back to REST API PATCH to ensure the issue is closed." # REST fallback to close the issue with state_reason "not_planned" REST_PAYLOAD=$(jq -n --arg state "closed" --arg sr "not_planned" '{state:$state, state_reason:$sr}') echo "REST: PATCH /repos/$OWNER/$REPO/issues/$ISSUE_NUMBER payload: $REST_PAYLOAD" RES_REST=$(curl -s -w "\n%{http_code}" -X PATCH \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ -H "Content-Type: application/json" \ -d "$REST_PAYLOAD" \ "https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER") HTTP_STATUS=$(printf '%s' "$RES_REST" | tail -n1) RESP_BODY=$(printf '%s' "$RES_REST" | sed '$d') echo "REST response body:" printf '%s\n' "$RESP_BODY" echo "REST HTTP status: $HTTP_STATUS" if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then CLOSED_STATE=$(printf '%s' "$RESP_BODY" | jq -r '.state // empty') CLOSED_REASON=$(printf '%s' "$RESP_BODY" | jq -r '.state_reason // empty') echo "Issue closed via REST: state=$CLOSED_STATE, state_reason=$CLOSED_REASON" if [ "$CLOSED_STATE" = "closed" ]; then CLOSED_OK=true fi else echo "REST fallback failed to close the issue. See REST response above." exit 1 fi fi # --- Attempt to lock the conversation (GraphQL first, then REST fallback) --- if [ "$CLOSED_OK" = "true" ]; then echo "Attempting to lock the conversation via GraphQL with reason NO_REASON..." MUT_LOCK='mutation($id: ID!, $reason: LockReason) { lockLockable(input:{lockableId:$id, lockReason:$reason}) { clientMutationId } }' LOCK_PAYLOAD=$(jq -n --arg q "$MUT_LOCK" --arg id "$ISSUE_ID" --arg reason "NO_REASON" '{query:$q, variables:{id:$id, reason:$reason}}') RES_LOCK=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" -H "Content-Type: application/json" -d "$LOCK_PAYLOAD" https://api.github.com/graphql) echo "GraphQL response (lock):" printf '%s\n' "$RES_LOCK" LOCK_ERR=$(printf '%s' "$RES_LOCK" | jq -r '.errors[]?.message // empty') if [ -n "$LOCK_ERR" ]; then echo "GraphQL lockLockable returned errors: $LOCK_ERR" echo "Falling back to REST API to lock the conversation (no explicit reason)." # REST fallback to lock the issue (no lock_reason to indicate "no reason") RES_REST_LOCK=$(curl -s -w "\n%{http_code}" -X PUT \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/lock" -d '{}') HTTP_STATUS_LOCK=$(printf '%s' "$RES_REST_LOCK" | tail -n1) RESP_BODY_LOCK=$(printf '%s' "$RES_REST_LOCK" | sed '$d') echo "REST lock response body:" printf '%s\n' "$RESP_BODY_LOCK" echo "REST lock HTTP status: $HTTP_STATUS_LOCK" if [ "$HTTP_STATUS_LOCK" -ge 200 ] && [ "$HTTP_STATUS_LOCK" -lt 300 ]; then echo "Issue conversation locked via REST (no explicit reason)." else echo "REST fallback failed to lock the conversation. See REST response above." exit 1 fi else echo "Lock via GraphQL succeeded (or returned no errors)." fi else echo "Issue was not successfully closed; skipping lock." fi else echo "Checkbox not present/checked. Nothing to do." fi ================================================ FILE: .github/workflows/dist-update-notification.yml ================================================ name: Comment on Discussion When sdata/dist-arch/ Changes on: push: branches: - main paths: - "sdata/dist-arch/**" - "!sdata/dist-arch/README.md" # workflow_dispatch: jobs: comment_on_discussion: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' && github.repository == 'end-4/dots-hyprland' steps: - name: Create comment on discussion #2140 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} DISCUSSION_NUMBER: 2140 # https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions # https://docs.github.com/en/graphql/reference/mutations#adddiscussioncomment run: | MESSAGE="**Auto notification:**\n" MESSAGE+="Directory \`sdata/dist-arch\` has been updated.\n" MESSAGE+="Commit HASH: ${{ github.sha }}\n" MESSAGE+="Commit message: ${{ github.event.head_commit.message }}" REPO_OWNER="${{ github.repository_owner }}" REPO_NAME="${{ github.event.repository.name }}" DISCUSSION_NODE_ID=$(gh api graphql -f query=' query { repository( owner: "'${REPO_OWNER}'", name: "'${REPO_NAME}'" ) { discussion(number: '${DISCUSSION_NUMBER}') { id } } }' | \ jq -r '.data.repository.discussion.id') gh api graphql -f query=' mutation { addDiscussionComment(input:{ discussionId: "'$DISCUSSION_NODE_ID'", body: "'"$MESSAGE"'", }) { clientMutationId comment { id body } } } ' ================================================ FILE: .github/workflows/dump-github-context.yml ================================================ name: Dump github context on: workflow_dispatch: jobs: dump_github_context: runs-on: ubuntu-latest steps: - name: Dump github context run: echo "$GITHUB_CONTEXT" shell: bash env: GITHUB_CONTEXT: ${{ toJson(github) }} ================================================ FILE: .github/workflows/moderator.yml ================================================ name: AI Moderator on: issues: types: [opened] issue_comment: types: [created] pull_request_review_comment: types: [created] jobs: spam-detection: runs-on: ubuntu-latest permissions: issues: write pull-requests: write models: read contents: read steps: - uses: actions/checkout@v4 - uses: github/ai-moderator@v1 with: token: ${{ secrets.GITHUB_TOKEN }} spam-label: 'spam' ai-label: 'ai-generated' minimize-detected-comments: true # Built-in prompt configuration (all enabled by default) enable-spam-detection: true enable-link-spam-detection: true enable-ai-detection: true # custom-prompt-path: '.github/prompts/my-custom.prompt.yml' # Optional ================================================ FILE: .gitignore ================================================ /diagnose.result /cache # Ignore Python cache files __pycache__/ *.py[cod] /dots/.config/quickshell/ii/.qmlls.ini # exp-update /.update-lock # custom os-release /os-release # Emacs auto backup file *~ ================================================ FILE: .gitmodules ================================================ [submodule "dots/.config/quickshell/ii/modules/common/widgets/shapes"] path = dots/.config/quickshell/ii/modules/common/widgets/shapes url = https://github.com/end-4/rounded-polygon-qmljs.git ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: diagnose ================================================ #!/usr/bin/env bash # # This script is for quickly generate helpful info # # It should be as independent as possible and should not source other files unless it has to # # TODO: How to get the Qt version which Quickshell was built against? STY_RED='\e[31m' STY_RST='\e[00m' cd "$(dirname "$0")";export base="$(pwd)" output_file=diagnose.result;rm $output_file export LANG=C;export LC_ALL=C case $(whoami) in root)echo -e "${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}";exit 1;; esac x() { _exec "$@" 2>&1 | tee -a $output_file ; } e() { _box "$@" | tee -a $output_file ; } _box() { length=$(echo "$1" | wc -L);total_width=$((length + 2)) #line=$(printf "═%.0s" $(seq 1 $total_width)) #border_up="╔${line}╗";border_down="╚${line}╝" #border_vertical="║" line=$(printf "=%.0s" $(seq 1 $total_width)) border_up="/${line}\\";border_down="\\${line}/" border_vertical="|" echo -e "\n$border_up" echo "$border_vertical $1 $border_vertical" echo "$border_down" } _exec() { printf "\n[===diagnose===] $*\n" "$@" err=$?;if [ ! $err -eq 0 ];then echo "[---EXIT $err---]";else echo "[---SUCCESS---]";fi } _check_distro_id() { OS_RELEASE_FILE=/etc/os-release if [[ -f "$OS_RELEASE_FILE" ]]; then OS_DISTRO_ID=$(awk -F'=' '/^ID=/ { gsub(/["\x27]/,"",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null) OS_DISTRO_ID_LIKE=$(awk -F'=' '/^ID_LIKE=/ { gsub(/["\x27]/,"",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null) echo "distro ID: $OS_DISTRO_ID" echo "distro ID_LIKE: $OS_DISTRO_ID_LIKE" else echo "$OS_RELEASE_FILE does not exist." fi } _check_distro() { lsb_release -a || cat /etc/os-release || cat /etc/lsb-release } _check_venv() { source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate which python deactivate } _check_quickshell_version() { pacman -Q | grep -E 'quickshell|qt6-base' } _check_PKGBUILD_version() { pacman -Q | grep '^illogical-impulse-' } e "Checking git repo info" x git remote get-url origin x git rev-parse HEAD x git status x git submodule status --recursive e "Checking distro" x _check_distro_id x cat os-release #x _check_distro e "Checking variables" x declare -p XDG_CACHE_HOME # ~/.cache x declare -p XDG_CONFIG_HOME # ~/.config x declare -p XDG_DATA_HOME # ~/.local/share x declare -p XDG_STATE_HOME # ~/.local/state x declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/quickshell/.venv e "Checking directories/files" x ls -l ~/.local/state/quickshell/.venv #e "Checking command existence" #commands=(yay pacman zypper apt dnf yum) #commands+=(ags agsv1) #commands+=(Hyprland hypr{ctl,idle,lock,picker}) #commands+=(uv) #for i in "${commands[@]}";do x command -v $i;done e "Checking versions" x Hyprland --version x _check_quickshell_version x _check_PKGBUILD_version e "Finished. Output saved as \"$output_file\"." if ! command -v curl 2>&1 >>/dev/null ;then echo "\"curl\" not found, pastebin upload unavailable.";exit;fi echo "(Optional) Do you agree to upload the file \"$output_file\" to the online pastebin (https://0x0.st)?" echo "Notes:" echo "1. It is a public service and the logfile will be expired in 15 days." echo "2. You should have a look at the content of \"$output_file\" before agreeing to upload it." echo "3. Only agree when necessary, typically when you are creating an issue and not able to upload the \"diagnose.result\" file there or copy-paste the output directly." read -p "y=yes, n=no (default) ====> " p case $p in [yY]) echo "OK, uploading..." curl -F'file=@diagnose.result' -Fexpires=360 https://0x0.st && \ echo "Uploaded. Please attach the URL above when asking for help." ;; *) echo "Uploading aborted.";; esac ================================================ FILE: dots/.config/Kvantum/Colloid/Colloid.kvconfig ================================================ [%General] author=Vince Liuice, based on KvAdapta by Tsu Jan comment=An uncomplicated theme inspired by the Materia GTK theme x11drag=none alt_mnemonic=true left_tabs=false attach_active_tab=false mirror_doc_tabs=true group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 splitter_width=1 scroll_width=9 scroll_arrows=false scroll_min_extent=60 slider_width=2 slider_handle_width=23 slider_handle_length=22 tickless_slider_handle_size=22 center_toolbar_handle=true check_size=24 textless_progressbar=false progressbar_thickness=2 menubar_mouse_tracking=true toolbutton_style=1 double_click=false translucent_windows=false blurring=false popup_blurring=false vertical_spin_indicators=false spin_button_width=24 fill_rubberband=false merge_menubar_with_toolbar=true small_icon_size=16 large_icon_size=32 button_icon_size=16 toolbar_icon_size=16 combo_as_lineedit=true animate_states=true button_contents_shift=false combo_menu=true hide_combo_checkboxes=true combo_focus_rect=false groupbox_top_label=true inline_spin_indicators=true joined_inactive_tabs=false layout_spacing=3 layout_margin=3 scrollbar_in_view=true transient_scrollbar=true transient_groove=false submenu_overlap=0 tooltip_delay=0 tree_branch_line=false no_window_pattern=false opaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,trojita,dragon,digikam reduce_window_opacity=0 respect_DE=true scrollable_menu=false submenu_delay=150 no_inactiveness=false reduce_menu_opacity=0 click_behavior=2 contrast=1.00 dialog_button_layout=0 intensity=1.00 saturation=1.00 shadowless_popup=false drag_from_buttons=false menu_blur_radius=0 tooltip_blur_radius=0 [GeneralColors] window.color=#F5F5F5 base.color=#ffffff alt.base.color=#f8f8f8 button.color=#f2f2f2 light.color=#ffffff mid.light.color=#f0f0f0 dark.color=#c8c8c8 mid.color=#e1e1e1 highlight.color=#3c84f7 inactive.highlight.color=#3c84f7 text.color=#444444 window.text.color=#444444 button.text.color=#444444 disabled.text.color=#44444474 tooltip.text.color=#444444 highlight.text.color=#333333 link.color=#0057AE link.visited.color=#E040FB progress.indicator.text.color=#444444 [Hacks] transparent_ktitle_label=true transparent_dolphin_view=true transparent_pcmanfm_sidepane=true blur_translucent=false transparent_menutitle=true respect_darkness=true kcapacitybar_as_progressbar=true force_size_grip=true iconless_pushbutton=false iconless_menu=false disabled_icon_opacity=100 lxqtmainmenu_iconsize=16 normal_default_pushbutton=true single_top_toolbar=true tint_on_mouseover=0 transparent_pcmanfm_view=true no_selection_tint=true transparent_arrow_button=true middle_click_scroll=false opaque_colors=false kinetic_scrolling=false scroll_jump_workaround=true centered_forms=false noninteger_translucency=false style_vertical_toolbars=false blur_only_active_window=true [PanelButtonCommand] frame=true frame.element=button frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true interior.element=button indicator.size=8 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=white highlight.text.color=white text.shadow=0 text.margin=4 text.iconspacing=4 indicator.element=arrow frame.expansion=0 [PanelButtonTool] inherits=PanelButtonCommand text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=white text.disabled.color=#44444474 text.bold=false indicator.element=arrow indicator.size=8 frame.expansion=0 [ToolbarButton] frame=true frame.element=tbutton interior.element=tbutton frame.top=14 frame.bottom=14 frame.left=14 frame.right=14 indicator.element=tarrow text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 text.disabled.color=#44444474 text.bold=false frame.expansion=28 [Dock] inherits=PanelButtonCommand interior.element=dock frame.element=dock frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#444444 [DockTitle] inherits=PanelButtonCommand frame=false interior=false text.normal.color=#444444 text.focus.color=#444444 text.bold=false [IndicatorSpinBox] inherits=PanelButtonCommand frame=true interior=true frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 indicator.element=spin indicator.size=8 text.normal.color=#444444 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [RadioButton] inherits=PanelButtonCommand frame=false interior.element=radio text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 min_width=+0.3font min_height=+0.3font [CheckBox] inherits=PanelButtonCommand frame=false interior.element=checkbox text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 min_width=+0.3font min_height=+0.3font [Focus] inherits=PanelButtonCommand frame=true frame.element=focus frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 frame.patternsize=14 [GenericFrame] inherits=PanelButtonCommand frame=true interior=false frame.element=common interior.element=common frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 [LineEdit] inherits=PanelButtonCommand frame.element=lineedit interior.element=lineedit frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [ToolbarLineEdit] frame.element=lineedit interior.element=lineedit [DropDownButton] inherits=PanelButtonCommand indicator.element=arrow-down [IndicatorArrow] indicator.element=arrow indicator.size=8 [ToolboxTab] inherits=PanelButtonCommand text.normal.color=#444444 text.press.color=#333333 text.focus.color=#444444 [Tab] inherits=PanelButtonCommand interior.element=tab text.margin.left=8 text.margin.right=8 text.margin.top=0 text.margin.bottom=0 frame.element=tab indicator.element=tab indicator.size=22 frame.top=8 frame.bottom=8 frame.left=8 frame.right=8 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 frame.expansion=0 text.bold=false [TabFrame] inherits=PanelButtonCommand frame.element=tabframe interior.element=tabframe frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 [TreeExpander] inherits=PanelButtonCommand indicator.size=8 indicator.element=tree [HeaderSection] inherits=PanelButtonCommand interior.element=header frame.element=header frame.top=0 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 frame.expansion=0 [SizeGrip] indicator.element=resize-grip [Toolbar] inherits=PanelButtonCommand indicator.element=toolbar indicator.size=5 text.margin=0 interior.element=menubar frame.element=menubar text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 frame.left=6 frame.right=6 frame.top=0 frame.bottom=1 frame.expansion=0 [Slider] inherits=PanelButtonCommand frame.element=slider focusFrame=true interior.element=slider frame.top=3 frame.bottom=3 frame.left=3 frame.right=3 [SliderCursor] inherits=PanelButtonCommand frame=false interior.element=slidercursor [Progressbar] inherits=PanelButtonCommand frame.element=progress interior.element=progress text.margin=0 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 text.bold=false frame.expansion=8 [ProgressbarContents] inherits=PanelButtonCommand frame=true frame.element=progress-pattern interior.element=progress-pattern [ItemView] inherits=PanelButtonCommand text.margin=0 frame.element=itemview interior.element=itemview frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.margin.top=0 text.margin.bottom=0 text.margin.left=8 text.margin.right=8 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 min_width=+0.3font min_height=+0.3font frame.expansion=0 [Splitter] interior.element=splitter frame=false indicator.size=0 [Scrollbar] inherits=PanelButtonCommand indicator.element=arrow indicator.size=12 [ScrollbarSlider] inherits=PanelButtonCommand frame.element=scrollbarslider interior=false frame.left=5 frame.right=5 frame.top=5 frame.bottom=5 indicator.element=grip indicator.size=12 [ScrollbarGroove] inherits=PanelButtonCommand interior=false frame=false [Menu] inherits=PanelButtonCommand frame.top=10 frame.bottom=10 frame.left=10 frame.right=10 frame.element=menu interior.element=menu text.normal.color=#444444 text.shadow=false frame.expansion=0 text.bold=false [MenuItem] inherits=PanelButtonCommand frame=true frame.element=menuitem interior.element=menuitem indicator.element=menuitem text.normal.color=#444444 text.focus.color=#333333 text.margin.top=0 text.margin.bottom=0 text.margin.left=6 text.margin.right=6 frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.bold=false frame.expansion=0 [MenuBar] inherits=PanelButtonCommand frame.element=menubar interior.element=menubar frame.bottom=0 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 frame.expansion=0 text.bold=false [MenuBarItem] inherits=PanelButtonCommand interior=true interior.element=menubaritem frame.element=menubaritem frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 text.margin.left=4 text.margin.right=4 text.margin.top=0 text.margin.bottom=0 text.normal.color=#444444 text.focus.color=#444444 text.press.color=#333333 text.toggle.color=#333333 text.bold=false min_width=+0.3font min_height=+0.3font frame.expansion=0 [TitleBar] inherits=PanelButtonCommand frame=false text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 interior.element=titlebar indicator.size=16 indicator.element=mdi text.normal.color=#444444 text.focus.color=#444444 text.bold=false text.italic=true frame.expansion=0 [ComboBox] inherits=PanelButtonCommand frame.element=combo interior.element=combo frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 text.normal.color=#444444 text.focus.color=#424242 text.press.color=#424242 text.toggle.color=#424242 [GroupBox] inherits=GenericFrame frame=false text.shadow=0 text.margin=0 text.normal.color=#444444 text.focus.color=#333333 text.bold=false frame.expansion=0 [TabBarFrame] inherits=GenericFrame frame=false interior=false [ToolTip] inherits=GenericFrame frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true text.shadow=0 text.margin=6 interior.element=tooltip frame.element=tooltip text.normal.color=#444444 text.focus.color=#333333 text.press.color=#333333 text.toggle.color=#333333 frame.expansion=6 [StatusBar] inherits=GenericFrame frame=false interior=false [Window] interior=true interior.element=window frame=true frame.element=window frame.bottom=10 frame.top=10 ================================================ FILE: dots/.config/Kvantum/Colloid/ColloidDark.kvconfig ================================================ [%General] author=Vince Liuice, based on KvAdapta by Tsu Jan comment=An uncomplicated theme inspired by the Materia GTK theme x11drag=none alt_mnemonic=true left_tabs=false attach_active_tab=false mirror_doc_tabs=true group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 splitter_width=1 scroll_width=9 scroll_arrows=false scroll_min_extent=60 slider_width=2 slider_handle_width=23 slider_handle_length=22 tickless_slider_handle_size=22 center_toolbar_handle=true check_size=24 textless_progressbar=false progressbar_thickness=2 menubar_mouse_tracking=true toolbutton_style=1 double_click=false translucent_windows=false blurring=false popup_blurring=false vertical_spin_indicators=false spin_button_width=24 fill_rubberband=false merge_menubar_with_toolbar=true small_icon_size=16 large_icon_size=32 button_icon_size=16 toolbar_icon_size=16 combo_as_lineedit=true animate_states=true button_contents_shift=false combo_menu=true hide_combo_checkboxes=true combo_focus_rect=false groupbox_top_label=true inline_spin_indicators=true joined_inactive_tabs=false layout_spacing=3 layout_margin=3 scrollbar_in_view=true transient_scrollbar=true transient_groove=false submenu_overlap=0 tooltip_delay=0 tree_branch_line=false no_window_pattern=false opaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,trojita,dragon,digikam reduce_window_opacity=0 respect_DE=true scrollable_menu=false submenu_delay=150 no_inactiveness=false reduce_menu_opacity=0 click_behavior=2 contrast=1.00 dialog_button_layout=0 intensity=1.00 saturation=1.00 shadowless_popup=false drag_from_buttons=false menu_blur_radius=0 tooltip_blur_radius=0 [GeneralColors] window.color=#2c2c2c base.color=#2c2c2c alt.base.color=#2e2e2e button.color=#4d4d4d light.color=#535353 mid.light.color=#474747 dark.color=#282828 mid.color=#323232 highlight.color=#5b9bf8 inactive.highlight.color=#5b9bf8 text.color=#dfdfdf window.text.color=#dfdfdf button.text.color=#dfdfdf disabled.text.color=#696969 tooltip.text.color=#efefef highlight.text.color=#ffffff link.color=#0057AE link.visited.color=#E040FB progress.indicator.text.color=#dfdfdf [Hacks] transparent_ktitle_label=true transparent_dolphin_view=true transparent_pcmanfm_sidepane=true blur_translucent=false transparent_menutitle=true respect_darkness=true kcapacitybar_as_progressbar=true force_size_grip=true iconless_pushbutton=false iconless_menu=false disabled_icon_opacity=100 lxqtmainmenu_iconsize=16 normal_default_pushbutton=true single_top_toolbar=true tint_on_mouseover=0 transparent_pcmanfm_view=true no_selection_tint=true transparent_arrow_button=true middle_click_scroll=false opaque_colors=false kinetic_scrolling=false scroll_jump_workaround=true centered_forms=false noninteger_translucency=false style_vertical_toolbars=false blur_only_active_window=true [PanelButtonCommand] frame=true frame.element=button frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true interior.element=button indicator.size=8 text.normal.color=#dfdfdf text.focus.color=white text.press.color=white text.toggle.color=#ffffff text.shadow=0 text.margin=4 text.iconspacing=4 indicator.element=arrow frame.expansion=0 [PanelButtonTool] inherits=PanelButtonCommand text.normal.color=#dfdfdf text.focus.color=white text.press.color=white text.toggle.color=#ffffff text.bold=false indicator.element=arrow indicator.size=8 frame.expansion=0 [ToolbarButton] frame=true frame.element=tbutton interior.element=tbutton frame.top=16 frame.bottom=16 frame.left=16 frame.right=16 indicator.element=tarrow text.normal.color=#dfdfdf text.focus.color=white text.press.color=white text.toggle.color=white text.bold=false frame.expansion=32 [Dock] inherits=PanelButtonCommand interior.element=dock frame.element=dock frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#dfdfdf [DockTitle] inherits=PanelButtonCommand frame=false interior=false text.normal.color=#dfdfdf text.focus.color=white text.bold=false [IndicatorSpinBox] inherits=PanelButtonCommand frame=true interior=true frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 indicator.element=spin indicator.size=8 text.normal.color=#dfdfdf text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [RadioButton] inherits=PanelButtonCommand frame=false interior.element=radio text.normal.color=#dfdfdf text.focus.color=white min_width=+0.3font min_height=+0.3font [CheckBox] inherits=PanelButtonCommand frame=false interior.element=checkbox text.normal.color=#dfdfdf text.focus.color=white min_width=+0.3font min_height=+0.3font [Focus] inherits=PanelButtonCommand frame=true frame.element=focus frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 frame.patternsize=14 [GenericFrame] inherits=PanelButtonCommand frame=true interior=false frame.element=common interior.element=common frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 [LineEdit] inherits=PanelButtonCommand frame.element=lineedit interior.element=lineedit frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [ToolbarLineEdit] frame.element=lineedit interior.element=lineedit [DropDownButton] inherits=PanelButtonCommand indicator.element=arrow-down [IndicatorArrow] indicator.element=arrow indicator.size=8 [ToolboxTab] inherits=PanelButtonCommand text.normal.color=#dfdfdf text.press.color=#dfdfdf text.focus.color=white [Tab] inherits=PanelButtonCommand interior.element=tab text.margin.left=8 text.margin.right=8 text.margin.top=0 text.margin.bottom=0 frame.element=tab indicator.element=tab indicator.size=22 frame.top=8 frame.bottom=8 frame.left=8 frame.right=8 text.normal.color=#dfdfdf text.focus.color=#dfdfdf text.press.color=white text.toggle.color=white frame.expansion=0 text.bold=false [TabFrame] inherits=PanelButtonCommand frame.element=tabframe interior.element=tabframe frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 [TreeExpander] inherits=PanelButtonCommand indicator.size=8 indicator.element=tree [HeaderSection] inherits=PanelButtonCommand interior.element=header frame.element=header frame.top=0 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#dfdfdf text.focus.color=white text.press.color=white text.toggle.color=white frame.expansion=0 [SizeGrip] indicator.element=resize-grip [Toolbar] inherits=PanelButtonCommand indicator.element=toolbar indicator.size=5 text.margin=0 interior.element=menubar frame.element=menubar text.normal.color=#dfdfdf text.focus.color=white text.press.color=#dfdfdf text.toggle.color=white frame.left=6 frame.right=6 frame.top=0 frame.bottom=1 frame.expansion=0 [Slider] inherits=PanelButtonCommand frame.element=slider focusFrame=true interior.element=slider frame.top=3 frame.bottom=3 frame.left=3 frame.right=3 [SliderCursor] inherits=PanelButtonCommand frame=false interior.element=slidercursor [Progressbar] inherits=PanelButtonCommand frame.element=progress interior.element=progress text.margin=0 text.normal.color=#dfdfdf text.focus.color=#dfdfdf text.press.color=#dfdfdf text.toggle.color=#dfdfdf text.bold=false frame.expansion=8 [ProgressbarContents] inherits=PanelButtonCommand frame=true frame.element=progress-pattern interior.element=progress-pattern [ItemView] inherits=PanelButtonCommand text.margin=0 frame.element=itemview interior.element=itemview frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.margin.top=0 text.margin.bottom=0 text.margin.left=8 text.margin.right=8 text.normal.color=#dfdfdf text.focus.color=#dfdfdf text.press.color=#ffffff text.toggle.color=#ffffff min_width=+0.3font min_height=+0.3font frame.expansion=0 [Splitter] interior.element=splitter frame=false indicator.size=0 [Scrollbar] inherits=PanelButtonCommand indicator.element=arrow indicator.size=12 [ScrollbarSlider] inherits=PanelButtonCommand frame.element=scrollbarslider interior=false frame.left=5 frame.right=5 frame.top=5 frame.bottom=5 indicator.element=grip indicator.size=12 [ScrollbarGroove] inherits=PanelButtonCommand interior=false frame=false [Menu] inherits=PanelButtonCommand frame.top=10 frame.bottom=10 frame.left=10 frame.right=10 frame.element=menu interior.element=menu text.normal.color=#dfdfdf text.shadow=false frame.expansion=0 text.bold=false [MenuItem] inherits=PanelButtonCommand frame=true frame.element=menuitem interior.element=menuitem indicator.element=menuitem text.normal.color=#dfdfdf text.focus.color=#ffffff text.margin.top=0 text.margin.bottom=0 text.margin.left=6 text.margin.right=6 frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.bold=false frame.expansion=0 [MenuBar] inherits=PanelButtonCommand frame.element=menubar interior.element=menubar frame.bottom=0 text.normal.color=#dfdfdf text.focus.color=#ffffff text.press.color=#ffffff text.toggle.color=#ffffff frame.expansion=0 text.bold=false [MenuBarItem] inherits=PanelButtonCommand interior=true interior.element=menubaritem frame.element=menubaritem frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 text.margin.left=4 text.margin.right=4 text.margin.top=0 text.margin.bottom=0 text.normal.color=#dfdfdf text.focus.color=#ffffff text.press.color=#ffffff text.toggle.color=#ffffff text.bold=false min_width=+0.3font min_height=+0.3font frame.expansion=0 [TitleBar] inherits=PanelButtonCommand frame=false text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 interior.element=titlebar indicator.size=16 indicator.element=mdi text.normal.color=#787878 text.focus.color=#dfdfdf text.bold=false text.italic=true frame.expansion=0 [ComboBox] inherits=PanelButtonCommand frame.element=combo interior.element=combo frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 text.focus.color=white text.press.color=#dfdfdf text.toggle.color=white [GroupBox] inherits=GenericFrame frame=false text.shadow=0 text.margin=0 text.normal.color=#dfdfdf text.focus.color=white text.bold=false frame.expansion=0 [TabBarFrame] inherits=GenericFrame frame=false frame.element=tabBarFrame interior=false frame.top=0 frame.bottom=0 frame.left=0 frame.right=0 [ToolTip] inherits=GenericFrame frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true text.shadow=0 text.margin=6 interior.element=tooltip frame.element=tooltip frame.expansion=6 [StatusBar] inherits=GenericFrame frame=false interior=false [Window] interior=true interior.element=window frame=true frame.element=window frame.bottom=10 frame.top=10 ================================================ FILE: dots/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig ================================================ [%General] author=Vince Liuice, based on KvAdapta by Tsu Jan comment=An uncomplicated theme inspired by the Materia GTK theme x11drag=none alt_mnemonic=true left_tabs=false attach_active_tab=false mirror_doc_tabs=true group_toolbar_buttons=false toolbar_item_spacing=0 toolbar_interior_spacing=2 spread_progressbar=true composite=true menu_shadow_depth=6 spread_menuitems=false tooltip_shadow_depth=7 splitter_width=1 scroll_width=9 scroll_arrows=false scroll_min_extent=60 slider_width=2 slider_handle_width=23 slider_handle_length=22 tickless_slider_handle_size=22 center_toolbar_handle=true check_size=24 textless_progressbar=false progressbar_thickness=2 menubar_mouse_tracking=true toolbutton_style=1 double_click=false translucent_windows=false blurring=false popup_blurring=false vertical_spin_indicators=false spin_button_width=24 fill_rubberband=false merge_menubar_with_toolbar=true small_icon_size=16 large_icon_size=32 button_icon_size=16 toolbar_icon_size=16 combo_as_lineedit=true animate_states=true button_contents_shift=false combo_menu=true hide_combo_checkboxes=true combo_focus_rect=false groupbox_top_label=true inline_spin_indicators=true joined_inactive_tabs=false layout_spacing=3 layout_margin=3 scrollbar_in_view=true transient_scrollbar=true transient_groove=false submenu_overlap=0 tooltip_delay=0 tree_branch_line=false no_window_pattern=false opaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,VirtualBoxVM,trojita,dragon,digikam,lyx reduce_window_opacity=0 respect_DE=true scrollable_menu=false submenu_delay=150 no_inactiveness=false reduce_menu_opacity=0 click_behavior=2 contrast=1.00 dialog_button_layout=0 intensity=1.00 saturation=1.00 shadowless_popup=false drag_from_buttons=false menu_blur_radius=0 tooltip_blur_radius=0 [GeneralColors] window.color=#0F1416 base.color=#0F1416 alt.base.color=#0F1416 button.color=#1B2022 light.color=#171C1E mid.light.color=#1B2022 dark.color=#303638 mid.color=#252B2D highlight.color=#84D2E7 inactive.highlight.color=#84D2E7 text.color=#DEE3E5 window.text.color=#DEE3E5 button.text.color=#DEE3E5 disabled.text.color=#DEE3E5 tooltip.text.color=#DEE3E5 highlight.text.color=#DEE3E5 link.color=#BFC4EB link.visited.color=#DDE1FF progress.indicator.text.color=#DEE3E5 [Hacks] transparent_ktitle_label=true transparent_dolphin_view=true transparent_pcmanfm_sidepane=true blur_translucent=false transparent_menutitle=true respect_darkness=true kcapacitybar_as_progressbar=true force_size_grip=true iconless_pushbutton=false iconless_menu=false disabled_icon_opacity=100 lxqtmainmenu_iconsize=16 normal_default_pushbutton=true single_top_toolbar=true tint_on_mouseover=0 transparent_pcmanfm_view=true no_selection_tint=true transparent_arrow_button=true middle_click_scroll=false opaque_colors=false kinetic_scrolling=false scroll_jump_workaround=true centered_forms=false noninteger_translucency=false style_vertical_toolbars=false blur_only_active_window=true [PanelButtonCommand] frame=true frame.element=button frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true interior.element=button indicator.size=8 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=white text.toggle.color=#ffffff text.shadow=0 text.margin=4 text.iconspacing=4 indicator.element=arrow frame.expansion=0 [PanelButtonTool] inherits=PanelButtonCommand text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=white text.toggle.color=#ffffff text.bold=false indicator.element=arrow indicator.size=8 frame.expansion=0 [ToolbarButton] frame=true frame.element=tbutton interior.element=tbutton frame.top=16 frame.bottom=16 frame.left=16 frame.right=16 indicator.element=tarrow text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=white text.toggle.color=white text.bold=false frame.expansion=32 [Dock] inherits=PanelButtonCommand interior.element=dock frame.element=dock frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#DEE3E5 [DockTitle] inherits=PanelButtonCommand frame=false interior=false text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.bold=false [IndicatorSpinBox] inherits=PanelButtonCommand frame=true interior=true frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 indicator.element=spin indicator.size=8 text.normal.color=#DEE3E5 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [RadioButton] inherits=PanelButtonCommand frame=false interior.element=radio text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 min_width=+0.3font min_height=+0.3font [CheckBox] inherits=PanelButtonCommand frame=false interior.element=checkbox text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 min_width=+0.3font min_height=+0.3font [Focus] inherits=PanelButtonCommand frame=true frame.element=focus frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 frame.patternsize=14 [GenericFrame] inherits=PanelButtonCommand frame=true interior=false frame.element=common interior.element=common frame.top=1 frame.bottom=1 frame.left=1 frame.right=1 [LineEdit] inherits=PanelButtonCommand frame.element=lineedit interior.element=lineedit frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 [ToolbarLineEdit] frame.element=lineedit interior.element=lineedit [DropDownButton] inherits=PanelButtonCommand indicator.element=arrow-down [IndicatorArrow] indicator.element=arrow indicator.size=8 [ToolboxTab] inherits=PanelButtonCommand text.normal.color=#DEE3E5 text.press.color=#dfdfdf text.focus.color=#DEE3E5 [Tab] inherits=PanelButtonCommand interior.element=tab text.margin.left=8 text.margin.right=8 text.margin.top=0 text.margin.bottom=0 frame.element=tab indicator.element=tab indicator.size=22 frame.top=8 frame.bottom=8 frame.left=8 frame.right=8 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=white text.toggle.color=white frame.expansion=0 text.bold=false [TabFrame] inherits=PanelButtonCommand frame.element=tabframe interior.element=tabframe frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 [TreeExpander] inherits=PanelButtonCommand indicator.size=8 indicator.element=tree [HeaderSection] inherits=PanelButtonCommand interior.element=header frame.element=header frame.top=0 frame.bottom=1 frame.left=1 frame.right=1 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=white text.toggle.color=white frame.expansion=0 [SizeGrip] indicator.element=resize-grip [Toolbar] inherits=PanelButtonCommand indicator.element=toolbar indicator.size=5 text.margin=0 interior.element=menubar frame.element=menubar text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=#dfdfdf text.toggle.color=white frame.left=6 frame.right=6 frame.top=0 frame.bottom=1 frame.expansion=0 [Slider] inherits=PanelButtonCommand frame.element=slider focusFrame=true interior.element=slider frame.top=3 frame.bottom=3 frame.left=3 frame.right=3 [SliderCursor] inherits=PanelButtonCommand frame=false interior.element=slidercursor [Progressbar] inherits=PanelButtonCommand frame.element=progress interior.element=progress text.margin=0 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=#dfdfdf text.toggle.color=#dfdfdf text.bold=false frame.expansion=8 [ProgressbarContents] inherits=PanelButtonCommand frame=true frame.element=progress-pattern interior.element=progress-pattern [ItemView] inherits=PanelButtonCommand text.margin=0 frame.element=itemview interior.element=itemview frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.margin.top=0 text.margin.bottom=0 text.margin.left=8 text.margin.right=8 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=#ffffff text.toggle.color=#ffffff min_width=+0.3font min_height=+0.3font frame.expansion=0 [Splitter] interior.element=splitter frame=false indicator.size=0 [Scrollbar] inherits=PanelButtonCommand indicator.element=arrow indicator.size=12 [ScrollbarSlider] inherits=PanelButtonCommand frame.element=scrollbarslider interior=false frame.left=5 frame.right=5 frame.top=5 frame.bottom=5 indicator.element=grip indicator.size=12 [ScrollbarGroove] inherits=PanelButtonCommand interior=false frame=false [Menu] inherits=PanelButtonCommand frame.top=10 frame.bottom=10 frame.left=10 frame.right=10 frame.element=menu interior.element=menu text.normal.color=#DEE3E5 text.shadow=false frame.expansion=0 text.bold=false [MenuItem] inherits=PanelButtonCommand frame=true frame.element=menuitem interior.element=menuitem indicator.element=menuitem text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.margin.top=0 text.margin.bottom=0 text.margin.left=6 text.margin.right=6 frame.top=4 frame.bottom=4 frame.left=4 frame.right=4 text.bold=false frame.expansion=0 [MenuBar] inherits=PanelButtonCommand frame.element=menubar interior.element=menubar frame.bottom=0 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=#ffffff text.toggle.color=#ffffff frame.expansion=0 text.bold=false [MenuBarItem] inherits=PanelButtonCommand interior=true interior.element=menubaritem frame.element=menubaritem frame.top=2 frame.bottom=2 frame.left=2 frame.right=2 text.margin.left=4 text.margin.right=4 text.margin.top=0 text.margin.bottom=0 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.press.color=#ffffff text.toggle.color=#ffffff text.bold=false min_width=+0.3font min_height=+0.3font frame.expansion=0 [TitleBar] inherits=PanelButtonCommand frame=false text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 interior.element=titlebar indicator.size=16 indicator.element=mdi text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.bold=false text.italic=true frame.expansion=0 [ComboBox] inherits=PanelButtonCommand frame.element=combo interior.element=combo frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 text.margin.top=2 text.margin.bottom=2 text.margin.left=2 text.margin.right=2 text.focus.color=#DEE3E5 text.press.color=#dfdfdf text.toggle.color=white [GroupBox] inherits=GenericFrame frame=false text.shadow=0 text.margin=0 text.normal.color=#DEE3E5 text.focus.color=#DEE3E5 text.bold=false frame.expansion=0 [TabBarFrame] inherits=GenericFrame frame=false frame.element=tabBarFrame interior=false frame.top=0 frame.bottom=0 frame.left=0 frame.right=0 [ToolTip] inherits=GenericFrame frame.top=6 frame.bottom=6 frame.left=6 frame.right=6 interior=true text.shadow=0 text.margin=6 interior.element=tooltip frame.element=tooltip frame.expansion=6 [StatusBar] inherits=GenericFrame frame=false interior=false [Window] interior=true interior.element=window frame=true frame.element=window frame.bottom=10 frame.top=10 text.disabled.color=#0F1416 ================================================ FILE: dots/.config/Kvantum/kvantum.kvconfig ================================================ [General] theme=MaterialAdw ================================================ FILE: dots/.config/chrome-flags.conf ================================================ --password-store=gnome-libsecret --ozone-platform-hint=wayland --gtk-version=4 --ignore-gpu-blocklist --enable-features=TouchpadOverscrollHistoryNavigation --enable-wayland-ime --disable-features=ExtensionManifestV2Unsupported ================================================ FILE: dots/.config/code-flags.conf ================================================ --ozone-platform-hint=wayland --gtk-version=4 --ignore-gpu-blocklist --enable-features=TouchpadOverscrollHistoryNavigation --enable-wayland-ime --password-store=gnome-libsecret ================================================ FILE: dots/.config/darklyrc ================================================ [Style] WidgetDrawShadow=false ================================================ FILE: dots/.config/dolphinrc ================================================ [DetailsMode] PreviewSize=32 [General] GlobalViewProps=false ShowFullPath=true ShowStatusBar=FullWidth Version=202 [KFileDialog Settings] Places Icons Auto-resize=false Places Icons Static Size=22 [MainWindow] MenuBar=Disabled [PreviewSettings] Plugins=ffmpegthumbnailer,appimagethumbnail,audiothumbnail,avif,blenderthumbnail,comicbookthumbnail,cursorthumbnail,djvuthumbnail,ebookthumbnail,exrthumbnail,directorythumbnail,fontthumbnail,heif,imagethumbnail,jpegthumbnail,jxl,kraorathumbnail,windowsexethumbnail,windowsimagethumbnail,mobithumbnail,opendocumentthumbnail,gsthumbnail,rawthumbnail,librsvg,svgthumbnail,ffmpegthumbs,gdk-pixbuf-thumbnailer,gsf-office ================================================ FILE: dots/.config/fish/auto-Hypr.fish ================================================ # Auto start Hyprland on tty1 if test -z "$DISPLAY" ;and test "$XDG_VTNR" -eq 1 mkdir -p ~/.cache exec start-hyprland > ~/.cache/hyprland.log 2>&1 end ================================================ FILE: dots/.config/fish/config.fish ================================================ # Commands to run in interactive sessions can go here if status is-interactive # No greeting set fish_greeting # Use starship function starship_transient_prompt_func starship module character end if test "$TERM" != "linux" starship init fish | source enable_transience end # Colors if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt end # Aliases # kitty doesn't clear properly so we need to do this weird printing alias clear "printf '\033[2J\033[3J\033[1;1H'" alias celar "printf '\033[2J\033[3J\033[1;1H'" alias claer "printf '\033[2J\033[3J\033[1;1H'" alias pamcan pacman alias q 'qs -c ii' if test "$TERM" != "linux" alias ls 'eza --icons' end if test "$TERM" = "xterm-kitty" alias ssh 'kitten ssh' end end ================================================ FILE: dots/.config/fish/fish_variables ================================================ # This file contains fish universal variable definitions. # VERSION: 3.0 SETUVAR __fish_initialized:3400 SETUVAR _fisher_jorgebucaran_2F_fisher_files:\x7e/\x2econfig/fish/functions/fisher\x2efish\x1e\x7e/\x2econfig/fish/completions/fisher\x2efish SETUVAR _fisher_plugins:jorgebucaran/fisher SETUVAR _fisher_upgraded_to_4_4:\x1d SETUVAR fish_color_autosuggestion:555\x1ebrblack SETUVAR fish_color_cancel:\x2dr SETUVAR fish_color_command:blue SETUVAR fish_color_comment:red SETUVAR fish_color_cwd:green SETUVAR fish_color_cwd_root:red SETUVAR fish_color_end:green SETUVAR fish_color_error:brred SETUVAR fish_color_escape:brcyan SETUVAR fish_color_history_current:\x2d\x2dbold SETUVAR fish_color_host:normal SETUVAR fish_color_host_remote:yellow SETUVAR fish_color_normal:normal SETUVAR fish_color_operator:brcyan SETUVAR fish_color_param:cyan SETUVAR fish_color_quote:yellow SETUVAR fish_color_redirection:cyan\x1e\x2d\x2dbold SETUVAR fish_color_search_match:\x2d\x2dbackground\x3d111 SETUVAR fish_color_selection:white\x1e\x2d\x2dbold\x1e\x2d\x2dbackground\x3dbrblack SETUVAR fish_color_status:red SETUVAR fish_color_user:brgreen SETUVAR fish_color_valid_path:\x2d\x2dunderline SETUVAR fish_key_bindings:fish_default_key_bindings SETUVAR fish_pager_color_completion:normal SETUVAR fish_pager_color_description:B3A06D\x1eyellow\x1e\x2di SETUVAR fish_pager_color_prefix:cyan\x1e\x2d\x2dbold\x1e\x2d\x2dunderline SETUVAR fish_pager_color_progress:brwhite\x1e\x2d\x2dbackground\x3dcyan SETUVAR fish_pager_color_selected_background:\x2dr ================================================ FILE: dots/.config/fontconfig/fonts.conf ================================================ none ================================================ FILE: dots/.config/foot/foot.ini ================================================ shell=fish term=xterm-256color title=foot font=JetBrainsMono Nerd Font:size=11 letter-spacing=0 dpi-aware=no pad=25x25 bold-text-in-bright=no [scrollback] lines=10000 [cursor] style=beam blink=no beam-thickness=1.5 # underline-thickness= [key-bindings] scrollback-up-page=Page_Up scrollback-down-page=Page_Down clipboard-copy=Control+c clipboard-paste=Control+v # primary-paste=Shift+Insert search-start=Control+f font-increase=Control+plus Control+equal Control+KP_Add font-decrease=Control+minus Control+KP_Subtract font-reset=Control+0 Control+KP_0 [search-bindings] cancel=Escape find-prev=Shift+F3 find-next=F3 Control+G delete-prev-word=Control+BackSpace [text-bindings] \x03=Control+Shift+c ================================================ FILE: dots/.config/fuzzel/fuzzel.ini ================================================ include="~/.config/fuzzel/fuzzel_theme.ini" font=Google Sans Flex:weight=medium terminal=kitty -1 prompt=">> " layer=overlay [border] radius=17 width=1 [dmenu] exit-immediately-if-empty=yes ================================================ FILE: dots/.config/fuzzel/fuzzel_theme.ini ================================================ [colors] background=161217ff text=e9e0e8ff selection=4b454dff selection-text=cdc3ceff border=4b454ddd match=dfb8f6ff selection-match=dfb8f6ff ================================================ FILE: dots/.config/hypr/custom/env.lua ================================================ ================================================ FILE: dots/.config/hypr/custom/execs.lua ================================================ ================================================ FILE: dots/.config/hypr/custom/general.lua ================================================ ================================================ FILE: dots/.config/hypr/custom/keybinds.lua ================================================ hl.bind("CTRL+SUPER+ALT+Slash", hl.dsp.exec_cmd("xdg-open ~/.config/hypr/custom/keybinds.lua"), {description = "Edit user keybinds"} ) ================================================ FILE: dots/.config/hypr/custom/rules.lua ================================================ ================================================ FILE: dots/.config/hypr/custom/scripts/__restore_video_wallpaper.sh ================================================ #!/bin/bash # The content of this script will be generated by switchwall.sh - Don't modify it by yourself. ================================================ FILE: dots/.config/hypr/custom/variables.lua ================================================ ================================================ FILE: dots/.config/hypr/hypridle.conf ================================================ $lock_cmd = hyprctl dispatch 'hl.dsp.global("quickshell:lock")' & pidof qs quickshell hyprlock || hyprlock # $lock_cmd = pidof hyprlock || hyprlock $suspend_cmd = systemctl suspend || loginctl suspend general { lock_cmd = $lock_cmd before_sleep_cmd = loginctl lock-session after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus inhibit_sleep = 3 } listener { timeout = 300 # 5mins on-timeout = loginctl lock-session } listener { timeout = 600 # 10mins on-timeout = hyprctl dispatch dpms off on-resume = hyprctl dispatch dpms on } listener { timeout = 900 # 15mins on-timeout = $suspend_cmd } ================================================ FILE: dots/.config/hypr/hyprland/colors.lua ================================================ hl.config({ general = { col = { active_border = "rgba(44464f77)", inactive_border = "rgba(1a1b2033)", }, }, misc = { background_color = "rgba(121318FF)", }, }) hl.window_rule({ -- not sure how to syntax "pin 1" match = { pin = 1 }, border_color = "rgba(afc6ffAA) rgba(afc6ff77)", }) ================================================ FILE: dots/.config/hypr/hyprland/env.lua ================================================ local home_dir = os.getenv("HOME") -- Wayland hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") -- Applications local xdg_data_dirs_old = os.getenv("XDG_DATA_DIRS") or "" hl.env("XDG_DATA_DIRS", home_dir .. "/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share:" .. xdg_data_dirs_old) -- Themes hl.env("QT_QPA_PLATFORM", "wayland;xcb") hl.env("QT_QPA_PLATFORMTHEME", "kde") hl.env("XDG_MENU_PREFIX", "plasma-") -- Virtual environment hl.env("ILLOGICAL_IMPULSE_VIRTUAL_ENV", home_dir .. "/.local/state/quickshell/.venv") ================================================ FILE: dots/.config/hypr/hyprland/execs.lua ================================================ -- put former exec-once commands inside the func and former exec commands outside hl.on("hyprland.start", function () -- Bar, wallpaper hl.exec_cmd("$HOME/.config/hypr/hyprland/scripts/start_geoclue_agent.sh") hl.exec_cmd("qs -c $qsConfig") hl.exec_cmd("$HOME/.config/hypr/custom/scripts/__restore_video_wallpaper.sh") -- Core components (authentication, lock screen, notification daemon) hl.exec_cmd("gnome-keyring-daemon --start --components=secrets") hl.exec_cmd("hypridle") hl.exec_cmd("dbus-update-activation-environment --all") hl.exec_cmd("sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP") -- Some fix idk -- Audio hl.exec_cmd("easyeffects --hide-window --service-mode") -- Clipboard: history --hl.exec_cmd("wl-paste --watch cliphist store") hl.exec_cmd("wl-paste --type text --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'") hl.exec_cmd("wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'") -- Cursor hl.exec_cmd("hyprctl setcursor Bibata-Modern-Classic 24") end) ================================================ FILE: dots/.config/hypr/hyprland/general.lua ================================================ -- MONITOR CONFIG hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "1" }) hl.gesture({ fingers = 3, direction = "swipe", action = "move" }) hl.gesture({ fingers = 3, direction = "pinch", action = "fullscreen" }) hl.gesture({ fingers = 4, direction = "horizontal", action = "workspace" }) hl.gesture({ fingers = 4, direction = "up", action = function() hl.dispatch(hl.dsp.global("quickshell:overviewWorkspacesToggle")) end }) hl.gesture({ fingers = 4, direction = "down", action = function() hl.dispatch(hl.dsp.global("quickshell:overviewWorkspacesToggle")) end }) hl.config({ gestures = { workspace_swipe_distance = 700, workspace_swipe_cancel_ratio = 0.2, workspace_swipe_min_speed_to_force = 5, workspace_swipe_direction_lock = true, workspace_swipe_direction_lock_threshold = 10, workspace_swipe_create_new = true }, general = { -- Gaps and border gaps_in = 4, gaps_out = 5, gaps_workspaces = 50, border_size = 1, col = { active_border = "rgba(0DB7D455)", inactive_border = "rgba(31313600)" }, resize_on_border = true, no_focus_fallback = true, allow_tearing = true, -- This just allows the `immediate` window rule to work snap = { enabled = true, window_gap = 4, monitor_gap = 5, respect_gaps = true } }, decoration = { -- 2 = circle, higher = squircle, 4 = very obvious squircle -- Fuck clearly visible squircles. 100% Apple brainrot. rounding_power = 2.5, rounding = 18, blur = { enabled = true, xray = true, special = false, new_optimizations = true, size = 10, passes = 3, brightness = 1, noise = 0.05, contrast = 0.89, vibrancy = 0.5, vibrancy_darkness = 0.5, popups = false, popups_ignorealpha = 0.6, input_methods = true, input_methods_ignorealpha = 0.8 }, shadow = { enabled = true, range = 20, offset = {0, 2}, render_power = 10, color = "rgba(00000020)" }, -- Dim dim_inactive = true, dim_strength = 0.05, dim_special = 0.2 }, animations = { enabled = true }, dwindle = { preserve_split = true, smart_split = false, smart_resizing = false -- precise_mouse_move = true, }, }) -- Curves hl.curve("expressiveFastSpatial", { type = "bezier", points = {{0.42, 1.67}, {0.21, 0.90}} }) hl.curve("expressiveSlowSpatial", { type = "bezier", points = {{0.39, 1.29}, {0.35, 0.98}} }) hl.curve("expressiveDefaultSpatial", { type = "bezier", points = {{0.38, 1.21}, {0.22, 1.00}} }) hl.curve("emphasizedDecel", { type = "bezier", points = {{0.05, 0.7}, {0.1, 1}} }) hl.curve("emphasizedAccel", { type = "bezier", points = {{0.3, 0}, {0.8, 0.15}} }) hl.curve("standardDecel", { type = "bezier", points = {{0, 0}, {0, 1}} }) hl.curve("menu_decel", { type = "bezier", points = {{0.1, 1}, {0, 1}} }) hl.curve("menu_accel", { type = "bezier", points = {{0.52, 0.03}, {0.72, 0.08}} }) hl.curve("stall", { type = "bezier", points = {{1, -0.1}, {0.7, 0.85}} }) -- Configs -- windows hl.animation({ leaf = "windowsIn", enabled = true, speed = 3, bezier = "emphasizedDecel", style = "popin 80%" }) hl.animation({ leaf = "fadeIn", enabled = true, speed = 3, bezier = "emphasizedDecel" }) hl.animation({ leaf = "windowsOut", enabled = true, speed = 2, bezier = "emphasizedDecel", style = "popin 90%" }) hl.animation({ leaf = "fadeOut", enabled = true, speed = 2, bezier = "emphasizedDecel" }) hl.animation({ leaf = "windowsMove", enabled = true, speed = 3, bezier = "emphasizedDecel", style = "slide" }) hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "emphasizedDecel" }) -- layers hl.animation({ leaf = "layersIn", enabled = true, speed = 2.7, bezier = "emphasizedDecel", style = "popin 93%" }) hl.animation({ leaf = "layersOut", enabled = true, speed = 2.4, bezier = "menu_accel", style = "popin 94%" }) -- fade hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 0.5, bezier = "menu_decel" }) hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 2.7, bezier = "stall" }) -- workspaces hl.animation({ leaf = "workspaces", enabled = true, speed = 7, bezier = "menu_decel", style = "slide" }) -- specialWorkspace hl.animation({ leaf = "specialWorkspaceIn", enabled = true, speed = 2.8, bezier = "emphasizedDecel", style = "slidevert" }) hl.animation({ leaf = "specialWorkspaceOut", enabled = true, speed = 1.2, bezier = "emphasizedAccel", style = "slidevert" }) -- zoom hl.animation({ leaf = "zoomFactor", enabled = true, speed = 3, bezier = "standardDecel" }) hl.config({ input = { kb_layout = "us", numlock_by_default = true, repeat_delay = 250, repeat_rate = 35, follow_mouse = 1, off_window_axis_events = 2, touchpad = { natural_scroll = true, disable_while_typing = true, clickfinger_behavior = true, scroll_factor = 0.7 } }, misc = { disable_hyprland_logo = true, disable_splash_rendering = true, vrr = 0, mouse_move_enables_dpms = true, key_press_enables_dpms = true, animate_manual_resizes = false, animate_mouse_windowdragging = false, enable_swallow = false, swallow_regex = "(foot|kitty|allacritty|Alacritty)", on_focus_under_fullscreen = 2, allow_session_lock_restore = true, session_lock_xray = true, initial_workspace_tracking = false, focus_on_activate = true }, binds = { scroll_event_delay = 0, hide_special_on_workspace_change = true }, cursor = { zoom_factor = 1, zoom_rigid = false, zoom_disable_aa = true, hotspot_padding = 1 }, xwayland = { force_zero_scaling = true } }) ================================================ FILE: dots/.config/hypr/hyprland/keybinds.lua ================================================ require("hyprland.lib") require("hyprland.variables") if is_file_exists(HOME .. "/.config/hypr/custom/variables.lua") then require("custom.variables") end local qsScripts = "$HOME/.config/quickshell/$qsConfig/scripts" local hyprScripts = "$HOME/.config/hypr/hyprland/scripts" local qsIpcCall = "qs -c $qsConfig ipc call" local qsIsAlive = qsIpcCall .. " TEST_ALIVE" hl.bind("SUPER + SUPER_L", hl.dsp.global("quickshell:searchToggleRelease"), { description = "Shell: Toggle search" }) hl.bind("SUPER + SUPER_R", hl.dsp.global("quickshell:searchToggleRelease")) hl.bind("SUPER + SUPER_L", hl.dsp.exec_cmd(qsIsAlive .. " || pkill fuzzel || fuzzel")) hl.bind("SUPER + SUPER_R", hl.dsp.exec_cmd(qsIsAlive .. " || pkill fuzzel || fuzzel")) hl.bind("SUPER_L", hl.dsp.global("quickshell:workspaceNumber"), { ignore_mods = true, transparent = true }) hl.bind("SUPER_R", hl.dsp.global("quickshell:workspaceNumber"), { ignore_mods = true, transparent = true }) hl.bind("SUPER_L", hl.dsp.global("quickshell:workspaceNumber"), { ignore_mods = true, transparent = true, release = true }) hl.bind("SUPER_R", hl.dsp.global("quickshell:workspaceNumber"), { ignore_mods = true, transparent = true, release = true }) hl.bind("SUPER + Tab", hl.dsp.global("quickshell:overviewWorkspacesToggle"), { description = "Shell: Toggle overview" }) hl.bind("SUPER + V", hl.dsp.global("quickshell:overviewClipboardToggle")) hl.bind("SUPER + Period", hl.dsp.global("quickshell:overviewEmojiToggle")) hl.bind("SUPER + A", hl.dsp.global("quickshell:sidebarLeftToggle"), { description = "Shell: Toggle left sidebar" }) hl.bind("SUPER + ALT + A", hl.dsp.global("quickshell:sidebarLeftToggleDetach")) hl.bind("SUPER + B", hl.dsp.global("quickshell:sidebarLeftToggle")) hl.bind("SUPER + O", hl.dsp.global("quickshell:sidebarLeftToggle")) hl.bind("SUPER + N", hl.dsp.global("quickshell:sidebarRightToggle"), { description = "Shell: Toggle right sidebar" }) hl.bind("SUPER + Slash", hl.dsp.global("quickshell:cheatsheetToggle"), { description = "Shell: Toggle cheatsheet" }) hl.bind("SUPER + K", hl.dsp.global("quickshell:oskToggle"), { description = "Shell: Toggle on-screen keyboard" }) hl.bind("SUPER + M", hl.dsp.global("quickshell:mediaControlsToggle"), { description = "Shell: Toggle media controls" }) hl.bind("SUPER + G", hl.dsp.global("quickshell:overlayToggle"), { description = "Shell: Toggle widget overlay" }) hl.bind("CTRL + ALT + Delete", hl.dsp.global("quickshell:sessionToggle"), { description = "Shell: Toggle session menu" }) hl.bind("SUPER + J", hl.dsp.global("quickshell:barToggle"), { description = "Shell: Toggle bar" }) hl.bind("CTRL + ALT + Delete", hl.dsp.exec_cmd(qsIsAlive .. " || pkill wlogout || wlogout -p layer-shell")) hl.bind("SHIFT + SUPER + ALT + Slash", hl.dsp.exec_cmd("qs -p $HOME/.config/quickshell/$qsConfig/welcome.qml")) hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd(qsIpcCall .. " brightness increment || brightnessctl s 5%+"), { locked = true, repeating = true }) hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd(qsIpcCall .. " brightness decrement || brightnessctl s 5%-"), { locked = true, repeating = true }) hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ -l 1.5"), { locked = true, repeating = true }) hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%-"), { locked = true, repeating = true }) hl.bind("CTRL + SUPER + T", hl.dsp.global("quickshell:wallpaperSelectorToggle"), { description = "Shell: Change wallpaper" }) hl.bind("CTRL + SUPER + ALT + T", hl.dsp.global("quickshell:wallpaperSelectorRandom"), { description = "Shell: Random wallpaper" }) hl.bind("CTRL + SUPER + SHIFT + D", hl.dsp.global("quickshell:toggleLightDark"), { description = "Shell: Toggle light/dark mode" }) hl.bind("CTRL + SUPER + T", hl.dsp.exec_cmd(qsIsAlive .. " || " .. qsScripts .. "/colors/switchwall.sh")) hl.bind("CTRL + SUPER + R", hl.dsp.exec_cmd("killall ydotool qs quickshell; qs -c $qsConfig &"), { description = "Shell: Restart widgets" }) hl.bind("CTRL + SUPER + P", hl.dsp.global("quickshell:panelFamilyCycle"), { description = "Shell: Cycle panel family" }) --##! Utilities --# Screenshot, Record, OCR, Color picker, Clipboard history hl.bind("SUPER + V", hl.dsp.exec_cmd( qsIsAlive .. " || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy"), { description = "Utilities: Clipboard history >> clipboard" }) hl.bind("SUPER + Period", hl.dsp.exec_cmd( qsIsAlive .. " || pkill fuzzel || " .. hyprScripts .. "/fuzzel-emoji.sh copy"), { description = "Utilities: Emoji >> clipboard" }) hl.bind("SUPER + SHIFT + S", hl.dsp.global("quickshell:regionScreenshot"), { description = "Utilities: Screen snip" }) hl.bind("SUPER + SHIFT + S", hl.dsp.exec_cmd(qsIsAlive .. " || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent")) hl.bind("SUPER + SHIFT + A", hl.dsp.global("quickshell:regionSearch"), { description = "Utilities: Google Lens" }) hl.bind("SUPER + SHIFT + A", hl.dsp.exec_cmd(qsIsAlive .. " || pidof slurp || " .. hyprScripts .. "/snip_to_search.sh")) --# OCR hl.bind("SUPER + SHIFT + X", hl.dsp.global("quickshell:regionOcr"), { description = "Utilities: Character recognition >> clipboard" }) hl.bind("SUPER + SHIFT + T", hl.dsp.global("quickshell:screenTranslate"), { description = "Utilities: Translate screen content" }) hl.bind("SUPER + SHIFT + X", hl.dsp.exec_cmd( qsIsAlive .. " || pidof slurp || grim -g \"$(slurp $SLURP_ARGS)\" \"/tmp/ocr_image.png\" && tesseract \"/tmp/ocr_image.png\" stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\\\n' '+' | sed 's/\\\\+$/\\\\n/') | wl-copy && rm \"/tmp/ocr_image.png\"" )) --# Color picker hl.bind("SUPER + SHIFT + C", hl.dsp.exec_cmd("hyprpicker -a"), { description = "Utilities: Pick color #RRGGBB >> clipboard" }) --# Recording stuff hl.bind("SUPER + SHIFT + R", hl.dsp.global("quickshell:regionRecord"), { locked = true, description = "Utilities: Record region (no sound)" }) hl.bind("SUPER + SHIFT + R", hl.dsp.exec_cmd(qsIsAlive .. " || " .. qsScripts .. "/videos/record.sh"), { locked = true }) hl.bind("SUPER + ALT + R", hl.dsp.global("quickshell:regionRecord"), { locked = true }) hl.bind("SUPER + ALT + R", hl.dsp.exec_cmd(qsIsAlive .. " || " .. qsScripts .. "/videos/record.sh"), { locked = true }) hl.bind("CTRL + ALT + R", hl.dsp.exec_cmd(qsScripts .. "/videos/record.sh --fullscreen"), { locked = true }) hl.bind("SUPER + SHIFT + ALT + R", hl.dsp.exec_cmd(qsScripts .. "/videos/record.sh --fullscreen --sound"), { locked = true, description = "Utilities: Record screen (with sound)" }) --# Fullscreen screenshot local grimhyprctl = "grim -o \"$(hyprctl activeworkspace -j | jq -r '.monitor')\"" hl.bind("Print", hl.dsp.exec_cmd(grimhyprctl .. " - | wl-copy"), { locked = true, description = "Utilities: Screenshot >> clipboard" }) hl.bind("CTRL + Print", hl.dsp.exec_cmd( "mkdir -p $(xdg-user-dir PICTURES)/Screenshots && " .. grimhyprctl .. " $(xdg-user-dir PICTURES)/Screenshots/Screenshot_\"$(date '+%Y-%m-%d_%H.%M.%S')\".png" ), { locked = true, non_consuming = true, description = "Utilities: Screenshot >> clipboard & file" }) hl.bind("CTRL + Print", hl.dsp.exec_cmd(grimhyprctl .. " - | wl-copy"), { locked = true, non_consuming = true }) --# AI hl.bind("SUPER + SHIFT + ALT + mouse:273", hl.dsp.exec_cmd(hyprScripts .. "/ai/primary-buffer-query.sh"), { description = "Utilities: Generate AI summary for selected text" }) -- (requires a running ollama model) --##! Screen --# Zoom local function zoomfunction(value) local zoomvalue = hl.get_config("cursor:zoom_factor") if (zoomvalue + value) > 3.0 then hl.config({ cursor = { zoom_factor = 3.0 } }) elseif (zoomvalue + value) < 1.0 then hl.config({ cursor = { zoom_factor = 1.0 } }) else hl.config({ cursor = { zoom_factor = zoomvalue + value } }) end end hl.bind("SUPER + Minus", function() zoomfunction(-0.3) end, { repeating = true, description = "Screen: Zoom out" }) hl.bind("SUPER + Equal", function() zoomfunction(0.3) end, { repeating = true, description = "Screen: Zoom in" }) --# Zoom with keypad hl.bind("SUPER + code:82", function() zoomfunction(-0.3) end, { repeating = true }) hl.bind("SUPER + code:86", function() zoomfunction(0.3) end, { repeating = true }) --##! Media local mediaNextCommand = "playerctl next || playerctl position `bc <<< \"100 * $(playerctl metadata mpris:length) / 1000000 / 100\"`" hl.bind("SUPER + SHIFT + N", hl.dsp.exec_cmd(mediaNextCommand), { locked = true, description = "Media: Next track" }) hl.bind("XF86AudioNext", hl.dsp.exec_cmd(mediaNextCommand), { locked = true }) hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true }) hl.bind("SUPER + SHIFT + ALT + mouse:275", hl.dsp.exec_cmd("playerctl previous")) hl.bind("SUPER + SHIFT + ALT + mouse:276", hl.dsp.exec_cmd(mediaNextCommand)) hl.bind("SUPER + SHIFT + B", hl.dsp.exec_cmd("playerctl previous"), { locked = true, description = "Media: Previous track" }) hl.bind("SUPER + SHIFT + P", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true, description = "Media: Play/pause media" }) hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true }) hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_SINK@ toggle"), { locked = true }) hl.bind("SUPER + SHIFT + M", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_SINK@ toggle"), { locked = true, description = "Media: Toggle mute" }) hl.bind("ALT + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_SOURCE@ toggle"), { locked = true }) hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_SOURCE@ toggle"), { locked = true }) hl.bind("SUPER + ALT + M", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_SOURCE@ toggle"), { locked = true, description = "Media: Toggle mic" }) --#! --##! Window --# Focusing hl.bind("SUPER + mouse:272", hl.dsp.window.drag(), { mouse = true, description = "Window: Move" }) hl.bind("SUPER + mouse:274", hl.dsp.window.drag(), { mouse = true }) hl.bind("SUPER + mouse:273", hl.dsp.window.resize(), { mouse = true, description = "Window: Resize" }) --#/# bind = SUPER + ←/↑/→/↓,, -- Focus in direction for i = 1, 4 do local arrowkey = { "Left", "Right", "Up", "Down" } local focusdir = { "l", "r", "u", "d" } hl.bind("SUPER + " .. arrowkey[i], hl.dsp.focus({ direction = focusdir[i] }), { description = "Window: Focus " .. arrowkey[i] }) end for i = 1, 2 do local arrowkey = { "BracketLeft", "BracketRight" } local focusdir = { "l", "r" } hl.bind("SUPER + " .. arrowkey[i], hl.dsp.focus({ direction = focusdir[i] })) end --#/# bind = SUPER + SHIFT, ←/↑/→/↓,, -- Move in direction for i = 1, 4 do local arrowkey = { "Left", "Right", "Up", "Down" } local focusdir = { "l", "r", "u", "d" } hl.bind("SUPER + SHIFT + " .. arrowkey[i], hl.dsp.window.move({ direction = focusdir[i] }), { description = "Window: Move " .. arrowkey[i] }) end hl.bind("ALT + F4", function() hl.exec_cmd( "notify-send \"Wrong close keybind\" \"Super+Q to close. Use Alt+F4 for Windows VMs\" -a Hyprland") end, { non_consuming = true }) hl.bind("SUPER + Q", hl.dsp.window.close(), { description = "Window: Close" }) hl.bind("SUPER + SHIFT + ALT + Q", hl.dsp.exec_cmd("hyprctl kill"), { description = "Window: Forcefully zap a window" }) --# Window split ratio --#/# binde = SUPER, ;/',, -- Adjust split ratio hl.bind("SUPER + Semicolon", hl.dsp.layout("splitratio -0.1"), { repeating = true }) hl.bind("SUPER + Apostrophe", hl.dsp.layout("splitratio +0.1"), { repeating = true }) --# Positioning mode hl.bind("SUPER + ALT + Space", hl.dsp.window.float({ action = "toggle" }), { description = "Window: Float/Tile" }) hl.bind("SUPER + D", hl.dsp.window.fullscreen({ mode = "maximized", action = "toggle" }), { description = "Window: Maximize" }) hl.bind("SUPER + F", hl.dsp.window.fullscreen({ mode = "fullscreen", action = "toggle" }), { description = "Window: Fullscreen" }) hl.bind("SUPER + ALT + F", hl.dsp.window.fullscreen_state({ internal = 0, client = 3, action = "toggle" }), { description = "Window: Fullscreen spoof" }) hl.bind("SUPER + P", hl.dsp.window.pin(), { description = "Window: Pin" }) --#/# bind = SUPER+ALT, Hash,, -- Send to workspace -- (1, 2, 3,...) for i = 1, 10 do hl.bind("SUPER + ALT + " .. (i % 10), function() hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false })) end, { description = "Window: Send to workspace " .. i }) end --# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev` for i = 1, 10 do local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } hl.bind("SUPER + ALT + code:" .. numberkey[i], function() hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false })) end) end --# keypad numbers for i = 1, 10 do local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 } hl.bind("SUPER + ALT + code:" .. numpadkey[i], function() hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false })) end) end --# #/# bind = SUPER+SHIFT, Scroll ↑/↓,, -- Send to workspace left/right for i = 1, 4 do local key = { "SUPER + SHIFT + mouse_", "SUPER + ALT + mouse_" } local keycombos = { key[1] .. "down", key[1] .. "up", key[2] .. "down", key[2] .. "up" } local prefix = { "r-", "r+", "r-", "r+" } hl.bind(keycombos[i], hl.dsp.window.move({ workspace = prefix[i] .. "1" })) end --#/# bind = SUPER+SHIFT, Page_↑/↓,, -- Send to workspace left/right for i = 1, 2 do local keydirs = { "Up", "Down" } local prefix = { "r-", "r+" } local descdir = { "left", "right" } hl.bind("SUPER + SHIFT + Page_" .. keydirs[i], hl.dsp.window.move({ workspace = prefix[i] .. "1" }), {description = "Window: Send to workspace " .. descdir[i]}) end for i = 1, 4 do local key = { "SUPER + ALT + Page_", "CTRL + SUPER + SHIFT + " } local keycombos = { key[1] .. "down", key[1] .. "up", key[2] .. "Right", key[2] .. "Left" } local prefix = { "r+", "r-", "r+", "r-" } hl.bind(keycombos[i], hl.dsp.window.move({ workspace = prefix[i] .. "1" })) -- # [hidden] end hl.bind("SUPER + ALT + S", hl.dsp.window.move({ workspace = "special:special", follow = false }), { description = "Window: Send to scratchpad" }) hl.bind("CTRL + SUPER + S", hl.dsp.workspace.toggle_special("special")) --##! Workspace --# Switching --#/# bind = SUPER, Hash,, -- Focus workspace -- (1, 2, 3,...) for i = 1, 10 do hl.bind("SUPER + " .. (i % 10), function() hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) })) end, { description = "Workspace: Focus " .. i }) end --# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev` for i = 1, 10 do local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } hl.bind("SUPER + code:" .. numberkey[i], function() hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) })) end) end --# keypad numbers for i = 1, 10 do local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 } hl.bind("SUPER + code:" .. numpadkey[i], function() hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) })) end) end --#/# bind = CTRL+SUPER, ←/→,, -- Focus left/right --#/# bind = CTRL+SUPER+ALT, ←/→,, -- # [hidden] Focus busy left/right for i = 1, 2 do local keys = { "Left", "Right" } local prefix = { "r-", "r+" } local descdir = { "left", "right" } hl.bind("CTRL + SUPER + " .. keys[i], hl.dsp.focus({ workspace = prefix[i] .. "1" }), {description = "Workspace: Focus " .. descdir[i]}) end for i = 1, 2 do local keys = { "Left", "Right" } local prefix = { "m-", "m+" } hl.bind("CTRL + SUPER + ALT + " .. keys[i], hl.dsp.focus({ workspace = prefix[i] .. "1" })) end --#/# bind = SUPER, Page_↑/↓,, -- Focus left/right for i = 1, 4 do local key = { "SUPER + Page_Down", "SUPER + Page_Up" } local keycombos = { key[1], key[2], "CTRL + " .. key[1], "CTRL + " .. key[2] } local prefix = { "r+", "r-", "r+", "r-" } hl.bind(keycombos[i], hl.dsp.focus({ workspace = prefix[i] .. "1" })) end --#/# bind = SUPER, Scroll ↑/↓,, -- Focus left/right for i = 1, 4 do local key = { "SUPER + mouse_up", "SUPER + mouse_down" } local keycombos = { key[1], key[2], "CTRL + " .. key[1], "CTRL + " .. key[2] } local prefix = { "+", "-", "r+", "r-" } hl.bind(keycombos[i], hl.dsp.focus({ workspace = prefix[i] .. "1" })) end --## Special hl.bind("SUPER + S", hl.dsp.workspace.toggle_special("special"), { description = "Workspace: Toggle scratchpad" }) hl.bind("SUPER + mouse:275", hl.dsp.workspace.toggle_special("special")) for i = 1, 4 do local key = { "BracketLeft", "BracketRight", "Up", "Down" } local prefix = { "-1", "+1", "r-5", "r+5" } hl.bind("CTRL + SUPER + " .. key[i], hl.dsp.focus({ workspace = prefix[i] })) end --##! Virtual machines hl.define_submap("virtual-machine", function() hl.bind("SUPER + ALT + F1", function() local currentsubmap = hl.get_current_submap() if currentsubmap == "virtual-machine" then hl.dispatch(hl.dsp.exec_cmd( "notify-send 'Exited Virtual Machine submap' 'Keybinds re-enabled' -a 'Hyprland'")) hl.dispatch(hl.dsp.submap("reset")) elseif currentsubmap == "" then hl.dispatch(hl.dsp.exec_cmd( "notify-send 'Entered Virtual Machine submap' 'Keybinds disabled. hit SUPER+ALT+F1 to escape' -a 'Hyprland'")) hl.dispatch(hl.dsp.submap("virtual-machine")) end end, { submap_universal = true }) end) --#! --# Testing hl.bind("SUPER + ALT + F11", hl.dsp.exec_cmd( "bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | shuf -n 1); ACTION=$(notify-send \"Test notification with body image\" \"This notification should contain your user account image and Discord icon. Oh and here is a random image in your Pictures folder: \\\"Testing\" -a \"Hyprland\" -p -h \"string:image-path:/var/lib/AccountsService/icons/$USER\" -t 6000 -i \"discord\" -A \"openImage=Profile image\" -A \"action2=Open the random image\" -A \"action3=Useless button\"); [[ $ACTION == *openImage ]] && xdg-open \"/var/lib/AccountsService/icons/$USER\"; [[ $ACTION == *action2 ]] && xdg-open \"$RANDOM_IMAGE\"'") ) -- # [hidden] hl.bind("SUPER + ALT + F12", hl.dsp.exec_cmd( "bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | shuf -n 1); ACTION=$(notify-send \"Test notification\" \"This notification should contain a random image in your Pictures folder and Discord icon.\nFlick right to dismiss!\" -a \"Discord (fake)\" -p -h \"string:image-path:$RANDOM_IMAGE\" -t 6000 -i \"discord\" -A \"openImage=Profile image\" -A \"action2=Useless button\"); [[ $ACTION == *openImage ]] && xdg-open \"/var/lib/AccountsService/icons/$USER\"'") ) -- # [hidden] hl.bind("SUPER + ALT + Equal", hl.dsp.exec_cmd("notify-send 'Urgent notification' 'Ah hell no' -u critical -a 'Hyprland keybind'")) -- # [hidden] --##! Session hl.bind("SUPER + L", hl.dsp.exec_cmd("loginctl lock-session"), { description = "Session: Lock" }) hl.bind("SUPER + SHIFT + L", hl.dsp.exec_cmd("systemctl suspend || loginctl suspend"), { locked = true, description = "Session: Sleep" }) -- Sleep -- hl.bind("switch:on:Lid Switch", hl.dsp.exec_cmd("systemctl suspend || loginctl suspend"), {locked = true} ) -- # [hidden] Suspend when laptop lid is closed, uncomment if for whatever reason it's not the default behavior hl.bind("CTRL + SHIFT + ALT + SUPER + Delete", hl.dsp.exec_cmd("systemctl poweroff || loginctl poweroff"), { description = "Session: Shut down" }) -- # [hidden] Power off --##! Apps hl.bind("SUPER + Return", hl.dsp.exec_cmd(terminal), { description = "App: Terminal" }) hl.bind("SUPER + T", hl.dsp.exec_cmd(terminal)) hl.bind("CTRL + ALT + T", hl.dsp.exec_cmd(terminal)) hl.bind("SUPER + E", hl.dsp.exec_cmd(fileManager), { description = "App: File manager" }) hl.bind("SUPER + W", hl.dsp.exec_cmd(browser), { description = "App: Browser" }) hl.bind("SUPER + C", hl.dsp.exec_cmd(codeEditor), { description = "App: Code editor" }) hl.bind("CTRL + SUPER + SHIFT + ALT + W", hl.dsp.exec_cmd(officeSoftware), { description = "App: Office software" }) hl.bind("SUPER + X", hl.dsp.exec_cmd(textEditor), { description = "App: Text editor" }) hl.bind("CTRL + SUPER + V", hl.dsp.exec_cmd(volumeMixer), { description = "App: Volume mixer" }) hl.bind("SUPER + I", hl.dsp.exec_cmd(settingsApp), { description = "App: Settings app" }) hl.bind("CTRL + SHIFT + Escape", hl.dsp.exec_cmd(taskManager), { description = "App: Task manager" }) --# Cursed stuff --## Make window not amogus large hl.bind("CTRL + SUPER + Backslash", hl.dsp.window.resize({ x = 640, y = 480, "exact" })) ================================================ FILE: dots/.config/hypr/hyprland/lib/init.lua ================================================ HOME = os.getenv("HOME") function is_file_exists(name) local f = io.open(name, "r") if f ~= nil then io.close(f) return true else return false end end function create_if_not_exists(path) if not is_file_exists(path) then os.execute("mkdir -p \"$(dirname \"" .. path .. "\")\"") os.execute("echo '-- This file will not be overwritten across dots-hyprland updates.\n-- The file name is for the sake of organization and does not matter\n-- See the corresponding files in ~/.config/hypr/hyprland for examples' > \"" .. path .. "\"") return true end return false end function workspace_in_group(i) local curr = hl.get_active_workspace().id local newVal = math.floor((curr - 1) / workspaceGroupSize) * workspaceGroupSize + i -- hl.notification.create({ text = "curr " .. curr .. " floor " .. math.floor(curr / 10) .. " new " .. newVal, duration = 5000 }) return newVal end ================================================ FILE: dots/.config/hypr/hyprland/rules.lua ================================================ -- ######## Window rules ######## -- Disable blur for xwayland context menus hl.window_rule({match = {class = "^()$", title = "^()$" }, no_blur = true }) -- Disable blur for every window hl.window_rule({match = {class = ".*" }, no_blur = true }) -- Floating hl.window_rule({match = {title = "^(Open File)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Open File)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(Select a File)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Select a File)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(Choose wallpaper)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Choose wallpaper)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(Choose wallpaper)(.*)$" }, size = {"(monitor_w*0.60)", "(monitor_h*0.65)"} }) hl.window_rule({match = {title = "^(Open Folder)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Open Folder)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(Save As)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Save As)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(Library)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(Library)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(File Upload)(.*)$" }, center = true}) hl.window_rule({match = {title = "^(File Upload)(.*)$" }, float = true}) hl.window_rule({match = {title = "^(.*)(wants to save)$" }, center = true}) hl.window_rule({match = {title = "^(.*)(wants to save)$" }, float = true}) hl.window_rule({match = {title = "^(.*)(wants to open)$" }, center = true}) hl.window_rule({match = {title = "^(.*)(wants to open)$" }, float = true}) hl.window_rule({match = {class = "^(blueberry\\.py)$" }, float = true}) hl.window_rule({match = {class = "^(guifetch)$" }, float = true}) -- FlafyDev/guifetch hl.window_rule({match = {class = "^(pavucontrol)$" }, float = true}) hl.window_rule({match = {class = "^(pavucontrol)$" }, size = {"(monitor_w*0.45)", "(monitor_h*0.45)"} }) hl.window_rule({match = {class = "^(pavucontrol)$" }, center = true}) hl.window_rule({match = {class = "^(org.pulseaudio.pavucontrol)$" }, float = true}) hl.window_rule({match = {class = "^(org.pulseaudio.pavucontrol)$" }, size = {"(monitor_w*0.45)", "(monitor_h*0.45)"} }) hl.window_rule({match = {class = "^(org.pulseaudio.pavucontrol)$" }, center = true}) hl.window_rule({match = {class = "^(nm-connection-editor)$" }, float = true}) hl.window_rule({match = {class = "^(nm-connection-editor)$" }, size = {"(monitor_w*0.45)", "(monitor_h*0.45)"} }) hl.window_rule({match = {class = "^(nm-connection-editor)$" }, center = true}) hl.window_rule({match = {class = ".*plasmawindowed.*" }, float = true}) hl.window_rule({match = {class = "kcm_.*" }, float = true}) hl.window_rule({match = {class = ".*bluedevilwizard" }, float = true}) hl.window_rule({match = {title = ".*Welcome" }, float = true}) hl.window_rule({match = {title = "^(illogical-impulse Settings)$" }, float = true}) hl.window_rule({match = {title = ".*Shell conflicts.*" }, float = true}) hl.window_rule({match = {class = "org.freedesktop.impl.portal.desktop.kde" }, float = true}) hl.window_rule({match = {class = "org.freedesktop.impl.portal.desktop.kde" }, size = {"(monitor_w*0.60)", "(monitor_h*0.65)"} }) hl.window_rule({match = {class = "^(Zotero)$" }, float = true}) hl.window_rule({match = {class = "^(Zotero)$" }, size = {"(monitor_w*0.45)", "(monitor_h*0.45)"} }) -- Move -- kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all. hl.window_rule({match = {class = "^(plasma-changeicons)$" }, float = true}) hl.window_rule({match = {class = "^(plasma-changeicons)$" }, no_initial_focus = true}) hl.window_rule({match = {class = "^(plasma-changeicons)$" }, move = {999999, 999999}}) -- stupid dolphin copy hl.window_rule({match = {title = "^(Copying — Dolphin)$" }, move = {40, 80}}) -- Tiling hl.window_rule({match = {class = "^dev\\.warp\\.Warp$" }, tile = true}) -- Picture-in-Picture hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, float = true}) hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, keep_aspect_ratio = true}) hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, move = {"(monitor_w*0.73)", "(monitor_h*0.72)"} }) hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, size = {"(monitor_w*0.25)", "(monitor_h*0.25)"} }) hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, float = true}) hl.window_rule({match = {title = "^([Pp]icture[-\\s]?[Ii]n[-\\s]?[Pp]icture)(.*)$" }, pin = true}) -- Screen sharing hl.window_rule({match = {title = ".*is sharing (a window|your screen).*" }, float = true}) hl.window_rule({match = {title = ".*is sharing (a window|your screen).*" }, pin = true}) hl.window_rule({match = {title = ".*is sharing (a window|your screen).*" }, move = {"(monitor_w*.5-window_w*.5)", "(monitor_h-window_h-12)"} }) -- --- Tearing --- hl.window_rule({match = {title = ".*\\.exe" }, immediate = true}) hl.window_rule({match = {title = ".*minecraft.*" }, immediate = true}) hl.window_rule({match = {class = "^(steam_app).*" }, immediate = true}) -- No shadow for tiled windows hl.window_rule({match = {float = 0 }, no_shadow = true}) -- ######## Workspace rules ######## hl.workspace_rule({ workspace = "special:special", gaps_out = 30 }) -- ######## Layer rules ######## hl.layer_rule({ match = { namespace = ".*" }, xray = true}) hl.layer_rule({ match = { namespace = "walker" }, no_anim = true}) hl.layer_rule({ match = { namespace = "selection" }, no_anim = true}) hl.layer_rule({ match = { namespace = "overview" }, no_anim = true}) hl.layer_rule({ match = { namespace = "anyrun" }, no_anim = true}) hl.layer_rule({ match = { namespace = "indicator.*" }, no_anim = true}) hl.layer_rule({ match = { namespace = "osk" }, no_anim = true}) hl.layer_rule({ match = { namespace = "hyprpicker" }, no_anim = true}) hl.layer_rule({ match = { namespace = "noanim" }, no_anim = true}) hl.layer_rule({ match = { namespace = "gtk-layer-shell" }, blur = true}) hl.layer_rule({ match = { namespace = "gtk-layer-shell" }, ignore_alpha = 0}) hl.layer_rule({ match = { namespace = "launcher" }, blur = true}) hl.layer_rule({ match = { namespace = "launcher" }, ignore_alpha = 0.5}) hl.layer_rule({ match = { namespace = "notifications" }, blur = true}) hl.layer_rule({ match = { namespace = "notifications" }, ignore_alpha = 0.69}) hl.layer_rule({ match = { namespace = "logout_dialog" }, blur = true}) -- wlogout -- ags hl.layer_rule({ match = { namespace = "sideleft.*" }, animation = "slide left"}) hl.layer_rule({ match = { namespace = "sideright.*" }, animation = "slide right"}) hl.layer_rule({ match = { namespace = "session[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "bar[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "bar[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "barcorner.*" }, blur = true}) hl.layer_rule({ match = { namespace = "barcorner.*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "dock[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "dock[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "indicator.*" }, blur = true}) hl.layer_rule({ match = { namespace = "indicator.*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "overview[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "overview[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "cheatsheet[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "cheatsheet[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "sideright[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "sideright[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "sideleft[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "sideleft[0-9]*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "indicator.*" }, blur = true}) hl.layer_rule({ match = { namespace = "indicator.*" }, ignore_alpha = 0.6}) hl.layer_rule({ match = { namespace = "osk[0-9]*" }, blur = true}) hl.layer_rule({ match = { namespace = "osk[0-9]*" }, ignore_alpha = 0.6}) -- Quickshell -- Quickshell: illogical-impulse hl.layer_rule({ match = { namespace = "quickshell:.*" }, blur_popups = true}) hl.layer_rule({ match = { namespace = "quickshell:.*" }, blur = true}) hl.layer_rule({ match = { namespace = "quickshell:.*" }, ignore_alpha = 0.79}) hl.layer_rule({ match = { namespace = "quickshell:bar" }, animation = "slide"}) hl.layer_rule({ match = { namespace = "quickshell:actionCenter" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:cheatsheet" }, animation = "slide bottom"}) hl.layer_rule({ match = { namespace = "quickshell:dock" }, animation = "slide bottom"}) hl.layer_rule({ match = { namespace = "quickshell:screenCorners" }, animation = "popin 120%"}) hl.layer_rule({ match = { namespace = "quickshell:lockWindowPusher" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:notificationPopup" }, animation = "fade"}) hl.layer_rule({ match = { namespace = "quickshell:overlay" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:overlay" }, ignore_alpha = 1}) hl.layer_rule({ match = { namespace = "quickshell:overview" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:osk" }, animation = "slide bottom"}) hl.layer_rule({ match = { namespace = "quickshell:polkit" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:popup" }, xray = false}) -- No weird color for bar tooltips (this in theory should suffice) hl.layer_rule({ match = { namespace = "quickshell:popup" }, ignore_alpha = 1}) -- No weird color for bar tooltips (but somehow this is necessary) hl.layer_rule({ match = { namespace = "quickshell:mediaControls" }, ignore_alpha = 1}) -- Same as above hl.layer_rule({ match = { namespace = "quickshell:reloadPopup" }, animation = "slide"}) hl.layer_rule({ match = { namespace = "quickshell:regionSelector" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:screenshot" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:session" }, blur = true}) hl.layer_rule({ match = { namespace = "quickshell:session" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:session" }, ignore_alpha = 0}) hl.layer_rule({ match = { namespace = "quickshell:sidebarRight" }, animation = "slide right"}) hl.layer_rule({ match = { namespace = "quickshell:sidebarLeft" }, animation = "slide left"}) hl.layer_rule({ match = { namespace = "quickshell:verticalBar" }, animation = "slide"}) hl.layer_rule({ match = { namespace = "quickshell:osk" }, order = -1}) -- Quickshell: waffles hl.layer_rule({ match = { namespace = "quickshell:wallpaperSelector" }, animation = "slide top"}) hl.layer_rule({ match = { namespace = "quickshell:wNotificationCenter" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:wOnScreenDisplay" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:wStartMenu" }, no_anim = true}) hl.layer_rule({ match = { namespace = "quickshell:wTaskView" }, ignore_alpha = 0}) hl.layer_rule({ match = { namespace = "quickshell:wTaskView" }, no_anim = true}) -- Launchers need to be FAST hl.layer_rule({ match = { namespace = "gtk4-layer-shell" }, no_anim = true}) ================================================ FILE: dots/.config/hypr/hyprland/scripts/ai/license_show-loaded-ollama-models.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: dots/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh ================================================ #!/usr/bin/env bash # Default system prompt SYSTEM_PROMPT="You are a helpful, quick assistant that provides brief and concise explanation \ to given content in at most 100 characters. If the given content is not in English, translate \ it to English. If the content is an English word, provide its meaning. If the content is a name, \ provide some info about it. For a math expression, provide a simplification, \ each step on a line following this style: \`2x=11 (subtract 7 from both sides)\`. \ If you do not know the answer, simply say 'No info available'. \ Only respond for the appropriate case and use as little text as possible.\ The content:" first_loaded_model=$("$(dirname "$0")/show-loaded-ollama-models.sh" -j | jq -r '.[0].model' 2>/dev/null) || first_loaded_model="" model=${first_loaded_model:-"llama3.2"} # Parse command-line arguments while [[ "$#" -gt 0 ]]; do case $1 in --model) model="$2"; shift ;; # Set the model from the flag *) echo "Unknown parameter: $1"; exit 1 ;; esac shift done # Combine the system prompt with the clipboard content content=$(wl-paste -p | tr '\n' ' ' | head -c 2000) # 2000 char limit to prevent overflow # Properly escape content for JSON using jq prompt_json=$(jq -n --arg system_prompt "$SYSTEM_PROMPT" --arg content "$content" '$system_prompt + " " + $content') # Make the API call with the specified or default model api_payload=$(jq -n --arg model "$model" --argjson prompt "$prompt_json" --argjson stream false \ '{model: $model, prompt: $prompt, stream: $stream}') response=$(curl -s http://localhost:11434/api/generate -d "$api_payload" | jq -r '.response' 2>/dev/null) # Check if content is a single line and no longer than 30 characters if [[ ${#content} -le 30 && "$content" != *$'\n'* ]]; then notify-send --app-name="Text selection query" --expire-time=10000 \ "$content" "$response" else notify-send --app-name="Text selection query" --expire-time=10000 \ "AI Response" "$response" fi ================================================ FILE: dots/.config/hypr/hyprland/scripts/ai/show-loaded-ollama-models.sh ================================================ #!/bin/bash # From strikeoncmputrz/LLM_Scripts # License: Apache-2.0, can be found in the same folder as this script # Global Vars ollama_url=http://localhost port="11434" blobs=() model_name_paths=() #Parse arguments while [ "$#" -gt 0 ]; do case $1 in -h|--help) echo echo " Identifies Ollama models running on this operating system by parsing running processes." echo echo " Usage: $0 [options]" echo echo " Options:" echo " -j, --json_output Prints result as a json object. Other output disabled. (Default: false)" echo " -p, --port [port number] Specify Ollama Server port (Default: 11434)" echo " -u, --ollama_url [url] Specify Ollama Server URL (Default: http://localhost)" echo echo " Dependencies: jq" exit 0 ;; -j|--json_output) json_out=1 shift 1 ;; -u|--ollama_url) ollama_url=$2 shift 2 ;; -p|--port) port=$2 shift 2 ;; *) echo "Unknown option: $1" exit 1 ;; esac done compare_running_models_and_modelfiles() { json_match=() json_output=() local matching_models=() OLDIFS=$IFS for ((i=0; i<${#model_name_paths[@]}; i++)); do # Iterate over the array of modelname,blob-path for blob in "${blobs[@]}"; do IFS=',', read -ra fields <<< "${model_name_paths[i]}" # Split the string into parts if [ "${fields[1]}" == "$blob" ]; then # Check if current 'field' matches a blob matching_models+=( '{ "model": "'"${fields[0]}"'", "path": "'"${fields[1]}"'"}') # Add to list of matching models fi done done if [ -z "$json_out" ]; then echo -e "\nModel Found: \n $(echo ${matching_models[*]} | jq '.' | sed s/[{}]//g) \n" else local json_match="${matching_models[*]}" json_output=$(echo $json_match | jq -c -s .) echo "$json_output" fi IFS=$OLDIFS } get_running_model_paths() { blobs=$(ps aux | grep -- '--model' | grep -v grep | grep -Po '(?<=--model\s).*' | cut -d ' ' -f1) if [ -z "$blobs" ]; then echo -e "\n\n Warning: No running Ollama models detected!\n" exit 0 fi } parse_modelfiles() { if [ -z "$json_out" ]; then echo -e "\nConnecting to $ollama_url:$port\n" if [ -z "$(curl -s $ollama_url:$port)" ]; then echo -e "Could not connect to Ollama. Check the ollama_url parameter and that the server is running\n" exit 1 fi curl -s "$ollama_url:$port" fi local models=( $(curl -s "$ollama_url:$port/api/tags" | jq -r '.models[].name') ) for model in "${models[@]}"; do local modelfile=$(curl -s "$ollama_url:$port/api/show" -d '{ "name": "'"$model"'", "modelfile": true }' | jq -r '.modelfile') model_name_paths+=($model,$(echo "$modelfile" | awk '/^FROM/{print $2}')) done } parse_modelfiles get_running_model_paths compare_running_models_and_modelfiles ================================================ FILE: dots/.config/hypr/hyprland/scripts/fuzzel-emoji.sh ================================================ #!/bin/bash set -euo pipefail MODE="${1:-type}" emoji="$(sed '1,/^### DATA ###$/d' "$0" | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n')" case "$MODE" in type) wtype "${emoji}" || wl-copy "${emoji}" ;; copy) wl-copy "${emoji}" ;; both) wtype "${emoji}" || true wl-copy "${emoji}" ;; *) echo "Usage: $0 [type|copy|both]" exit 1 ;; esac exit ### DATA ### 😀 grinning face face smile happy joy :D grin 😃 grinning face with big eyes face happy joy haha :D :) smile funny 😄 grinning face with smiling eyes face happy joy funny haha laugh like :D :) smile 😁 beaming face with smiling eyes face happy smile joy kawaii 😆 grinning squinting face happy joy lol satisfied haha face glad XD laugh 😅 grinning face with sweat face hot happy laugh sweat smile relief 🤣 rolling on the floor laughing face rolling floor laughing lol haha rofl 😂 face with tears of joy face cry tears weep happy happytears haha 🙂 slightly smiling face face smile 🙃 upside down face face flipped silly smile 😉 winking face face happy mischievous secret ;) smile eye 😊 smiling face with smiling eyes face smile happy flushed crush embarrassed shy joy 😇 smiling face with halo face angel heaven halo 🥰 smiling face with hearts face love like affection valentines infatuation crush hearts adore 😍 smiling face with heart eyes face love like affection valentines infatuation crush heart 🤩 star struck face smile starry eyes grinning 😘 face blowing a kiss face love like affection valentines infatuation kiss 😗 kissing face love like face 3 valentines infatuation kiss ☺️ smiling face face blush massage happiness 😚 kissing face with closed eyes face love like affection valentines infatuation kiss 😙 kissing face with smiling eyes face affection valentines infatuation kiss 😋 face savoring food happy joy tongue smile face silly yummy nom delicious savouring 😛 face with tongue face prank childish playful mischievous smile tongue 😜 winking face with tongue face prank childish playful mischievous smile wink tongue 🤪 zany face face goofy crazy 😝 squinting face with tongue face prank playful mischievous smile tongue 🤑 money mouth face face rich dollar money 🤗 hugging face face smile hug 🤭 face with hand over mouth face whoops shock surprise 🤫 shushing face face quiet shhh 🤔 thinking face face hmmm think consider 🤐 zipper mouth face face sealed zipper secret 🤨 face with raised eyebrow face distrust scepticism disapproval disbelief surprise 😐 neutral face indifference meh :| neutral 😑 expressionless face face indifferent - - meh deadpan 😶 face without mouth face hellokitty 😏 smirking face face smile mean prank smug sarcasm 😒 unamused face indifference bored straight face serious sarcasm unimpressed skeptical dubious side eye 🙄 face with rolling eyes face eyeroll frustrated 😬 grimacing face face grimace teeth 🤥 lying face face lie pinocchio 😌 relieved face face relaxed phew massage happiness 😔 pensive face face sad depressed upset 😪 sleepy face face tired rest nap 🤤 drooling face face 😴 sleeping face face tired sleepy night zzz 😷 face with medical mask face sick ill disease 🤒 face with thermometer sick temperature thermometer cold fever 🤕 face with head bandage injured clumsy bandage hurt 🤢 nauseated face face vomit gross green sick throw up ill 🤮 face vomiting face sick 🤧 sneezing face face gesundheit sneeze sick allergy 🥵 hot face face feverish heat red sweating 🥶 cold face face blue freezing frozen frostbite icicles 🥴 woozy face face dizzy intoxicated tipsy wavy 😵 dizzy face spent unconscious xox dizzy 🤯 exploding head face shocked mind blown 🤠 cowboy hat face face cowgirl hat 🥳 partying face face celebration woohoo 😎 smiling face with sunglasses face cool smile summer beach sunglass 🤓 nerd face face nerdy geek dork 🧐 face with monocle face stuffy wealthy 😕 confused face face indifference huh weird hmmm :/ 😟 worried face face concern nervous :( 🙁 slightly frowning face face frowning disappointed sad upset ☹️ frowning face face sad upset frown 😮 face with open mouth face surprise impressed wow whoa :O 😯 hushed face face woo shh 😲 astonished face face xox surprised poisoned 😳 flushed face face blush shy flattered sex 🥺 pleading face face begging mercy 😦 frowning face with open mouth face aw what 😧 anguished face face stunned nervous 😨 fearful face face scared terrified nervous oops huh 😰 anxious face with sweat face nervous sweat 😥 sad but relieved face face phew sweat nervous 😢 crying face face tears sad depressed upset :'( 😭 loudly crying face face cry tears sad upset depressed sob 😱 face screaming in fear face munch scared omg 😖 confounded face face confused sick unwell oops :S 😣 persevering face face sick no upset oops 😞 disappointed face face sad upset depressed :( 😓 downcast face with sweat face hot sad tired exercise 😩 weary face face tired sleepy sad frustrated upset 😫 tired face sick whine upset frustrated 🥱 yawning face tired sleepy 😤 face with steam from nose face gas phew proud pride 😡 pouting face angry mad hate despise 😠 angry face mad face annoyed frustrated 🤬 face with symbols on mouth face swearing cursing cussing profanity expletive 😈 smiling face with horns devil horns 👿 angry face with horns devil angry horns 💀 skull dead skeleton creepy death ☠️ skull and crossbones poison danger deadly scary death pirate evil 💩 pile of poo hankey shitface fail turd shit 🤡 clown face face 👹 ogre monster red mask halloween scary creepy devil demon japanese ogre 👺 goblin red evil mask monster scary creepy japanese goblin 👻 ghost halloween spooky scary 👽 alien UFO paul weird outer space 👾 alien monster game arcade play 🤖 robot computer machine bot 😺 grinning cat animal cats happy smile 😸 grinning cat with smiling eyes animal cats smile 😹 cat with tears of joy animal cats haha happy tears 😻 smiling cat with heart eyes animal love like affection cats valentines heart 😼 cat with wry smile animal cats smirk 😽 kissing cat animal cats kiss 🙀 weary cat animal cats munch scared scream 😿 crying cat animal tears weep sad cats upset cry 😾 pouting cat animal cats 🙈 see no evil monkey monkey animal nature haha 🙉 hear no evil monkey animal monkey nature 🙊 speak no evil monkey monkey animal nature omg 💋 kiss mark face lips love like affection valentines 💌 love letter email like affection envelope valentines 💘 heart with arrow love like heart affection valentines 💝 heart with ribbon love valentines 💖 sparkling heart love like affection valentines 💗 growing heart like love affection valentines pink 💓 beating heart love like affection valentines pink heart 💞 revolving hearts love like affection valentines 💕 two hearts love like affection valentines heart 💟 heart decoration purple-square love like ❣️ heart exclamation decoration love 💔 broken heart sad sorry break heart heartbreak ❤️ red heart love like valentines 🧡 orange heart love like affection valentines 💛 yellow heart love like affection valentines 💚 green heart love like affection valentines 💙 blue heart love like affection valentines 💜 purple heart love like affection valentines 🤎 brown heart coffee 🖤 black heart evil 🤍 white heart pure 💯 hundred points score perfect numbers century exam quiz test pass hundred 💢 anger symbol angry mad 💥 collision bomb explode explosion collision blown 💫 dizzy star sparkle shoot magic 💦 sweat droplets water drip oops 💨 dashing away wind air fast shoo fart smoke puff 🕳️ hole embarrassing 💣 bomb boom explode explosion terrorism 💬 speech balloon bubble words message talk chatting 👁️‍🗨️ eye in speech bubble info 🗨️ left speech bubble words message talk chatting 🗯️ right anger bubble caption speech thinking mad 💭 thought balloon bubble cloud speech thinking dream 💤 zzz sleepy tired dream 👋 waving hand hands gesture goodbye solong farewell hello hi palm 🤚 raised back of hand fingers raised backhand 🖐️ hand with fingers splayed hand fingers palm ✋ raised hand fingers stop highfive palm ban 🖖 vulcan salute hand fingers spock star trek 👌 ok hand fingers limbs perfect ok okay 🤏 pinching hand tiny small size ✌️ victory hand fingers ohyeah hand peace victory two 🤞 crossed fingers good lucky 🤟 love you gesture hand fingers gesture 🤘 sign of the horns hand fingers evil eye sign of horns rock on 🤙 call me hand hands gesture shaka 👈 backhand index pointing left direction fingers hand left 👉 backhand index pointing right fingers hand direction right 👆 backhand index pointing up fingers hand direction up 🖕 middle finger hand fingers rude middle flipping 👇 backhand index pointing down fingers hand direction down ☝️ index pointing up hand fingers direction up 👍 thumbs up thumbsup yes awesome good agree accept cool hand like +1 👎 thumbs down thumbsdown no dislike hand -1 ✊ raised fist fingers hand grasp 👊 oncoming fist angry violence fist hit attack hand 🤛 left facing fist hand fistbump 🤜 right facing fist hand fistbump 👏 clapping hands hands praise applause congrats yay 🙌 raising hands gesture hooray yea celebration hands 👐 open hands fingers butterfly hands open 🤲 palms up together hands gesture cupped prayer 🤝 handshake agreement shake 🙏 folded hands please hope wish namaste highfive pray ✍️ writing hand lower left ballpoint pen stationery write compose 💅 nail polish beauty manicure finger fashion nail 🤳 selfie camera phone 💪 flexed biceps arm flex hand summer strong biceps 🦾 mechanical arm accessibility 🦿 mechanical leg accessibility 🦵 leg kick limb 🦶 foot kick stomp 👂 ear face hear sound listen 🦻 ear with hearing aid accessibility 👃 nose smell sniff 🧠 brain smart intelligent 🦷 tooth teeth dentist 🦴 bone skeleton 👀 eyes look watch stalk peek see 👁️ eye face look see watch stare 👅 tongue mouth playful 👄 mouth mouth kiss 👶 baby child boy girl toddler 🧒 child gender-neutral young 👦 boy man male guy teenager 👧 girl female woman teenager 🧑 person gender-neutral person 👱 person blond hair hairstyle 👨 man mustache father dad guy classy sir moustache 🧔 man beard person bewhiskered 👨‍🦰 man red hair hairstyle 👨‍🦱 man curly hair hairstyle 👨‍🦳 man white hair old elder 👨‍🦲 man bald hairless 👩 woman female girls lady 👩‍🦰 woman red hair hairstyle 🧑‍🦰 person red hair hairstyle 👩‍🦱 woman curly hair hairstyle 🧑‍🦱 person curly hair hairstyle 👩‍🦳 woman white hair old elder 🧑‍🦳 person white hair elder old 👩‍🦲 woman bald hairless 🧑‍🦲 person bald hairless 👱‍♀️ woman blond hair woman female girl blonde person 👱‍♂️ man blond hair man male boy blonde guy person 🧓 older person human elder senior gender-neutral 👴 old man human male men old elder senior 👵 old woman human female women lady old elder senior 🙍 person frowning worried 🙍‍♂️ man frowning male boy man sad depressed discouraged unhappy 🙍‍♀️ woman frowning female girl woman sad depressed discouraged unhappy 🙎 person pouting upset 🙎‍♂️ man pouting male boy man 🙎‍♀️ woman pouting female girl woman 🙅 person gesturing no decline 🙅‍♂️ man gesturing no male boy man nope 🙅‍♀️ woman gesturing no female girl woman nope 🙆 person gesturing ok agree 🙆‍♂️ man gesturing ok men boy male blue human man 🙆‍♀️ woman gesturing ok women girl female pink human woman 💁 person tipping hand information 💁‍♂️ man tipping hand male boy man human information 💁‍♀️ woman tipping hand female girl woman human information 🙋 person raising hand question 🙋‍♂️ man raising hand male boy man 🙋‍♀️ woman raising hand female girl woman 🧏 deaf person accessibility 🧏‍♂️ deaf man accessibility 🧏‍♀️ deaf woman accessibility 🙇 person bowing respectiful 🙇‍♂️ man bowing man male boy 🙇‍♀️ woman bowing woman female girl 🤦 person facepalming disappointed 🤦‍♂️ man facepalming man male boy disbelief 🤦‍♀️ woman facepalming woman female girl disbelief 🤷 person shrugging regardless 🤷‍♂️ man shrugging man male boy confused indifferent doubt 🤷‍♀️ woman shrugging woman female girl confused indifferent doubt 🧑‍⚕️ health worker hospital 👨‍⚕️ man health worker doctor nurse therapist healthcare man human 👩‍⚕️ woman health worker doctor nurse therapist healthcare woman human 🧑‍🎓 student learn 👨‍🎓 man student graduate man human 👩‍🎓 woman student graduate woman human 🧑‍🏫 teacher professor 👨‍🏫 man teacher instructor professor man human 👩‍🏫 woman teacher instructor professor woman human 🧑‍⚖️ judge law 👨‍⚖️ man judge justice court man human 👩‍⚖️ woman judge justice court woman human 🧑‍🌾 farmer crops 👨‍🌾 man farmer rancher gardener man human 👩‍🌾 woman farmer rancher gardener woman human 🧑‍🍳 cook food kitchen culinary 👨‍🍳 man cook chef man human 👩‍🍳 woman cook chef woman human 🧑‍🔧 mechanic worker technician 👨‍🔧 man mechanic plumber man human wrench 👩‍🔧 woman mechanic plumber woman human wrench 🧑‍🏭 factory worker labor 👨‍🏭 man factory worker assembly industrial man human 👩‍🏭 woman factory worker assembly industrial woman human 🧑‍💼 office worker business 👨‍💼 man office worker business manager man human 👩‍💼 woman office worker business manager woman human 🧑‍🔬 scientist chemistry 👨‍🔬 man scientist biologist chemist engineer physicist man human 👩‍🔬 woman scientist biologist chemist engineer physicist woman human 🧑‍💻 technologist computer 👨‍💻 man technologist coder developer engineer programmer software man human laptop computer 👩‍💻 woman technologist coder developer engineer programmer software woman human laptop computer 🧑‍🎤 singer song artist performer 👨‍🎤 man singer rockstar entertainer man human 👩‍🎤 woman singer rockstar entertainer woman human 🧑‍🎨 artist painting draw creativity 👨‍🎨 man artist painter man human 👩‍🎨 woman artist painter woman human 🧑‍✈️ pilot fly plane airplane 👨‍✈️ man pilot aviator plane man human 👩‍✈️ woman pilot aviator plane woman human 🧑‍🚀 astronaut outerspace 👨‍🚀 man astronaut space rocket man human 👩‍🚀 woman astronaut space rocket woman human 🧑‍🚒 firefighter fire 👨‍🚒 man firefighter fireman man human 👩‍🚒 woman firefighter fireman woman human 👮 police officer cop 👮‍♂️ man police officer man police law legal enforcement arrest 911 👮‍♀️ woman police officer woman police law legal enforcement arrest 911 female 🕵️ detective human spy detective 🕵️‍♂️ man detective crime 🕵️‍♀️ woman detective human spy detective female woman 💂 guard protect 💂‍♂️ man guard uk gb british male guy royal 💂‍♀️ woman guard uk gb british female royal woman 👷 construction worker labor build 👷‍♂️ man construction worker male human wip guy build construction worker labor 👷‍♀️ woman construction worker female human wip build construction worker labor woman 🤴 prince boy man male crown royal king 👸 princess girl woman female blond crown royal queen 👳 person wearing turban headdress 👳‍♂️ man wearing turban male indian hinduism arabs 👳‍♀️ woman wearing turban female indian hinduism arabs woman 👲 man with skullcap male boy chinese 🧕 woman with headscarf female hijab mantilla tichel 🤵 man in tuxedo couple marriage wedding groom 👰 bride with veil couple marriage wedding woman bride 🤰 pregnant woman baby 🤱 breast feeding nursing baby 👼 baby angel heaven wings halo 🎅 santa claus festival man male xmas father christmas 🤶 mrs claus woman female xmas mother christmas 🦸 superhero marvel 🦸‍♂️ man superhero man male good hero superpowers 🦸‍♀️ woman superhero woman female good heroine superpowers 🦹 supervillain marvel 🦹‍♂️ man supervillain man male evil bad criminal hero superpowers 🦹‍♀️ woman supervillain woman female evil bad criminal heroine superpowers 🧙 mage magic 🧙‍♂️ man mage man male mage sorcerer 🧙‍♀️ woman mage woman female mage witch 🧚 fairy wings magical 🧚‍♂️ man fairy man male 🧚‍♀️ woman fairy woman female 🧛 vampire blood twilight 🧛‍♂️ man vampire man male dracula 🧛‍♀️ woman vampire woman female 🧜 merperson sea 🧜‍♂️ merman man male triton 🧜‍♀️ mermaid woman female merwoman ariel 🧝 elf magical 🧝‍♂️ man elf man male 🧝‍♀️ woman elf woman female 🧞 genie magical wishes 🧞‍♂️ man genie man male 🧞‍♀️ woman genie woman female 🧟 zombie dead 🧟‍♂️ man zombie man male dracula undead walking dead 🧟‍♀️ woman zombie woman female undead walking dead 💆 person getting massage relax 💆‍♂️ man getting massage male boy man head 💆‍♀️ woman getting massage female girl woman head 💇 person getting haircut hairstyle 💇‍♂️ man getting haircut male boy man 💇‍♀️ woman getting haircut female girl woman 🚶 person walking move 🚶‍♂️ man walking human feet steps 🚶‍♀️ woman walking human feet steps woman female 🧍 person standing still 🧍‍♂️ man standing still 🧍‍♀️ woman standing still 🧎 person kneeling pray respectful 🧎‍♂️ man kneeling pray respectful 🧎‍♀️ woman kneeling respectful pray 🧑‍🦯 person with probing cane blind 👨‍🦯 man with probing cane blind 👩‍🦯 woman with probing cane blind 🧑‍🦼 person in motorized wheelchair disability accessibility 👨‍🦼 man in motorized wheelchair disability accessibility 👩‍🦼 woman in motorized wheelchair disability accessibility 🧑‍🦽 person in manual wheelchair disability accessibility 👨‍🦽 man in manual wheelchair disability accessibility 👩‍🦽 woman in manual wheelchair disability accessibility 🏃 person running move 🏃‍♂️ man running man walking exercise race running 🏃‍♀️ woman running woman walking exercise race running female 💃 woman dancing female girl woman fun 🕺 man dancing male boy fun dancer 🕴️ man in suit levitating suit business levitate hover jump 👯 people with bunny ears perform costume 👯‍♂️ men with bunny ears male bunny men boys 👯‍♀️ women with bunny ears female bunny women girls 🧖 person in steamy room relax spa 🧖‍♂️ man in steamy room male man spa steamroom sauna 🧖‍♀️ woman in steamy room female woman spa steamroom sauna 🧗 person climbing sport 🧗‍♂️ man climbing sports hobby man male rock 🧗‍♀️ woman climbing sports hobby woman female rock 🤺 person fencing sports fencing sword 🏇 horse racing animal betting competition gambling luck ⛷️ skier sports winter snow 🏂 snowboarder sports winter 🏌️ person golfing sports business 🏌️‍♂️ man golfing sport 🏌️‍♀️ woman golfing sports business woman female 🏄 person surfing sport sea 🏄‍♂️ man surfing sports ocean sea summer beach 🏄‍♀️ woman surfing sports ocean sea summer beach woman female 🚣 person rowing boat sport move 🚣‍♂️ man rowing boat sports hobby water ship 🚣‍♀️ woman rowing boat sports hobby water ship woman female 🏊 person swimming sport pool 🏊‍♂️ man swimming sports exercise human athlete water summer 🏊‍♀️ woman swimming sports exercise human athlete water summer woman female ⛹️ person bouncing ball sports human ⛹️‍♂️ man bouncing ball sport ⛹️‍♀️ woman bouncing ball sports human woman female 🏋️ person lifting weights sports training exercise 🏋️‍♂️ man lifting weights sport 🏋️‍♀️ woman lifting weights sports training exercise woman female 🚴 person biking sport move 🚴‍♂️ man biking sports bike exercise hipster 🚴‍♀️ woman biking sports bike exercise hipster woman female 🚵 person mountain biking sport move 🚵‍♂️ man mountain biking transportation sports human race bike 🚵‍♀️ woman mountain biking transportation sports human race bike woman female 🤸 person cartwheeling sport gymnastic 🤸‍♂️ man cartwheeling gymnastics 🤸‍♀️ woman cartwheeling gymnastics 🤼 people wrestling sport 🤼‍♂️ men wrestling sports wrestlers 🤼‍♀️ women wrestling sports wrestlers 🤽 person playing water polo sport 🤽‍♂️ man playing water polo sports pool 🤽‍♀️ woman playing water polo sports pool 🤾 person playing handball sport 🤾‍♂️ man playing handball sports 🤾‍♀️ woman playing handball sports 🤹 person juggling performance balance 🤹‍♂️ man juggling juggle balance skill multitask 🤹‍♀️ woman juggling juggle balance skill multitask 🧘 person in lotus position meditate 🧘‍♂️ man in lotus position man male meditation yoga serenity zen mindfulness 🧘‍♀️ woman in lotus position woman female meditation yoga serenity zen mindfulness 🛀 person taking bath clean shower bathroom 🛌 person in bed bed rest 🧑‍🤝‍🧑 people holding hands friendship 👭 women holding hands pair friendship couple love like female people human 👫 woman and man holding hands pair people human love date dating like affection valentines marriage 👬 men holding hands pair couple love like bromance friendship people human 💏 kiss pair valentines love like dating marriage 👩‍❤️‍💋‍👨 kiss woman man love 👨‍❤️‍💋‍👨 kiss man man pair valentines love like dating marriage 👩‍❤️‍💋‍👩 kiss woman woman pair valentines love like dating marriage 💑 couple with heart pair love like affection human dating valentines marriage 👩‍❤️‍👨 couple with heart woman man love 👨‍❤️‍👨 couple with heart man man pair love like affection human dating valentines marriage 👩‍❤️‍👩 couple with heart woman woman pair love like affection human dating valentines marriage 👪 family home parents child mom dad father mother people human 👨‍👩‍👦 family man woman boy love 👨‍👩‍👧 family man woman girl home parents people human child 👨‍👩‍👧‍👦 family man woman girl boy home parents people human children 👨‍👩‍👦‍👦 family man woman boy boy home parents people human children 👨‍👩‍👧‍👧 family man woman girl girl home parents people human children 👨‍👨‍👦 family man man boy home parents people human children 👨‍👨‍👧 family man man girl home parents people human children 👨‍👨‍👧‍👦 family man man girl boy home parents people human children 👨‍👨‍👦‍👦 family man man boy boy home parents people human children 👨‍👨‍👧‍👧 family man man girl girl home parents people human children 👩‍👩‍👦 family woman woman boy home parents people human children 👩‍👩‍👧 family woman woman girl home parents people human children 👩‍👩‍👧‍👦 family woman woman girl boy home parents people human children 👩‍👩‍👦‍👦 family woman woman boy boy home parents people human children 👩‍👩‍👧‍👧 family woman woman girl girl home parents people human children 👨‍👦 family man boy home parent people human child 👨‍👦‍👦 family man boy boy home parent people human children 👨‍👧 family man girl home parent people human child 👨‍👧‍👦 family man girl boy home parent people human children 👨‍👧‍👧 family man girl girl home parent people human children 👩‍👦 family woman boy home parent people human child 👩‍👦‍👦 family woman boy boy home parent people human children 👩‍👧 family woman girl home parent people human child 👩‍👧‍👦 family woman girl boy home parent people human children 👩‍👧‍👧 family woman girl girl home parent people human children 🗣️ speaking head user person human sing say talk 👤 bust in silhouette user person human 👥 busts in silhouette user person human group team 👣 footprints feet tracking walking beach 🐵 monkey face animal nature circus 🐒 monkey animal nature banana circus 🦍 gorilla animal nature circus 🦧 orangutan animal 🐶 dog face animal friend nature woof puppy pet faithful 🐕 dog animal nature friend doge pet faithful 🦮 guide dog animal blind 🐕‍🦺 service dog blind animal 🐩 poodle dog animal 101 nature pet 🐺 wolf animal nature wild 🦊 fox animal nature face 🦝 raccoon animal nature 🐱 cat face animal meow nature pet kitten 🐈 cat animal meow pet cats 🦁 lion animal nature 🐯 tiger face animal cat danger wild nature roar 🐅 tiger animal nature roar 🐆 leopard animal nature 🐴 horse face animal brown nature 🐎 horse animal gamble luck 🦄 unicorn animal nature mystical 🦓 zebra animal nature stripes safari 🦌 deer animal nature horns venison 🐮 cow face beef ox animal nature moo milk 🐂 ox animal cow beef 🐃 water buffalo animal nature ox cow 🐄 cow beef ox animal nature moo milk 🐷 pig face animal oink nature 🐖 pig animal nature 🐗 boar animal nature 🐽 pig nose animal oink 🐏 ram animal sheep nature 🐑 ewe animal nature wool shipit 🐐 goat animal nature 🐪 camel animal hot desert hump 🐫 two hump camel animal nature hot desert hump 🦙 llama animal nature alpaca 🦒 giraffe animal nature spots safari 🐘 elephant animal nature nose th circus 🦏 rhinoceros animal nature horn 🦛 hippopotamus animal nature 🐭 mouse face animal nature cheese wedge rodent 🐁 mouse animal nature rodent 🐀 rat animal mouse rodent 🐹 hamster animal nature 🐰 rabbit face animal nature pet spring magic bunny 🐇 rabbit animal nature pet magic spring 🐿️ chipmunk animal nature rodent squirrel 🦔 hedgehog animal nature spiny 🦇 bat animal nature blind vampire 🐻 bear animal nature wild 🐨 koala animal nature 🐼 panda animal nature panda 🦥 sloth animal 🦦 otter animal 🦨 skunk animal 🦘 kangaroo animal nature australia joey hop marsupial 🦡 badger animal nature honey 🐾 paw prints animal tracking footprints dog cat pet feet 🦃 turkey animal bird 🐔 chicken animal cluck nature bird 🐓 rooster animal nature chicken 🐣 hatching chick animal chicken egg born baby bird 🐤 baby chick animal chicken bird 🐥 front facing baby chick animal chicken baby bird 🐦 bird animal nature fly tweet spring 🐧 penguin animal nature 🕊️ dove animal bird 🦅 eagle animal nature bird 🦆 duck animal nature bird mallard 🦢 swan animal nature bird 🦉 owl animal nature bird hoot 🦩 flamingo animal 🦚 peacock animal nature peahen bird 🦜 parrot animal nature bird pirate talk 🐸 frog animal nature croak toad 🐊 crocodile animal nature reptile lizard alligator 🐢 turtle animal slow nature tortoise 🦎 lizard animal nature reptile 🐍 snake animal evil nature hiss python 🐲 dragon face animal myth nature chinese green 🐉 dragon animal myth nature chinese green 🦕 sauropod animal nature dinosaur brachiosaurus brontosaurus diplodocus extinct 🦖 t rex animal nature dinosaur tyrannosaurus extinct 🐳 spouting whale animal nature sea ocean 🐋 whale animal nature sea ocean 🐬 dolphin animal nature fish sea ocean flipper fins beach 🐟 fish animal food nature 🐠 tropical fish animal swim ocean beach nemo 🐡 blowfish animal nature food sea ocean 🦈 shark animal nature fish sea ocean jaws fins beach 🐙 octopus animal creature ocean sea nature beach 🐚 spiral shell nature sea beach 🐌 snail slow animal shell 🦋 butterfly animal insect nature caterpillar 🐛 bug animal insect nature worm 🐜 ant animal insect nature bug 🐝 honeybee animal insect nature bug spring honey 🐞 lady beetle animal insect nature ladybug 🦗 cricket animal cricket chirp 🕷️ spider animal arachnid 🕸️ spider web animal insect arachnid silk 🦂 scorpion animal arachnid 🦟 mosquito animal nature insect malaria 🦠 microbe amoeba bacteria germs virus 💐 bouquet flowers nature spring 🌸 cherry blossom nature plant spring flower 💮 white flower japanese spring 🏵️ rosette flower decoration military 🌹 rose flowers valentines love spring 🥀 wilted flower plant nature flower 🌺 hibiscus plant vegetable flowers beach 🌻 sunflower nature plant fall 🌼 blossom nature flowers yellow 🌷 tulip flowers plant nature summer spring 🌱 seedling plant nature grass lawn spring 🌲 evergreen tree plant nature 🌳 deciduous tree plant nature 🌴 palm tree plant vegetable nature summer beach mojito tropical 🌵 cactus vegetable plant nature 🌾 sheaf of rice nature plant 🌿 herb vegetable plant medicine weed grass lawn ☘️ shamrock vegetable plant nature irish clover 🍀 four leaf clover vegetable plant nature lucky irish 🍁 maple leaf nature plant vegetable ca fall 🍂 fallen leaf nature plant vegetable leaves 🍃 leaf fluttering in wind nature plant tree vegetable grass lawn spring 🍇 grapes fruit food wine 🍈 melon fruit nature food 🍉 watermelon fruit food picnic summer 🍊 tangerine food fruit nature orange 🍋 lemon fruit nature 🍌 banana fruit food monkey 🍍 pineapple fruit nature food 🥭 mango fruit food tropical 🍎 red apple fruit mac school 🍏 green apple fruit nature 🍐 pear fruit nature food 🍑 peach fruit nature food 🍒 cherries food fruit 🍓 strawberry fruit food nature 🥝 kiwi fruit fruit food 🍅 tomato fruit vegetable nature food 🥥 coconut fruit nature food palm 🥑 avocado fruit food 🍆 eggplant vegetable nature food aubergine 🥔 potato food tuber vegatable starch 🥕 carrot vegetable food orange 🌽 ear of corn food vegetable plant 🌶️ hot pepper food spicy chilli chili 🥒 cucumber fruit food pickle 🥬 leafy green food vegetable plant bok choy cabbage kale lettuce 🥦 broccoli fruit food vegetable 🧄 garlic food spice cook 🧅 onion cook food spice 🍄 mushroom plant vegetable 🥜 peanuts food nut 🌰 chestnut food squirrel 🍞 bread food wheat breakfast toast 🥐 croissant food bread french 🥖 baguette bread food bread french 🥨 pretzel food bread twisted 🥯 bagel food bread bakery schmear 🥞 pancakes food breakfast flapjacks hotcakes 🧇 waffle food breakfast 🧀 cheese wedge food chadder 🍖 meat on bone good food drumstick 🍗 poultry leg food meat drumstick bird chicken turkey 🥩 cut of meat food cow meat cut chop lambchop porkchop 🥓 bacon food breakfast pork pig meat 🍔 hamburger meat fast food beef cheeseburger mcdonalds burger king 🍟 french fries chips snack fast food 🍕 pizza food party 🌭 hot dog food frankfurter 🥪 sandwich food lunch bread 🌮 taco food mexican 🌯 burrito food mexican 🥙 stuffed flatbread food flatbread stuffed gyro 🧆 falafel food 🥚 egg food chicken breakfast 🍳 cooking food breakfast kitchen egg 🥘 shallow pan of food food cooking casserole paella 🍲 pot of food food meat soup 🥣 bowl with spoon food breakfast cereal oatmeal porridge 🥗 green salad food healthy lettuce 🍿 popcorn food movie theater films snack 🧈 butter food cook 🧂 salt condiment shaker 🥫 canned food food soup 🍱 bento box food japanese box 🍘 rice cracker food japanese 🍙 rice ball food japanese 🍚 cooked rice food china asian 🍛 curry rice food spicy hot indian 🍜 steaming bowl food japanese noodle chopsticks 🍝 spaghetti food italian noodle 🍠 roasted sweet potato food nature 🍢 oden food japanese 🍣 sushi food fish japanese rice 🍤 fried shrimp food animal appetizer summer 🍥 fish cake with swirl food japan sea beach narutomaki pink swirl kamaboko surimi ramen 🥮 moon cake food autumn 🍡 dango food dessert sweet japanese barbecue meat 🥟 dumpling food empanada pierogi potsticker 🥠 fortune cookie food prophecy 🥡 takeout box food leftovers 🦀 crab animal crustacean 🦞 lobster animal nature bisque claws seafood 🦐 shrimp animal ocean nature seafood 🦑 squid animal nature ocean sea 🦪 oyster food 🍦 soft ice cream food hot dessert summer 🍧 shaved ice hot dessert summer 🍨 ice cream food hot dessert 🍩 doughnut food dessert snack sweet donut 🍪 cookie food snack oreo chocolate sweet dessert 🎂 birthday cake food dessert cake 🍰 shortcake food dessert 🧁 cupcake food dessert bakery sweet 🥧 pie food dessert pastry 🍫 chocolate bar food snack dessert sweet 🍬 candy snack dessert sweet lolly 🍭 lollipop food snack candy sweet 🍮 custard dessert food 🍯 honey pot bees sweet kitchen 🍼 baby bottle food container milk 🥛 glass of milk beverage drink cow ☕ hot beverage beverage caffeine latte espresso coffee 🍵 teacup without handle drink bowl breakfast green british 🍶 sake wine drink drunk beverage japanese alcohol booze 🍾 bottle with popping cork drink wine bottle celebration 🍷 wine glass drink beverage drunk alcohol booze 🍸 cocktail glass drink drunk alcohol beverage booze mojito 🍹 tropical drink beverage cocktail summer beach alcohol booze mojito 🍺 beer mug relax beverage drink drunk party pub summer alcohol booze 🍻 clinking beer mugs relax beverage drink drunk party pub summer alcohol booze 🥂 clinking glasses beverage drink party alcohol celebrate cheers wine champagne toast 🥃 tumbler glass drink beverage drunk alcohol liquor booze bourbon scotch whisky glass shot 🥤 cup with straw drink soda 🧃 beverage box drink 🧉 mate drink tea beverage 🧊 ice water cold 🥢 chopsticks food 🍽️ fork and knife with plate food eat meal lunch dinner restaurant 🍴 fork and knife cutlery kitchen 🥄 spoon cutlery kitchen tableware 🔪 kitchen knife knife blade cutlery kitchen weapon 🏺 amphora vase jar 🌍 globe showing europe africa globe world international 🌎 globe showing americas globe world USA international 🌏 globe showing asia australia globe world east international 🌐 globe with meridians earth international world internet interweb i18n 🗺️ world map location direction 🗾 map of japan nation country japanese asia 🧭 compass magnetic navigation orienteering 🏔️ snow capped mountain photo nature environment winter cold ⛰️ mountain photo nature environment 🌋 volcano photo nature disaster 🗻 mount fuji photo mountain nature japanese 🏕️ camping photo outdoors tent 🏖️ beach with umbrella weather summer sunny sand mojito 🏜️ desert photo warm saharah 🏝️ desert island photo tropical mojito 🏞️ national park photo environment nature 🏟️ stadium photo place sports concert venue 🏛️ classical building art culture history 🏗️ building construction wip working progress 🧱 brick bricks 🏘️ houses buildings photo 🏚️ derelict house abandon evict broken building 🏠 house building home 🏡 house with garden home plant nature 🏢 office building building bureau work 🏣 japanese post office building envelope communication 🏤 post office building email 🏥 hospital building health surgery doctor 🏦 bank building money sales cash business enterprise 🏨 hotel building accomodation checkin 🏩 love hotel like affection dating 🏪 convenience store building shopping groceries 🏫 school building student education learn teach 🏬 department store building shopping mall 🏭 factory building industry pollution smoke 🏯 japanese castle photo building 🏰 castle building royalty history 💒 wedding love like affection couple marriage bride groom 🗼 tokyo tower photo japanese 🗽 statue of liberty american newyork ⛪ church building religion christ 🕌 mosque islam worship minaret 🛕 hindu temple religion 🕍 synagogue judaism worship temple jewish ⛩️ shinto shrine temple japan kyoto 🕋 kaaba mecca mosque islam ⛲ fountain photo summer water fresh ⛺ tent photo camping outdoors 🌁 foggy photo mountain 🌃 night with stars evening city downtown 🏙️ cityscape photo night life urban 🌄 sunrise over mountains view vacation photo 🌅 sunrise morning view vacation photo 🌆 cityscape at dusk photo evening sky buildings 🌇 sunset photo good morning dawn 🌉 bridge at night photo sanfrancisco ♨️ hot springs bath warm relax 🎠 carousel horse photo carnival 🎡 ferris wheel photo carnival londoneye 🎢 roller coaster carnival playground photo fun 💈 barber pole hair salon style 🎪 circus tent festival carnival party 🚂 locomotive transportation vehicle train 🚃 railway car transportation vehicle 🚄 high speed train transportation vehicle 🚅 bullet train transportation vehicle speed fast public travel 🚆 train transportation vehicle 🚇 metro transportation blue-square mrt underground tube 🚈 light rail transportation vehicle 🚉 station transportation vehicle public 🚊 tram transportation vehicle 🚝 monorail transportation vehicle 🚞 mountain railway transportation vehicle 🚋 tram car transportation vehicle carriage public travel 🚌 bus car vehicle transportation 🚍 oncoming bus vehicle transportation 🚎 trolleybus bart transportation vehicle 🚐 minibus vehicle car transportation 🚑 ambulance health 911 hospital 🚒 fire engine transportation cars vehicle 🚓 police car vehicle cars transportation law legal enforcement 🚔 oncoming police car vehicle law legal enforcement 911 🚕 taxi uber vehicle cars transportation 🚖 oncoming taxi vehicle cars uber 🚗 automobile red transportation vehicle 🚘 oncoming automobile car vehicle transportation 🚙 sport utility vehicle transportation vehicle 🚚 delivery truck cars transportation 🚛 articulated lorry vehicle cars transportation express 🚜 tractor vehicle car farming agriculture 🏎️ racing car sports race fast formula f1 🏍️ motorcycle race sports fast 🛵 motor scooter vehicle vespa sasha 🦽 manual wheelchair accessibility 🦼 motorized wheelchair accessibility 🛺 auto rickshaw move transportation 🚲 bicycle sports bicycle exercise hipster 🛴 kick scooter vehicle kick razor 🛹 skateboard board 🚏 bus stop transportation wait 🛣️ motorway road cupertino interstate highway 🛤️ railway track train transportation 🛢️ oil drum barrell ⛽ fuel pump gas station petroleum 🚨 police car light police ambulance 911 emergency alert error pinged law legal 🚥 horizontal traffic light transportation signal 🚦 vertical traffic light transportation driving 🛑 stop sign stop 🚧 construction wip progress caution warning ⚓ anchor ship ferry sea boat ⛵ sailboat ship summer transportation water sailing 🛶 canoe boat paddle water ship 🚤 speedboat ship transportation vehicle summer 🛳️ passenger ship yacht cruise ferry ⛴️ ferry boat ship yacht 🛥️ motor boat ship 🚢 ship transportation titanic deploy ✈️ airplane vehicle transportation flight fly 🛩️ small airplane flight transportation fly vehicle 🛫 airplane departure airport flight landing 🛬 airplane arrival airport flight boarding 🪂 parachute fly glide 💺 seat sit airplane transport bus flight fly 🚁 helicopter transportation vehicle fly 🚟 suspension railway vehicle transportation 🚠 mountain cableway transportation vehicle ski 🚡 aerial tramway transportation vehicle ski 🛰️ satellite communication gps orbit spaceflight NASA ISS 🚀 rocket launch ship staffmode NASA outer space outer space fly 🛸 flying saucer transportation vehicle ufo 🛎️ bellhop bell service 🧳 luggage packing travel ⌛ hourglass done time clock oldschool limit exam quiz test ⏳ hourglass not done oldschool time countdown ⌚ watch time accessories ⏰ alarm clock time wake ⏱️ stopwatch time deadline ⏲️ timer clock alarm 🕰️ mantelpiece clock time 🕛 twelve o clock time noon midnight midday late early schedule 🕧 twelve thirty time late early schedule 🕐 one o clock time late early schedule 🕜 one thirty time late early schedule 🕑 two o clock time late early schedule 🕝 two thirty time late early schedule 🕒 three o clock time late early schedule 🕞 three thirty time late early schedule 🕓 four o clock time late early schedule 🕟 four thirty time late early schedule 🕔 five o clock time late early schedule 🕠 five thirty time late early schedule 🕕 six o clock time late early schedule dawn dusk 🕡 six thirty time late early schedule 🕖 seven o clock time late early schedule 🕢 seven thirty time late early schedule 🕗 eight o clock time late early schedule 🕣 eight thirty time late early schedule 🕘 nine o clock time late early schedule 🕤 nine thirty time late early schedule 🕙 ten o clock time late early schedule 🕥 ten thirty time late early schedule 🕚 eleven o clock time late early schedule 🕦 eleven thirty time late early schedule 🌑 new moon nature twilight planet space night evening sleep 🌒 waxing crescent moon nature twilight planet space night evening sleep 🌓 first quarter moon nature twilight planet space night evening sleep 🌔 waxing gibbous moon nature night sky gray twilight planet space evening sleep 🌕 full moon nature yellow twilight planet space night evening sleep 🌖 waning gibbous moon nature twilight planet space night evening sleep waxing gibbous moon 🌗 last quarter moon nature twilight planet space night evening sleep 🌘 waning crescent moon nature twilight planet space night evening sleep 🌙 crescent moon night sleep sky evening magic 🌚 new moon face nature twilight planet space night evening sleep 🌛 first quarter moon face nature twilight planet space night evening sleep 🌜 last quarter moon face nature twilight planet space night evening sleep 🌡️ thermometer weather temperature hot cold ☀️ sun weather nature brightness summer beach spring 🌝 full moon face nature twilight planet space night evening sleep 🌞 sun with face nature morning sky 🪐 ringed planet outerspace ⭐ star night yellow 🌟 glowing star night sparkle awesome good magic 🌠 shooting star night photo 🌌 milky way photo space stars ☁️ cloud weather sky ⛅ sun behind cloud weather nature cloudy morning fall spring ⛈️ cloud with lightning and rain weather lightning 🌤️ sun behind small cloud weather 🌥️ sun behind large cloud weather 🌦️ sun behind rain cloud weather 🌧️ cloud with rain weather 🌨️ cloud with snow weather 🌩️ cloud with lightning weather thunder 🌪️ tornado weather cyclone twister 🌫️ fog weather 🌬️ wind face gust air 🌀 cyclone weather swirl blue cloud vortex spiral whirlpool spin tornado hurricane typhoon 🌈 rainbow nature happy unicorn face photo sky spring 🌂 closed umbrella weather rain drizzle ☂️ umbrella weather spring ☔ umbrella with rain drops rainy weather spring ⛱️ umbrella on ground weather summer ⚡ high voltage thunder weather lightning bolt fast ❄️ snowflake winter season cold weather christmas xmas ☃️ snowman winter season cold weather christmas xmas frozen ⛄ snowman without snow winter season cold weather christmas xmas frozen without snow ☄️ comet space 🔥 fire hot cook flame 💧 droplet water drip faucet spring 🌊 water wave sea water wave nature tsunami disaster 🎃 jack o lantern halloween light pumpkin creepy fall 🎄 christmas tree festival vacation december xmas celebration 🎆 fireworks photo festival carnival congratulations 🎇 sparkler stars night shine 🧨 firecracker dynamite boom explode explosion explosive ✨ sparkles stars shine shiny cool awesome good magic 🎈 balloon party celebration birthday circus 🎉 party popper party congratulations birthday magic circus celebration tada 🎊 confetti ball festival party birthday circus 🎋 tanabata tree plant nature branch summer 🎍 pine decoration plant nature vegetable panda pine decoration 🎎 japanese dolls japanese toy kimono 🎏 carp streamer fish japanese koinobori carp banner 🎐 wind chime nature ding spring bell 🎑 moon viewing ceremony photo japan asia tsukimi 🧧 red envelope gift 🎀 ribbon decoration pink girl bowtie 🎁 wrapped gift present birthday christmas xmas 🎗️ reminder ribbon sports cause support awareness 🎟️ admission tickets sports concert entrance 🎫 ticket event concert pass 🎖️ military medal award winning army 🏆 trophy win award contest place ftw ceremony 🏅 sports medal award winning 🥇 1st place medal award winning first 🥈 2nd place medal award second 🥉 3rd place medal award third ⚽ soccer ball sports football ⚾ baseball sports balls 🥎 softball sports balls 🏀 basketball sports balls NBA 🏐 volleyball sports balls 🏈 american football sports balls NFL 🏉 rugby football sports team 🎾 tennis sports balls green 🥏 flying disc sports frisbee ultimate 🎳 bowling sports fun play 🏏 cricket game sports 🏑 field hockey sports 🏒 ice hockey sports 🥍 lacrosse sports ball stick 🏓 ping pong sports pingpong 🏸 badminton sports 🥊 boxing glove sports fighting 🥋 martial arts uniform judo karate taekwondo 🥅 goal net sports ⛳ flag in hole sports business flag hole summer ⛸️ ice skate sports 🎣 fishing pole food hobby summer 🤿 diving mask sport ocean 🎽 running shirt play pageant 🎿 skis sports winter cold snow 🛷 sled sleigh luge toboggan 🥌 curling stone sports 🎯 direct hit game play bar target bullseye 🪀 yo yo toy 🪁 kite wind fly 🎱 pool 8 ball pool hobby game luck magic 🔮 crystal ball disco party magic circus fortune teller 🧿 nazar amulet bead charm 🎮 video game play console PS4 Wii GameCube controller 🕹️ joystick game play 🎰 slot machine bet gamble vegas fruit machine luck casino 🎲 game die dice random tabletop play luck 🧩 puzzle piece interlocking puzzle piece 🧸 teddy bear plush stuffed ♠️ spade suit poker cards suits magic ♥️ heart suit poker cards magic suits ♦️ diamond suit poker cards magic suits ♣️ club suit poker cards magic suits ♟️ chess pawn expendable 🃏 joker poker cards game play magic 🀄 mahjong red dragon game play chinese kanji 🎴 flower playing cards game sunset red 🎭 performing arts acting theater drama 🖼️ framed picture photography 🎨 artist palette design paint draw colors 🧵 thread needle sewing spool string 🧶 yarn ball crochet knit 👓 glasses fashion accessories eyesight nerdy dork geek 🕶️ sunglasses face cool accessories 🥽 goggles eyes protection safety 🥼 lab coat doctor experiment scientist chemist 🦺 safety vest protection 👔 necktie shirt suitup formal fashion cloth business 👕 t shirt fashion cloth casual shirt tee 👖 jeans fashion shopping 🧣 scarf neck winter clothes 🧤 gloves hands winter clothes 🧥 coat jacket 🧦 socks stockings clothes 👗 dress clothes fashion shopping 👘 kimono dress fashion women female japanese 🥻 sari dress 🩱 one piece swimsuit fashion 🩲 briefs clothing 🩳 shorts clothing 👙 bikini swimming female woman girl fashion beach summer 👚 woman s clothes fashion shopping bags female 👛 purse fashion accessories money sales shopping 👜 handbag fashion accessory accessories shopping 👝 clutch bag bag accessories shopping 🛍️ shopping bags mall buy purchase 🎒 backpack student education bag backpack 👞 man s shoe fashion male 👟 running shoe shoes sports sneakers 🥾 hiking boot backpacking camping hiking 🥿 flat shoe ballet slip-on slipper 👠 high heeled shoe fashion shoes female pumps stiletto 👡 woman s sandal shoes fashion flip flops 🩰 ballet shoes dance 👢 woman s boot shoes fashion 👑 crown king kod leader royalty lord 👒 woman s hat fashion accessories female lady spring 🎩 top hat magic gentleman classy circus 🎓 graduation cap school college degree university graduation cap hat legal learn education 🧢 billed cap cap baseball ⛑️ rescue worker s helmet construction build 📿 prayer beads dhikr religious 💄 lipstick female girl fashion woman 💍 ring wedding propose marriage valentines diamond fashion jewelry gem engagement 💎 gem stone blue ruby diamond jewelry 🔇 muted speaker sound volume silence quiet 🔈 speaker low volume sound volume silence broadcast 🔉 speaker medium volume volume speaker broadcast 🔊 speaker high volume volume noise noisy speaker broadcast 📢 loudspeaker volume sound 📣 megaphone sound speaker volume 📯 postal horn instrument music 🔔 bell sound notification christmas xmas chime 🔕 bell with slash sound volume mute quiet silent 🎼 musical score treble clef compose 🎵 musical note score tone sound 🎶 musical notes music score 🎙️ studio microphone sing recording artist talkshow 🎚️ level slider scale 🎛️ control knobs dial 🎤 microphone sound music PA sing talkshow 🎧 headphone music score gadgets 📻 radio communication music podcast program 🎷 saxophone music instrument jazz blues 🎸 guitar music instrument 🎹 musical keyboard piano instrument compose 🎺 trumpet music brass 🎻 violin music instrument orchestra symphony 🪕 banjo music instructment 🥁 drum music instrument drumsticks snare 📱 mobile phone technology apple gadgets dial 📲 mobile phone with arrow iphone incoming ☎️ telephone technology communication dial telephone 📞 telephone receiver technology communication dial 📟 pager bbcall oldschool 90s 📠 fax machine communication technology 🔋 battery power energy sustain 🔌 electric plug charger power 💻 laptop technology laptop screen display monitor 🖥️ desktop computer technology computing screen 🖨️ printer paper ink ⌨️ keyboard technology computer type input text 🖱️ computer mouse click 🖲️ trackball technology trackpad 💽 computer disk technology record data disk 90s 💾 floppy disk oldschool technology save 90s 80s 💿 optical disk technology dvd disk disc 90s 📀 dvd cd disk disc 🧮 abacus calculation 🎥 movie camera film record 🎞️ film frames movie 📽️ film projector video tape record movie 🎬 clapper board movie film record 📺 television technology program oldschool show television 📷 camera gadgets photography 📸 camera with flash photography gadgets 📹 video camera film record 📼 videocassette record video oldschool 90s 80s 🔍 magnifying glass tilted left search zoom find detective 🔎 magnifying glass tilted right search zoom find detective 🕯️ candle fire wax 💡 light bulb light electricity idea 🔦 flashlight dark camping sight night 🏮 red paper lantern light paper halloween spooky 🪔 diya lamp lighting 📔 notebook with decorative cover classroom notes record paper study 📕 closed book read library knowledge textbook learn 📖 open book book read library knowledge literature learn study 📗 green book read library knowledge study 📘 blue book read library knowledge learn study 📙 orange book read library knowledge textbook study 📚 books literature library study 📓 notebook stationery record notes paper study 📒 ledger notes paper 📃 page with curl documents office paper 📜 scroll documents ancient history paper 📄 page facing up documents office paper information 📰 newspaper press headline 🗞️ rolled up newspaper press headline 📑 bookmark tabs favorite save order tidy 🔖 bookmark favorite label save 🏷️ label sale tag 💰 money bag dollar payment coins sale 💴 yen banknote money sales japanese dollar currency 💵 dollar banknote money sales bill currency 💶 euro banknote money sales dollar currency 💷 pound banknote british sterling money sales bills uk england currency 💸 money with wings dollar bills payment sale 💳 credit card money sales dollar bill payment shopping 🧾 receipt accounting expenses 💹 chart increasing with yen green-square graph presentation stats 💱 currency exchange money sales dollar travel 💲 heavy dollar sign money sales payment currency buck ✉️ envelope letter postal inbox communication 📧 e mail communication inbox 📨 incoming envelope email inbox 📩 envelope with arrow email communication 📤 outbox tray inbox email 📥 inbox tray email documents 📦 package mail gift cardboard box moving 📫 closed mailbox with raised flag email inbox communication 📪 closed mailbox with lowered flag email communication inbox 📬 open mailbox with raised flag email inbox communication 📭 open mailbox with lowered flag email inbox 📮 postbox email letter envelope 🗳️ ballot box with ballot election vote ✏️ pencil stationery write paper writing school study ✒️ black nib pen stationery writing write 🖋️ fountain pen stationery writing write 🖊️ pen stationery writing write 🖌️ paintbrush drawing creativity art 🖍️ crayon drawing creativity 📝 memo write documents stationery pencil paper writing legal exam quiz test study compose 💼 briefcase business documents work law legal job career 📁 file folder documents business office 📂 open file folder documents load 🗂️ card index dividers organizing business stationery 📅 calendar calendar schedule 📆 tear off calendar schedule date planning 🗒️ spiral notepad memo stationery 🗓️ spiral calendar date schedule planning 📇 card index business stationery 📈 chart increasing graph presentation stats recovery business economics money sales good success 📉 chart decreasing graph presentation stats recession business economics money sales bad failure 📊 bar chart graph presentation stats 📋 clipboard stationery documents 📌 pushpin stationery mark here 📍 round pushpin stationery location map here 📎 paperclip documents stationery 🖇️ linked paperclips documents stationery 📏 straight ruler stationery calculate length math school drawing architect sketch 📐 triangular ruler stationery math architect sketch ✂️ scissors stationery cut 🗃️ card file box business stationery 🗄️ file cabinet filing organizing 🗑️ wastebasket bin trash rubbish garbage toss 🔒 locked security password padlock 🔓 unlocked privacy security 🔏 locked with pen security secret 🔐 locked with key security privacy 🔑 key lock door password 🗝️ old key lock door password 🔨 hammer tools build create 🪓 axe tool chop cut ⛏️ pick tools dig ⚒️ hammer and pick tools build create 🛠️ hammer and wrench tools build create 🗡️ dagger weapon ⚔️ crossed swords weapon 🔫 pistol violence weapon pistol revolver 🏹 bow and arrow sports 🛡️ shield protection security 🔧 wrench tools diy ikea fix maintainer 🔩 nut and bolt handy tools fix ⚙️ gear cog 🗜️ clamp tool ⚖️ balance scale law fairness weight 🦯 probing cane accessibility 🔗 link rings url ⛓️ chains lock arrest 🧰 toolbox tools diy fix maintainer mechanic 🧲 magnet attraction magnetic ⚗️ alembic distilling science experiment chemistry 🧪 test tube chemistry experiment lab science 🧫 petri dish bacteria biology culture lab 🧬 dna biologist genetics life 🔬 microscope laboratory experiment zoomin science study 🔭 telescope stars space zoom science astronomy 📡 satellite antenna communication future radio space 💉 syringe health hospital drugs blood medicine needle doctor nurse 🩸 drop of blood period hurt harm wound 💊 pill health medicine doctor pharmacy drug 🩹 adhesive bandage heal 🩺 stethoscope health 🚪 door house entry exit 🛏️ bed sleep rest 🛋️ couch and lamp read chill 🪑 chair sit furniture 🚽 toilet restroom wc washroom bathroom potty 🚿 shower clean water bathroom 🛁 bathtub clean shower bathroom 🪒 razor cut 🧴 lotion bottle moisturizer sunscreen 🧷 safety pin diaper 🧹 broom cleaning sweeping witch 🧺 basket laundry 🧻 roll of paper roll 🧼 soap bar bathing cleaning lather 🧽 sponge absorbing cleaning porous 🧯 fire extinguisher quench 🛒 shopping cart trolley 🚬 cigarette kills tobacco cigarette joint smoke ⚰️ coffin vampire dead die death rip graveyard cemetery casket funeral box ⚱️ funeral urn dead die death rip ashes 🗿 moai rock easter island moai 🏧 atm sign money sales cash blue-square payment bank 🚮 litter in bin sign blue-square sign human info 🚰 potable water blue-square liquid restroom cleaning faucet ♿ wheelchair symbol blue-square disabled accessibility 🚹 men s room toilet restroom wc blue-square gender male 🚺 women s room purple-square woman female toilet loo restroom gender 🚻 restroom blue-square toilet refresh wc gender 🚼 baby symbol orange-square child 🚾 water closet toilet restroom blue-square 🛂 passport control custom blue-square 🛃 customs passport border blue-square 🛄 baggage claim blue-square airport transport 🛅 left luggage blue-square travel ⚠️ warning exclamation wip alert error problem issue 🚸 children crossing school warning danger sign driving yellow-diamond ⛔ no entry limit security privacy bad denied stop circle 🚫 prohibited forbid stop limit denied disallow circle 🚳 no bicycles cyclist prohibited circle 🚭 no smoking cigarette blue-square smell smoke 🚯 no littering trash bin garbage circle 🚱 non potable water drink faucet tap circle 🚷 no pedestrians rules crossing walking circle 📵 no mobile phones iphone mute circle 🔞 no one under eighteen 18 drink pub night minor circle ☢️ radioactive nuclear danger ☣️ biohazard danger ⬆️ up arrow blue-square continue top direction ↗️ up right arrow blue-square point direction diagonal northeast ➡️ right arrow blue-square next ↘️ down right arrow blue-square direction diagonal southeast ⬇️ down arrow blue-square direction bottom ↙️ down left arrow blue-square direction diagonal southwest ⬅️ left arrow blue-square previous back ↖️ up left arrow blue-square point direction diagonal northwest ↕️ up down arrow blue-square direction way vertical ↔️ left right arrow shape direction horizontal sideways ↩️ right arrow curving left back return blue-square undo enter ↪️ left arrow curving right blue-square return rotate direction ⤴️ right arrow curving up blue-square direction top ⤵️ right arrow curving down blue-square direction bottom 🔃 clockwise vertical arrows sync cycle round repeat 🔄 counterclockwise arrows button blue-square sync cycle 🔙 back arrow arrow words return 🔚 end arrow words arrow 🔛 on arrow arrow words 🔜 soon arrow arrow words 🔝 top arrow words blue-square 🛐 place of worship religion church temple prayer ⚛️ atom symbol science physics chemistry 🕉️ om hinduism buddhism sikhism jainism ✡️ star of david judaism ☸️ wheel of dharma hinduism buddhism sikhism jainism ☯️ yin yang balance ✝️ latin cross christianity ☦️ orthodox cross suppedaneum religion ☪️ star and crescent islam ☮️ peace symbol hippie 🕎 menorah hanukkah candles jewish 🔯 dotted six pointed star purple-square religion jewish hexagram ♈ aries sign purple-square zodiac astrology ♉ taurus purple-square sign zodiac astrology ♊ gemini sign zodiac purple-square astrology ♋ cancer sign zodiac purple-square astrology ♌ leo sign purple-square zodiac astrology ♍ virgo sign zodiac purple-square astrology ♎ libra sign purple-square zodiac astrology ♏ scorpio sign zodiac purple-square astrology scorpio ♐ sagittarius sign zodiac purple-square astrology ♑ capricorn sign zodiac purple-square astrology ♒ aquarius sign purple-square zodiac astrology ♓ pisces purple-square sign zodiac astrology ⛎ ophiuchus sign purple-square constellation astrology 🔀 shuffle tracks button blue-square shuffle music random 🔁 repeat button loop record 🔂 repeat single button blue-square loop ▶️ play button blue-square right direction play ⏩ fast forward button blue-square play speed continue ⏭️ next track button forward next blue-square ⏯️ play or pause button blue-square play pause ◀️ reverse button blue-square left direction ⏪ fast reverse button play blue-square ⏮️ last track button backward 🔼 upwards button blue-square triangle direction point forward top ⏫ fast up button blue-square direction top 🔽 downwards button blue-square direction bottom ⏬ fast down button blue-square direction bottom ⏸️ pause button pause blue-square ⏹️ stop button blue-square ⏺️ record button blue-square ⏏️ eject button blue-square 🎦 cinema blue-square record film movie curtain stage theater 🔅 dim button sun afternoon warm summer 🔆 bright button sun light 📶 antenna bars blue-square reception phone internet connection wifi bluetooth bars 📳 vibration mode orange-square phone 📴 mobile phone off mute orange-square silence quiet ♀️ female sign woman women lady girl ♂️ male sign man boy men ⚕️ medical symbol health hospital ♾️ infinity forever ♻️ recycling symbol arrow environment garbage trash ⚜️ fleur de lis decorative scout 🔱 trident emblem weapon spear 📛 name badge fire forbid 🔰 japanese symbol for beginner badge shield ⭕ hollow red circle circle round ✅ check mark button green-square ok agree vote election answer tick ☑️ check box with check ok agree confirm black-square vote election yes tick ✔️ check mark ok nike answer yes tick ✖️ multiplication sign math calculation ❌ cross mark no delete remove cancel red ❎ cross mark button x green-square no deny ➕ plus sign math calculation addition more increase ➖ minus sign math calculation subtract less ➗ division sign divide math calculation ➰ curly loop scribble draw shape squiggle ➿ double curly loop tape cassette 〽️ part alternation mark graph presentation stats business economics bad ✳️ eight spoked asterisk star sparkle green-square ✴️ eight pointed star orange-square shape polygon ❇️ sparkle stars green-square awesome good fireworks ‼️ double exclamation mark exclamation surprise ⁉️ exclamation question mark wat punctuation surprise ❓ question mark doubt confused ❔ white question mark doubts gray huh confused ❕ white exclamation mark surprise punctuation gray wow warning ❗ exclamation mark heavy exclamation mark danger surprise punctuation wow warning 〰️ wavy dash draw line moustache mustache squiggle scribble ©️ copyright ip license circle law legal ®️ registered alphabet circle ™️ trade mark trademark brand law legal #️⃣ keycap symbol blue-square twitter *️⃣ keycap star keycap 0️⃣ keycap 0 0 numbers blue-square null 1️⃣ keycap 1 blue-square numbers 1 2️⃣ keycap 2 numbers 2 prime blue-square 3️⃣ keycap 3 3 numbers prime blue-square 4️⃣ keycap 4 4 numbers blue-square 5️⃣ keycap 5 5 numbers blue-square prime 6️⃣ keycap 6 6 numbers blue-square 7️⃣ keycap 7 7 numbers blue-square prime 8️⃣ keycap 8 8 blue-square numbers 9️⃣ keycap 9 blue-square numbers 9 🔟 keycap 10 numbers 10 blue-square 🔠 input latin uppercase alphabet words blue-square 🔡 input latin lowercase blue-square alphabet 🔢 input numbers numbers blue-square 🔣 input symbols blue-square music note ampersand percent glyphs characters 🔤 input latin letters blue-square alphabet 🅰️ a button red-square alphabet letter 🆎 ab button red-square alphabet 🅱️ b button red-square alphabet letter 🆑 cl button alphabet words red-square 🆒 cool button words blue-square 🆓 free button blue-square words ℹ️ information blue-square alphabet letter 🆔 id button purple-square words Ⓜ️ circled m alphabet blue-circle letter 🆕 new button blue-square words start 🆖 ng button blue-square words shape icon 🅾️ o button alphabet red-square letter 🆗 ok button good agree yes blue-square 🅿️ p button cars blue-square alphabet letter 🆘 sos button help red-square words emergency 911 🆙 up button blue-square above high 🆚 vs button words orange-square 🈁 japanese here button blue-square here katakana japanese destination 🈂️ japanese service charge button japanese blue-square katakana 🈷️ japanese monthly amount button chinese month moon japanese orange-square kanji 🈶 japanese not free of charge button orange-square chinese have kanji 🈯 japanese reserved button chinese point green-square kanji 🉐 japanese bargain button chinese kanji obtain get circle 🈹 japanese discount button cut divide chinese kanji pink-square 🈚 japanese free of charge button nothing chinese kanji japanese orange-square 🈲 japanese prohibited button kanji japanese chinese forbidden limit restricted red-square 🉑 japanese acceptable button ok good chinese kanji agree yes orange-circle 🈸 japanese application button chinese japanese kanji orange-square 🈴 japanese passing grade button japanese chinese join kanji red-square 🈳 japanese vacancy button kanji japanese chinese empty sky blue-square ㊗️ japanese congratulations button chinese kanji japanese red-circle ㊙️ japanese secret button privacy chinese sshh kanji red-circle 🈺 japanese open for business button japanese opening hours orange-square 🈵 japanese no vacancy button full chinese japanese red-square kanji 🔴 red circle shape error danger 🟠 orange circle round 🟡 yellow circle round 🟢 green circle round 🔵 blue circle shape icon button 🟣 purple circle round 🟤 brown circle round ⚫ black circle shape button round ⚪ white circle shape round 🟥 red square 🟧 orange square 🟨 yellow square 🟩 green square 🟦 blue square 🟪 purple square 🟫 brown square ⬛ black large square shape icon button ⬜ white large square shape icon stone button ◼️ black medium square shape button icon ◻️ white medium square shape stone icon ◾ black medium small square icon shape button ◽ white medium small square shape stone icon button ▪️ black small square shape icon ▫️ white small square shape icon 🔶 large orange diamond shape jewel gem 🔷 large blue diamond shape jewel gem 🔸 small orange diamond shape jewel gem 🔹 small blue diamond shape jewel gem 🔺 red triangle pointed up shape direction up top 🔻 red triangle pointed down shape direction bottom 💠 diamond with a dot jewel blue gem crystal fancy 🔘 radio button input old music circle 🔳 white square button shape input 🔲 black square button shape input frame 🏁 chequered flag contest finishline race gokart 🚩 triangular flag mark milestone place 🎌 crossed flags japanese nation country border 🏴 black flag pirate 🏳️ white flag losing loser lost surrender give up fail 🏳️‍🌈 rainbow flag flag rainbow pride gay lgbt glbt queer homosexual lesbian bisexual transgender 🏴‍☠️ pirate flag skull crossbones flag banner 🇦🇨 flag ascension island 🇦🇩 flag andorra ad flag nation country banner andorra 🇦🇪 flag united arab emirates united arab emirates flag nation country banner united arab emirates 🇦🇫 flag afghanistan af flag nation country banner afghanistan 🇦🇬 flag antigua barbuda antigua barbuda flag nation country banner antigua barbuda 🇦🇮 flag anguilla ai flag nation country banner anguilla 🇦🇱 flag albania al flag nation country banner albania 🇦🇲 flag armenia am flag nation country banner armenia 🇦🇴 flag angola ao flag nation country banner angola 🇦🇶 flag antarctica aq flag nation country banner antarctica 🇦🇷 flag argentina ar flag nation country banner argentina 🇦🇸 flag american samoa american ws flag nation country banner american samoa 🇦🇹 flag austria at flag nation country banner austria 🇦🇺 flag australia au flag nation country banner australia 🇦🇼 flag aruba aw flag nation country banner aruba 🇦🇽 flag aland islands Åland islands flag nation country banner aland islands 🇦🇿 flag azerbaijan az flag nation country banner azerbaijan 🇧🇦 flag bosnia herzegovina bosnia herzegovina flag nation country banner bosnia herzegovina 🇧🇧 flag barbados bb flag nation country banner barbados 🇧🇩 flag bangladesh bd flag nation country banner bangladesh 🇧🇪 flag belgium be flag nation country banner belgium 🇧🇫 flag burkina faso burkina faso flag nation country banner burkina faso 🇧🇬 flag bulgaria bg flag nation country banner bulgaria 🇧🇭 flag bahrain bh flag nation country banner bahrain 🇧🇮 flag burundi bi flag nation country banner burundi 🇧🇯 flag benin bj flag nation country banner benin 🇧🇱 flag st barthelemy saint barthélemy flag nation country banner st barthelemy 🇧🇲 flag bermuda bm flag nation country banner bermuda 🇧🇳 flag brunei bn darussalam flag nation country banner brunei 🇧🇴 flag bolivia bo flag nation country banner bolivia 🇧🇶 flag caribbean netherlands bonaire flag nation country banner caribbean netherlands 🇧🇷 flag brazil br flag nation country banner brazil 🇧🇸 flag bahamas bs flag nation country banner bahamas 🇧🇹 flag bhutan bt flag nation country banner bhutan 🇧🇻 flag bouvet island norway 🇧🇼 flag botswana bw flag nation country banner botswana 🇧🇾 flag belarus by flag nation country banner belarus 🇧🇿 flag belize bz flag nation country banner belize 🇨🇦 flag canada ca flag nation country banner canada 🇨🇨 flag cocos islands cocos keeling islands flag nation country banner cocos islands 🇨🇩 flag congo kinshasa congo democratic republic flag nation country banner congo kinshasa 🇨🇫 flag central african republic central african republic flag nation country banner central african republic 🇨🇬 flag congo brazzaville congo flag nation country banner congo brazzaville 🇨🇭 flag switzerland ch flag nation country banner switzerland 🇨🇮 flag cote d ivoire ivory coast flag nation country banner cote d ivoire 🇨🇰 flag cook islands cook islands flag nation country banner cook islands 🇨🇱 flag chile flag nation country banner chile 🇨🇲 flag cameroon cm flag nation country banner cameroon 🇨🇳 flag china china chinese prc flag country nation banner china 🇨🇴 flag colombia co flag nation country banner colombia 🇨🇵 flag clipperton island 🇨🇷 flag costa rica costa rica flag nation country banner costa rica 🇨🇺 flag cuba cu flag nation country banner cuba 🇨🇻 flag cape verde cabo verde flag nation country banner cape verde 🇨🇼 flag curacao curaçao flag nation country banner curacao 🇨🇽 flag christmas island christmas island flag nation country banner christmas island 🇨🇾 flag cyprus cy flag nation country banner cyprus 🇨🇿 flag czechia cz flag nation country banner czechia 🇩🇪 flag germany german nation flag country banner germany 🇩🇬 flag diego garcia 🇩🇯 flag djibouti dj flag nation country banner djibouti 🇩🇰 flag denmark dk flag nation country banner denmark 🇩🇲 flag dominica dm flag nation country banner dominica 🇩🇴 flag dominican republic dominican republic flag nation country banner dominican republic 🇩🇿 flag algeria dz flag nation country banner algeria 🇪🇦 flag ceuta melilla 🇪🇨 flag ecuador ec flag nation country banner ecuador 🇪🇪 flag estonia ee flag nation country banner estonia 🇪🇬 flag egypt eg flag nation country banner egypt 🇪🇭 flag western sahara western sahara flag nation country banner western sahara 🇪🇷 flag eritrea er flag nation country banner eritrea 🇪🇸 flag spain spain flag nation country banner spain 🇪🇹 flag ethiopia et flag nation country banner ethiopia 🇪🇺 flag european union european union flag banner 🇫🇮 flag finland fi flag nation country banner finland 🇫🇯 flag fiji fj flag nation country banner fiji 🇫🇰 flag falkland islands falkland islands malvinas flag nation country banner falkland islands 🇫🇲 flag micronesia micronesia federated states flag nation country banner micronesia 🇫🇴 flag faroe islands faroe islands flag nation country banner faroe islands 🇫🇷 flag france banner flag nation france french country france 🇬🇦 flag gabon ga flag nation country banner gabon 🇬🇧 flag united kingdom united kingdom great britain northern ireland flag nation country banner british UK english england union jack united kingdom 🇬🇩 flag grenada gd flag nation country banner grenada 🇬🇪 flag georgia ge flag nation country banner georgia 🇬🇫 flag french guiana french guiana flag nation country banner french guiana 🇬🇬 flag guernsey gg flag nation country banner guernsey 🇬🇭 flag ghana gh flag nation country banner ghana 🇬🇮 flag gibraltar gi flag nation country banner gibraltar 🇬🇱 flag greenland gl flag nation country banner greenland 🇬🇲 flag gambia gm flag nation country banner gambia 🇬🇳 flag guinea gn flag nation country banner guinea 🇬🇵 flag guadeloupe gp flag nation country banner guadeloupe 🇬🇶 flag equatorial guinea equatorial gn flag nation country banner equatorial guinea 🇬🇷 flag greece gr flag nation country banner greece 🇬🇸 flag south georgia south sandwich islands south georgia sandwich islands flag nation country banner south georgia south sandwich islands 🇬🇹 flag guatemala gt flag nation country banner guatemala 🇬🇺 flag guam gu flag nation country banner guam 🇬🇼 flag guinea bissau gw bissau flag nation country banner guinea bissau 🇬🇾 flag guyana gy flag nation country banner guyana 🇭🇰 flag hong kong sar china hong kong flag nation country banner hong kong sar china 🇭🇲 flag heard mcdonald islands 🇭🇳 flag honduras hn flag nation country banner honduras 🇭🇷 flag croatia hr flag nation country banner croatia 🇭🇹 flag haiti ht flag nation country banner haiti 🇭🇺 flag hungary hu flag nation country banner hungary 🇮🇨 flag canary islands canary islands flag nation country banner canary islands 🇮🇩 flag indonesia flag nation country banner indonesia 🇮🇪 flag ireland ie flag nation country banner ireland 🇮🇱 flag israel il flag nation country banner israel 🇮🇲 flag isle of man isle man flag nation country banner isle of man 🇮🇳 flag india in flag nation country banner india 🇮🇴 flag british indian ocean territory british indian ocean territory flag nation country banner british indian ocean territory 🇮🇶 flag iraq iq flag nation country banner iraq 🇮🇷 flag iran iran islamic republic flag nation country banner iran 🇮🇸 flag iceland is flag nation country banner iceland 🇮🇹 flag italy italy flag nation country banner italy 🇯🇪 flag jersey je flag nation country banner jersey 🇯🇲 flag jamaica jm flag nation country banner jamaica 🇯🇴 flag jordan jo flag nation country banner jordan 🇯🇵 flag japan japanese nation flag country banner japan 🇰🇪 flag kenya ke flag nation country banner kenya 🇰🇬 flag kyrgyzstan kg flag nation country banner kyrgyzstan 🇰🇭 flag cambodia kh flag nation country banner cambodia 🇰🇮 flag kiribati ki flag nation country banner kiribati 🇰🇲 flag comoros km flag nation country banner comoros 🇰🇳 flag st kitts nevis saint kitts nevis flag nation country banner st kitts nevis 🇰🇵 flag north korea north korea nation flag country banner north korea 🇰🇷 flag south korea south korea nation flag country banner south korea 🇰🇼 flag kuwait kw flag nation country banner kuwait 🇰🇾 flag cayman islands cayman islands flag nation country banner cayman islands 🇰🇿 flag kazakhstan kz flag nation country banner kazakhstan 🇱🇦 flag laos lao democratic republic flag nation country banner laos 🇱🇧 flag lebanon lb flag nation country banner lebanon 🇱🇨 flag st lucia saint lucia flag nation country banner st lucia 🇱🇮 flag liechtenstein li flag nation country banner liechtenstein 🇱🇰 flag sri lanka sri lanka flag nation country banner sri lanka 🇱🇷 flag liberia lr flag nation country banner liberia 🇱🇸 flag lesotho ls flag nation country banner lesotho 🇱🇹 flag lithuania lt flag nation country banner lithuania 🇱🇺 flag luxembourg lu flag nation country banner luxembourg 🇱🇻 flag latvia lv flag nation country banner latvia 🇱🇾 flag libya ly flag nation country banner libya 🇲🇦 flag morocco ma flag nation country banner morocco 🇲🇨 flag monaco mc flag nation country banner monaco 🇲🇩 flag moldova moldova republic flag nation country banner moldova 🇲🇪 flag montenegro me flag nation country banner montenegro 🇲🇫 flag st martin 🇲🇬 flag madagascar mg flag nation country banner madagascar 🇲🇭 flag marshall islands marshall islands flag nation country banner marshall islands 🇲🇰 flag north macedonia macedonia flag nation country banner north macedonia 🇲🇱 flag mali ml flag nation country banner mali 🇲🇲 flag myanmar mm flag nation country banner myanmar 🇲🇳 flag mongolia mn flag nation country banner mongolia 🇲🇴 flag macao sar china macao flag nation country banner macao sar china 🇲🇵 flag northern mariana islands northern mariana islands flag nation country banner northern mariana islands 🇲🇶 flag martinique mq flag nation country banner martinique 🇲🇷 flag mauritania mr flag nation country banner mauritania 🇲🇸 flag montserrat ms flag nation country banner montserrat 🇲🇹 flag malta mt flag nation country banner malta 🇲🇺 flag mauritius mu flag nation country banner mauritius 🇲🇻 flag maldives mv flag nation country banner maldives 🇲🇼 flag malawi mw flag nation country banner malawi 🇲🇽 flag mexico mx flag nation country banner mexico 🇲🇾 flag malaysia my flag nation country banner malaysia 🇲🇿 flag mozambique mz flag nation country banner mozambique 🇳🇦 flag namibia na flag nation country banner namibia 🇳🇨 flag new caledonia new caledonia flag nation country banner new caledonia 🇳🇪 flag niger ne flag nation country banner niger 🇳🇫 flag norfolk island norfolk island flag nation country banner norfolk island 🇳🇬 flag nigeria flag nation country banner nigeria 🇳🇮 flag nicaragua ni flag nation country banner nicaragua 🇳🇱 flag netherlands nl flag nation country banner netherlands 🇳🇴 flag norway no flag nation country banner norway 🇳🇵 flag nepal np flag nation country banner nepal 🇳🇷 flag nauru nr flag nation country banner nauru 🇳🇺 flag niue nu flag nation country banner niue 🇳🇿 flag new zealand new zealand flag nation country banner new zealand 🇴🇲 flag oman om symbol flag nation country banner oman 🇵🇦 flag panama pa flag nation country banner panama 🇵🇪 flag peru pe flag nation country banner peru 🇵🇫 flag french polynesia french polynesia flag nation country banner french polynesia 🇵🇬 flag papua new guinea papua new guinea flag nation country banner papua new guinea 🇵🇭 flag philippines ph flag nation country banner philippines 🇵🇰 flag pakistan pk flag nation country banner pakistan 🇵🇱 flag poland pl flag nation country banner poland 🇵🇲 flag st pierre miquelon saint pierre miquelon flag nation country banner st pierre miquelon 🇵🇳 flag pitcairn islands pitcairn flag nation country banner pitcairn islands 🇵🇷 flag puerto rico puerto rico flag nation country banner puerto rico 🇵🇸 flag palestinian territories palestine palestinian territories flag nation country banner palestinian territories 🇵🇹 flag portugal pt flag nation country banner portugal 🇵🇼 flag palau pw flag nation country banner palau 🇵🇾 flag paraguay py flag nation country banner paraguay 🇶🇦 flag qatar qa flag nation country banner qatar 🇷🇪 flag reunion réunion flag nation country banner reunion 🇷🇴 flag romania ro flag nation country banner romania 🇷🇸 flag serbia rs flag nation country banner serbia 🇷🇺 flag russia russian federation flag nation country banner russia 🇷🇼 flag rwanda rw flag nation country banner rwanda 🇸🇦 flag saudi arabia flag nation country banner saudi arabia 🇸🇧 flag solomon islands solomon islands flag nation country banner solomon islands 🇸🇨 flag seychelles sc flag nation country banner seychelles 🇸🇩 flag sudan sd flag nation country banner sudan 🇸🇪 flag sweden se flag nation country banner sweden 🇸🇬 flag singapore sg flag nation country banner singapore 🇸🇭 flag st helena saint helena ascension tristan cunha flag nation country banner st helena 🇸🇮 flag slovenia si flag nation country banner slovenia 🇸🇯 flag svalbard jan mayen 🇸🇰 flag slovakia sk flag nation country banner slovakia 🇸🇱 flag sierra leone sierra leone flag nation country banner sierra leone 🇸🇲 flag san marino san marino flag nation country banner san marino 🇸🇳 flag senegal sn flag nation country banner senegal 🇸🇴 flag somalia so flag nation country banner somalia 🇸🇷 flag suriname sr flag nation country banner suriname 🇸🇸 flag south sudan south sd flag nation country banner south sudan 🇸🇹 flag sao tome principe sao tome principe flag nation country banner sao tome principe 🇸🇻 flag el salvador el salvador flag nation country banner el salvador 🇸🇽 flag sint maarten sint maarten dutch flag nation country banner sint maarten 🇸🇾 flag syria syrian arab republic flag nation country banner syria 🇸🇿 flag eswatini sz flag nation country banner eswatini 🇹🇦 flag tristan da cunha 🇹🇨 flag turks caicos islands turks caicos islands flag nation country banner turks caicos islands 🇹🇩 flag chad td flag nation country banner chad 🇹🇫 flag french southern territories french southern territories flag nation country banner french southern territories 🇹🇬 flag togo tg flag nation country banner togo 🇹🇭 flag thailand th flag nation country banner thailand 🇹🇯 flag tajikistan tj flag nation country banner tajikistan 🇹🇰 flag tokelau tk flag nation country banner tokelau 🇹🇱 flag timor leste timor leste flag nation country banner timor leste 🇹🇲 flag turkmenistan flag nation country banner turkmenistan 🇹🇳 flag tunisia tn flag nation country banner tunisia 🇹🇴 flag tonga to flag nation country banner tonga 🇹🇷 flag turkey turkey flag nation country banner turkey 🇹🇹 flag trinidad tobago trinidad tobago flag nation country banner trinidad tobago 🇹🇻 flag tuvalu flag nation country banner tuvalu 🇹🇼 flag taiwan tw flag nation country banner taiwan 🇹🇿 flag tanzania tanzania united republic flag nation country banner tanzania 🇺🇦 flag ukraine ua flag nation country banner ukraine 🇺🇬 flag uganda ug flag nation country banner uganda 🇺🇲 flag u s outlying islands 🇺🇳 flag united nations un flag banner 🇺🇸 flag united states united states america flag nation country banner united states 🇺🇾 flag uruguay uy flag nation country banner uruguay 🇺🇿 flag uzbekistan uz flag nation country banner uzbekistan 🇻🇦 flag vatican city vatican city flag nation country banner vatican city 🇻🇨 flag st vincent grenadines saint vincent grenadines flag nation country banner st vincent grenadines 🇻🇪 flag venezuela ve bolivarian republic flag nation country banner venezuela 🇻🇬 flag british virgin islands british virgin islands bvi flag nation country banner british virgin islands 🇻🇮 flag u s virgin islands virgin islands us flag nation country banner u s virgin islands 🇻🇳 flag vietnam viet nam flag nation country banner vietnam 🇻🇺 flag vanuatu vu flag nation country banner vanuatu 🇼🇫 flag wallis futuna wallis futuna flag nation country banner wallis futuna 🇼🇸 flag samoa ws flag nation country banner samoa 🇽🇰 flag kosovo xk flag nation country banner kosovo 🇾🇪 flag yemen ye flag nation country banner yemen 🇾🇹 flag mayotte yt flag nation country banner mayotte 🇿🇦 flag south africa south africa flag nation country banner south africa 🇿🇲 flag zambia zm flag nation country banner zambia 🇿🇼 flag zimbabwe zw flag nation country banner zimbabwe 🏴󠁧󠁢󠁥󠁮󠁧󠁿 flag england flag english 🏴󠁧󠁢󠁳󠁣󠁴󠁿 flag scotland flag scottish 🏴󠁧󠁢󠁷󠁬󠁳󠁿 flag wales flag welsh 🥲 smiling face with tear sad cry pretend 🥸 disguised face pretent brows glasses moustache 🤌 pinched fingers size tiny small 🫀 anatomical heart health heartbeat 🫁 lungs breathe 🥷 ninja ninjutsu skills japanese 🤵‍♂️ man in tuxedo formal fashion 🤵‍♀️ woman in tuxedo formal fashion 👰‍♂️ man with veil wedding marriage 👰‍♀️ woman with veil wedding marriage 👩‍🍼 woman feeding baby birth food 👨‍🍼 man feeding baby birth food 🧑‍🍼 person feeding baby birth food 🧑‍🎄 mx claus christmas 🫂 people hugging care 🐈‍⬛ black cat superstition luck 🦬 bison ox 🦣 mammoth elephant tusks 🦫 beaver animal rodent 🐻‍❄️ polar bear animal arctic 🦤 dodo animal bird 🪶 feather bird fly 🦭 seal animal creature sea 🪲 beetle insect 🪳 cockroach insect pests 🪰 fly insect 🪱 worm animal 🪴 potted plant greenery house 🫐 blueberries fruit 🫒 olive fruit 🫑 bell pepper fruit plant 🫓 flatbread flour food 🫔 tamale food masa 🫕 fondue cheese pot food 🫖 teapot drink hot 🧋 bubble tea taiwan boba milk tea straw 🪨 rock stone 🪵 wood nature timber trunk 🛖 hut house structure 🛻 pickup truck car transportation 🛼 roller skate footwear sports 🪄 magic wand supernature power 🪅 pinata mexico candy celebration 🪆 nesting dolls matryoshka toy 🪡 sewing needle stitches 🪢 knot rope scout 🩴 thong sandal footwear summer 🪖 military helmet army protection 🪗 accordion music 🪘 long drum music 🪙 coin money currency 🪃 boomerang weapon 🪚 carpentry saw cut chop 🪛 screwdriver tools 🪝 hook tools 🪜 ladder tools 🛗 elevator lift 🪞 mirror reflection 🪟 window scenery 🪠 plunger toilet 🪤 mouse trap cheese 🪣 bucket water container 🪥 toothbrush hygiene dental 🪦 headstone death rip grave 🪧 placard announcement ⚧️ transgender symbol lgbtq 🏳️‍⚧️ transgender flag lgbtq 😶‍🌫️ face in clouds shower steam dream 😮‍💨 face exhaling relieve relief tired sigh 😵‍💫 face with spiral eyes sick ill confused nauseous nausea ❤️‍🔥 heart on fire passionate enthusiastic ❤️‍🩹 mending heart broken heart bandage wounded 🧔‍♂️ man beard facial hair 🧔‍♀️ woman beard facial hair 🫠 melting face hot heat 🫢 face with open eyes and hand over mouth silence secret shock surprise 🫣 face with peeking eye scared frightening embarrassing 🫡 saluting face respect salute 🫥 dotted line face invisible lonely isolation depression 🫤 face with diagonal mouth skeptic confuse frustrated indifferent 🥹 face holding back tears touched gratitude 🫱 rightwards hand palm offer 🫲 leftwards hand palm offer 🫳 palm down hand palm drop 🫴 palm up hand lift offer demand 🫰 hand with index finger and thumb crossed heart love money expensive 🫵 index pointing at the viewer you recruit 🫶 heart hands love appreciation support 🫦 biting lip flirt sexy pain worry 🫅 person with crown royalty power 🫃 pregnant man baby belly 🫄 pregnant person baby belly 🧌 troll mystical monster 🪸 coral ocean sea reef 🪷 lotus flower calm meditation 🪹 empty nest bird 🪺 nest with eggs bird 🫘 beans food 🫗 pouring liquid cup water 🫙 jar container sauce 🛝 playground slide fun park 🛞 wheel car transport 🛟 ring buoy life saver life preserver 🪬 hamsa religion protection 🪩 mirror ball disco dance party 🪫 low battery drained dead 🩼 crutch accessibility assist 🩻 x-ray skeleton medicine 🫧 bubbles soap fun carbonation sparkling 🪪 identification card document 🟰 heavy equals sign math ¿? question upside down reversed spanish ← left arrow ↑ up arrow → right arrow ↓ down arrow ←↑→↓ all directions up down left right arrows AH↗️HA↘️HA↗️HA↘️ pekora arrows hahaha rabbit • dot circle separator 「」 japanese quote square bracket ¯\_(ツ)_/¯ shrug idk i dont know ↵ enter key return 𝕏 twitter x logo 👉👈 etou ughhhhhhh shy 👉👌 put it in imagination perv 🫨 shaking face tremble shake shocked 🩷 pink heart love 🩵 light blue heart love cyan 🩶 grey heart gray love 🫷 leftwards pushing hand stop halt left 🫸 rightwards pushing hand stop halt right 🫎 moose animal antlers 🫏 donkey animal mule ass 🪽 wing bird feather fly 🐦‍⬛ black bird crow raven rook 🪿 goose bird honk 🪼 jellyfish sea ocean sting 🪻 hyacinth flower spring 🫚 ginger root spice food 🫛 pea pod peas vegetable food 🪭 folding hand fan fan cool 🪮 hair pick afro comb 🪇 maracas instrument music shake 🪈 flute instrument music 🪯 khanda sikh religion symbol 🛜 wireless wifi wi-fi internet network 🙂‍↔️ head shaking horizontally no shake 🙂‍↕️ head shaking vertically yes nod 🚶‍➡️ person walking facing right walk 🚶‍♀️‍➡️ woman walking facing right walk 🚶‍♂️‍➡️ man walking facing right walk 🧎‍➡️ person kneeling facing right kneel 🧎‍♀️‍➡️ woman kneeling facing right kneel 🧎‍♂️‍➡️ man kneeling facing right kneel 🧑‍🦯‍➡️ person with white cane facing right accessibility blind 👨‍🦯‍➡️ man with white cane facing right accessibility blind 👩‍🦯‍➡️ woman with white cane facing right accessibility blind 🧑‍🦼‍➡️ person in motorized wheelchair facing right accessibility 👨‍🦼‍➡️ man in motorized wheelchair facing right accessibility 👩‍🦼‍➡️ woman in motorized wheelchair facing right accessibility 🧑‍🦽‍➡️ person in manual wheelchair facing right accessibility 👨‍🦽‍➡️ man in manual wheelchair facing right accessibility 👩‍🦽‍➡️ woman in manual wheelchair facing right accessibility 🏃‍➡️ person running facing right run 🏃‍♀️‍➡️ woman running facing right run 🏃‍♂️‍➡️ man running facing right run 🧑‍🧑‍🧒 family adult adult child parents 🧑‍🧑‍🧒‍🧒 family adult adult child child parents 🧑‍🧒 family adult child parent 🧑‍🧒‍🧒 family adult child child parent 🐦‍🔥 phoenix fire bird rebirth 🍋‍🟩 lime fruit citrus green 🍄‍🟫 brown mushroom fungi ⛓️‍💥 broken chain snap shatter 🫩 face with bags under eyes tired sleepy exhausted 🫆 fingerprint id biometric 🪾 leafless tree barren dead winter 🫜 root vegetable food turnip radish 🪉 harp instrument music 🪏 shovel dig tool 🫟 splatter splash stain mess 🇨🇶 flag sark 🫪 distorted face anxiety shocked panic 🫯 fight cloud comic brawl dust 🫈 hairy creature sasquatch bigfoot 🧑‍🩰 ballet dancer dance ballerina 🫍 orca killer whale 🛘 landslide rockfall disaster 🪊 trombone instrument music 🪎 treasure chest gold loot pirate ================================================ FILE: dots/.config/hypr/hyprland/scripts/launch_first_available.sh ================================================ #!/usr/bin/env bash for cmd in "$@"; do [[ -z "$cmd" ]] && continue eval "command -v ${cmd%% *}" >/dev/null 2>&1 || continue eval "$cmd" & exit done ================================================ FILE: dots/.config/hypr/hyprland/scripts/snip_to_search.sh ================================================ #!/usr/bin/env bash grim -g "$(slurp)" /tmp/image.png imageLink=$(curl -sF files[]=@/tmp/image.png 'https://uguu.se/upload' | jq -r '.files[0].url') xdg-open "https://lens.google.com/uploadbyurl?url=${imageLink}" rm /tmp/image.png ================================================ FILE: dots/.config/hypr/hyprland/scripts/start_geoclue_agent.sh ================================================ #!/usr/bin/env bash # Check if GeoClue agent is already running if pgrep -f 'geoclue-2.0/demos/agent' > /dev/null; then echo "GeoClue agent is already running." exit 0 fi # List of known possible GeoClue agent paths AGENT_PATHS=( /usr/libexec/geoclue-2.0/demos/agent /usr/lib/geoclue-2.0/demos/agent "$HOME/.nix-profile/libexec/geoclue-2.0/demos/agent" "$HOME/.nix-profile/lib/geoclue-2.0/demos/agent" /run/current-system/sw/libexec/geoclue-2.0/demos/agent ) # Find the first valid agent path for path in "${AGENT_PATHS[@]}"; do if [ -x "$path" ]; then echo "Starting GeoClue agent from: $path" "$path" & # starts in the background exit 0 fi done # If we got here, none of the paths worked echo "GeoClue agent not found in known paths." echo "Please install GeoClue or update the script with the correct path." exit 1 ================================================ FILE: dots/.config/hypr/hyprland/services/create_custom_config.lua ================================================ require("hyprland/lib") hl.on("hyprland.start", function() local homeDir = os.getenv("HOME") if string.len(homeDir) == 0 then return end local baseCustomDir = homeDir .. "/.config/hypr/custom" local files = { baseCustomDir .. "/env.lua", baseCustomDir .. "/execs.lua", baseCustomDir .. "/general.lua", baseCustomDir .. "/keybinds.lua", baseCustomDir .. "/rules.lua", baseCustomDir .. "/variables.lua" } local createdFiles = 0 for _, file in ipairs(files) do if not is_file_exists(file) then create_if_not_exists(file) createdFiles = createdFiles + 1 end end if createdFiles > 0 then -- hl.exec_cmd("notify-send 'Hyprland config' 'Created " .. createdFiles .. " custom Hyprland config files in " .. baseCustomDir .. "' -a 'Hyprland'") -- hl.exec_cmd("hyprctl reload") end end) ================================================ FILE: dots/.config/hypr/hyprland/services/init.lua ================================================ require("hyprland/services/create_custom_config") ================================================ FILE: dots/.config/hypr/hyprland/shellOverrides/main.lua ================================================ -- DO NOT EDIT THIS FILE. IT IS MANAGED BY THE SHELL AND FOLLOWS STRICT RULES -- In other words, I ain't writing a lua parser for this, so please be a good boi/girl/whatever ================================================ FILE: dots/.config/hypr/hyprland/variables.lua ================================================ -- Default variables -- Copy these to ~/.config/hypr/custom/variables.lua to make changes in a dotfiles-update-friendly manner -- The folder within ~/.config/quickshell containing the config hl.env("qsConfig", "ii") -- Apps -- PULL REQUESTS ADDING MORE WILL NOT BE ACCEPTED, CONFIG FOR YOURSELF terminal = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'foot' 'kitty -1' 'alacritty' 'wezterm' 'konsole' 'kgx' 'uxterm' 'xterm'" fileManager = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'dolphin' 'nautilus' 'nemo' 'thunar' 'kitty -1 fish -c yazi'" browser = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'google-chrome-stable' 'zen-browser' 'firefox' 'brave' 'chromium' 'microsoft-edge-stable' 'opera' 'librewolf'" codeEditor = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'windsurf' 'antigravity' 'code' 'codium' 'cursor' 'zed' 'zedit' 'zeditor' 'kate' 'gnome-text-editor' 'emacs' 'command -v nvim && kitty -1 nvim' 'command -v micro && kitty -1 micro'" officeSoftware = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'wps' 'onlyoffice-desktopeditors' 'libreoffice'" textEditor = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'kate' 'gnome-text-editor' 'emacs'" volumeMixer = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'pavucontrol-qt' 'pavucontrol'" settingsApp = "XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh 'qs -p ~/.config/quickshell/$qsConfig/settings.qml' 'systemsettings' 'gnome-control-center' 'better-control'" taskManager = "~/.config/hypr/hyprland/scripts/launch_first_available.sh 'gnome-system-monitor' 'plasma-systemmonitor --page-name Processes' 'command -v btop && kitty -1 fish -c btop'" workspaceGroupSize = 10 ================================================ FILE: dots/.config/hypr/hyprland.lua ================================================ -- This file sources other files in `hyprland` and `custom` folders -- You wanna add your stuff in files in `custom` -- Internal stuff -- require("hyprland.lib") require("hyprland.services") -- Environment variables -- require("hyprland.env") if is_file_exists(HOME .. "/.config/hypr/custom/env.lua") then require("custom.env") end -- Default configurations -- require("hyprland.execs") require("hyprland.general") require("hyprland.rules") require("hyprland.colors") require("hyprland.keybinds") -- Custom configurations -- if is_file_exists(HOME .. "/.config/hypr/custom/execs.lua") then require("custom.execs") end if is_file_exists(HOME .. "/.config/hypr/custom/general.lua") then require("custom.general") end if is_file_exists(HOME .. "/.config/hypr/custom/rules.lua") then require("custom.rules") end if is_file_exists(HOME .. "/.config/hypr/custom/keybinds.lua") then require("custom.keybinds") end -- nwg-displays support: re-add the files if it updates later -- require("workspaces") -- require("monitors") -- Shell overrides -- require("hyprland.shellOverrides.main") ================================================ FILE: dots/.config/hypr/hyprlock/check-capslock.sh ================================================ #!/bin/env bash MAIN_KB_CAPS=$(hyprctl devices | grep -B 6 "main: yes" | grep "capsLock" | head -1 | awk '{print $2}') if [ "$MAIN_KB_CAPS" = "yes" ]; then echo "Caps Lock active" else echo "" fi ================================================ FILE: dots/.config/hypr/hyprlock/colors.conf ================================================ # This configuration is generated by matugen # Changing these variables with matugen still enabled will overwrite them. $text_color = rgba(d9e2ffFF) $entry_background_color = rgba(00194411) $entry_border_color = rgba(8f909955) $entry_color = rgba(d9e2ffFF) $font_family = Google Sans Flex Medium $font_family_clock = Google Sans Flex Medium $font_material_symbols = Material Symbols Rounded ================================================ FILE: dots/.config/hypr/hyprlock/status.sh ================================================ #!/usr/bin/env bash ############ Variables ############ enable_battery=false battery_charging=false ####### Check availability ######## for battery in /sys/class/power_supply/*BAT*; do if [[ -f "$battery/uevent" ]]; then enable_battery=true if [[ $(cat /sys/class/power_supply/*/status | head -1) == "Charging" ]]; then battery_charging=true fi break fi done ############# Output ############# if [[ $enable_battery == true ]]; then if [[ $battery_charging == true ]]; then echo -n "(+) " fi echo -n "$(cat /sys/class/power_supply/*/capacity | head -1)"% if [[ $battery_charging == false ]]; then echo -n " remaining" fi fi echo '' ================================================ FILE: dots/.config/hypr/hyprlock.conf ================================================ source=~/.config/hypr/hyprlock/colors.conf background { color = rgba(181818FF) } input-field { monitor = size = 250, 50 outline_thickness = 2 dots_size = 0.1 dots_spacing = 0.3 outer_color = $entry_border_color inner_color = $entry_background_color font_color = $entry_color fade_on_empty = true position = 0, 20 halign = center valign = center } label { monitor = text = $LAYOUT color = $text_color font_size = 14 font_family = $font_family position = -30, 30 halign = right valign = bottom } label { # Caps Lock Warning monitor = text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh color = $text_color font_size = 13 font_family = $font_family position = 0, -25 halign = center valign = center } label { # Clock monitor = text = $TIME color = $text_color font_size = 65 font_family = $font_family_clock position = 0, 300 halign = center valign = center } label { # Date monitor = text = cmd[update:5000] date +"%A, %B %d" color = $text_color font_size = 17 font_family = $font_family_clock position = 0, 240 halign = center valign = center } label { # User monitor = text =  $USER color = $text_color outline_thickness = 2 dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 dots_center = true font_size = 20 font_family = $font_family position = 0, 50 halign = center valign = bottom } label { # Status monitor = text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh color = $text_color font_size = 14 font_family = $font_family position = 30, -30 halign = left valign = top } ================================================ FILE: dots/.config/kde-material-you-colors/config.conf ================================================ [CUSTOM] # INSTRUCTIONS # Run kde-material-you-colors with no arguments from terminal # to debug your configuration changing in real time. # Monitor to get wallpaper from # For me main is 0 but second one is 6, play with this to find yours # Default is 0 monitor = 0 # File containing absolute path of an image (Takes precedence over automatic wallpaper detection) # Commented by default file = ~/.local/state/quickshell/user/generated/wallpaper/path.txt # List of 7 space separated colors (hex or rgb) to be used for text in pywal/konsole/KSyntaxHighlighting instead of wallpaper ones # Accepted values are hex e.g #ff0000 and rgb e.g 255,0,0 colors (rgb is converted to hex) # Commented by default # Example using catppuccin color scheme: custom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f # Enable Light mode # Accepted values are True or False # Commented by default to follow System Color Setting (Material You Light/Dark only) # NOTE: # Will fallback to dark mode if not defined here or enabled in Settings #light = False # Alternative color mode (default is 0), some images return more than one color, this will use either the matched or last color # Default is 0 ncolor = 0 # Light scheme icons theme #iconslight = OneUI-light iconslight = breeze-plus # Dark scheme icons theme #iconsdark = OneUI-dark iconsdark = breeze-plus-dark # Use pywal to theme other programs using Material You colors pywal=False # The amount of perceptible color for backgrounds in dark mode # A number between 0 and 4.0 (limited for accessibility purposes) # Defaults to 1 if not set #light_blend_multiplier = 1.0 # The amount of perceptible color for backgrounds in dark mode # A number between 0 and 4.0 (limited for accessibility purposes) # Defaults to 1 if not set #dark_blend_multiplier = 1.0 # A script/command that will be executed on start or wallpaper/dark/light/settings change # example below using https://github.com/vlevit/notify-send.sh to send a desktop notification: #on_change_hook = notify-send.sh "kde-material-you-colors" "This is a test" -t 2000 # Scheme Variant # Changes between Material You scheme variants (0-8) # 0 = Content # 1 = Expressive # 2 = Fidelity # 3 = Monochrome # 4 = Neutral # 5 = TonalSpot # 6 = Vibrant # 7 = Rainbow # 8 = FruitSalad # Default is 5 scheme_variant = 5 # Colorfulness chroma_multiplier = 1 # Brightness # An integer between 0.5 and 1.5 tone_multiplier = 1 ================================================ FILE: dots/.config/kdeglobals ================================================ [ColorEffects:Disabled] ChangeSelectionColor= Color=#211f24 ColorAmount=0.5 ColorEffect=3 ContrastAmount=0 ContrastEffect=0 Enable= IntensityAmount=0 IntensityEffect=0 [ColorEffects:Inactive] ChangeSelectionColor=true Color=#0c0a10 ColorAmount=0.025 ColorEffect=0 ContrastAmount=0.1 ContrastEffect=0 Enable=true IntensityAmount=0 IntensityEffect=0 [Colors:Button] BackgroundAlternate=#47434c BackgroundNormal=#2b292f DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#e6e0e9 ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:Complementary] BackgroundAlternate=#121016 BackgroundNormal=#211f24 DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#cac4cf ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:Header] BackgroundAlternate=#211f24 BackgroundNormal=#211f24 DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#cac4cf ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:Header][Inactive] BackgroundAlternate=#211f24 BackgroundNormal=#211f24 DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#cac4cf ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:Selection] BackgroundAlternate=#cdb9fb BackgroundNormal=#cdb9fb DecorationFocus=#cdb9fb DecorationHover=#c9bfd8 ForegroundActive=#36265d ForegroundInactive=#36265d ForegroundLink=#004b73 ForegroundNegative=#920023 ForegroundNeutral=#753400 ForegroundNormal=#36265d ForegroundPositive=#005228 ForegroundVisited=#74009f [Colors:Tooltip] BackgroundAlternate=#47434c BackgroundNormal=#211f24 DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#e6e0e9 ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:View] BackgroundAlternate=#211f24 BackgroundNormal=#121016 DecorationFocus=#cdb9fb DecorationHover=#65558f ForegroundActive=#e6e0e9 ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#e6e0e9 ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [Colors:Window] BackgroundAlternate=#47434c BackgroundNormal=#211f24 DecorationFocus=#cdb9fb DecorationHover=#cdb9fb ForegroundActive=#8fc9fc ForegroundInactive=#948f99 ForegroundLink=#8fc9fc ForegroundNegative=#ffb3b4 ForegroundNeutral=#fcb38a ForegroundNormal=#cac4cf ForegroundPositive=#00e479 ForegroundVisited=#ebb2ff [General] ColorScheme=MaterialYouDark ColorSchemeHash=3c0cecefbea43cdb8fe3da156e4a106f7384a526 LastUsedCustomAccentColor=184,117,220 XftHintStyle=hintslight TerminalApplication=kitty -1 XftSubPixel=none fixed=JetBrainsMono Nerd Font,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 font=Google Sans Flex,11,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium menuFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium smallestReadableFont=Google Sans Flex,9,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium toolBarFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium [Icons] Theme=breeze-dark [KDE] widgetStyle=Darkly [KFileDialog Settings] Allow Expansion=false Automatically select filename extension=true Breadcrumb Navigation=true Decoration position=2 LocationCombo Completionmode=5 PathCombo Completionmode=5 Show Bookmarks=false Show Full Path=false Show Inline Previews=true Show Preview=false Show Speedbar=true Show hidden files=false Sort by=Name Sort directories first=true Sort hidden files last=false Sort reversed=false Speedbar Width=168 View Style=Simple [Sounds] Theme=freedesktop [WM] activeBackground=54,52,58 activeBlend=252,252,252 activeForeground=230,224,233 inactiveBackground=76,70,90 activeFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium inactiveBlend=161,169,177 inactiveForeground=232,222,248 ================================================ FILE: dots/.config/kitty/kitty.conf ================================================ # Theming include ~/.local/state/quickshell/user/generated/terminal/kitty-theme.conf # Font font_family JetBrains Mono Nerd Font font_size 11.0 # Cursor cursor_shape beam cursor_trail 1 # Padding (why weird value? consistency with foot) window_margin_width 21.75 # No stupid close confirmation confirm_os_window_close 0 # Use fish shell shell fish # Copy map ctrl+c copy_or_interrupt # Search map ctrl+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id map kitty_mod+f launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id # Scroll & Zoom map page_up scroll_page_up map page_down scroll_page_down map ctrl+plus change_font_size all +1 map ctrl+equal change_font_size all +1 map ctrl+kp_add change_font_size all +1 map ctrl+minus change_font_size all -1 map ctrl+underscore change_font_size all -1 map ctrl+kp_subtract change_font_size all -1 map ctrl+0 change_font_size all 0 map ctrl+kp_0 change_font_size all 0 ================================================ FILE: dots/.config/kitty/scroll_mark.py ================================================ from kittens.tui.handler import result_handler from kitty.boss import Boss def main(args: list[str]) -> None: pass @result_handler(no_ui=True) def handle_result( args: list[str], answer: str, target_window_id: int, boss: Boss ) -> None: w = boss.window_id_map.get(target_window_id) if w is not None: if len(args) > 1 and args[1] != "prev": w.scroll_to_mark(prev=False) else: w.scroll_to_mark() ================================================ FILE: dots/.config/kitty/search.py ================================================ # Kitty search from https://github.com/trygveaa/kitty-kitten-search # License: GPLv3 import json import re import subprocess from gettext import gettext as _ from pathlib import Path from subprocess import PIPE, run from kittens.tui.handler import Handler from kittens.tui.line_edit import LineEdit from kittens.tui.loop import Loop from kittens.tui.operations import ( clear_screen, cursor, set_line_wrapping, set_window_title, styled, ) from kitty.config import cached_values_for from kitty.key_encoding import EventType from kitty.typing_compat import KeyEventType, ScreenSize NON_SPACE_PATTERN = re.compile(r"\S+") SPACE_PATTERN = re.compile(r"\s+") SPACE_PATTERN_END = re.compile(r"\s+$") SPACE_PATTERN_START = re.compile(r"^\s+") NON_ALPHANUM_PATTERN = re.compile(r"[^\w\d]+") NON_ALPHANUM_PATTERN_END = re.compile(r"[^\w\d]+$") NON_ALPHANUM_PATTERN_START = re.compile(r"^[^\w\d]+") ALPHANUM_PATTERN = re.compile(r"[\w\d]+") def call_remote_control(args: list[str]) -> None: subprocess.run(["kitty", "@", *args], capture_output=True) def reindex( text: str, pattern: re.Pattern[str], right: bool = False ) -> tuple[int, int]: if not right: m = pattern.search(text) else: matches = [x for x in pattern.finditer(text) if x] if not matches: raise ValueError m = matches[-1] if not m: raise ValueError return m.span() SCROLLMARK_FILE = Path(__file__).parent.absolute() / "scroll_mark.py" class Search(Handler): def __init__( self, cached_values: dict[str, str], window_ids: list[int], error: str = "" ) -> None: self.cached_values = cached_values self.window_ids = window_ids self.error = error self.line_edit = LineEdit() last_search = cached_values.get("last_search", "") self.line_edit.add_text(last_search) self.text_marked = bool(last_search) self.mode = cached_values.get("mode", "text") self.update_prompt() self.mark() def update_prompt(self) -> None: self.prompt = "~> " if self.mode == "regex" else "=> " def init_terminal_state(self) -> None: self.write(set_line_wrapping(False)) self.write(set_window_title(_("Search"))) def initialize(self) -> None: self.init_terminal_state() self.draw_screen() def draw_screen(self) -> None: self.write(clear_screen()) if self.window_ids: input_text = self.line_edit.current_input if self.text_marked: self.line_edit.current_input = styled(input_text, reverse=True) self.line_edit.write(self.write, self.prompt) self.line_edit.current_input = input_text if self.error: with cursor(self.write): self.print("") for l in self.error.split("\n"): self.print(l) def refresh(self) -> None: self.draw_screen() self.mark() def switch_mode(self) -> None: if self.mode == "regex": self.mode = "text" else: self.mode = "regex" self.cached_values["mode"] = self.mode self.update_prompt() def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: if self.text_marked: self.text_marked = False self.line_edit.clear() self.line_edit.on_text(text, in_bracketed_paste) self.refresh() def on_key(self, key_event: KeyEventType) -> None: if ( self.text_marked and key_event.type == EventType.PRESS and key_event.key not in [ "TAB", "LEFT_CONTROL", "RIGHT_CONTROL", "LEFT_ALT", "RIGHT_ALT", "LEFT_SHIFT", "RIGHT_SHIFT", "LEFT_SUPER", "RIGHT_SUPER", ] ): self.text_marked = False self.refresh() if self.line_edit.on_key(key_event): self.refresh() return if key_event.matches("ctrl+u"): self.line_edit.clear() self.refresh() elif key_event.matches("ctrl+a"): self.line_edit.home() self.refresh() elif key_event.matches("ctrl+e"): self.line_edit.end() self.refresh() elif key_event.matches("ctrl+backspace") or key_event.matches("ctrl+w"): before, _ = self.line_edit.split_at_cursor() try: start, _ = reindex(before, SPACE_PATTERN_END, right=True) except ValueError: start = -1 try: space = before[:start].rindex(" ") except ValueError: space = 0 self.line_edit.backspace(len(before) - space) self.refresh() elif key_event.matches("ctrl+left") or key_event.matches("ctrl+b"): before, _ = self.line_edit.split_at_cursor() try: start, _ = reindex(before, SPACE_PATTERN_END, right=True) except ValueError: start = -1 try: space = before[:start].rindex(" ") except ValueError: space = 0 self.line_edit.left(len(before) - space) self.refresh() elif key_event.matches("ctrl+right") or key_event.matches("ctrl+f"): _, after = self.line_edit.split_at_cursor() try: _, end = reindex(after, SPACE_PATTERN_START) except ValueError: end = 0 try: space = after[end:].index(" ") + 1 except ValueError: space = len(after) self.line_edit.right(space) self.refresh() elif key_event.matches("alt+backspace") or key_event.matches("alt+w"): before, _ = self.line_edit.split_at_cursor() try: start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) except ValueError: start = -1 else: self.line_edit.backspace(len(before) - start) self.refresh() return try: start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) except ValueError: self.line_edit.backspace(len(before)) self.refresh() return self.line_edit.backspace(len(before) - (start + 1)) self.refresh() elif key_event.matches("alt+left") or key_event.matches("alt+b"): before, _ = self.line_edit.split_at_cursor() try: start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True) except ValueError: start = -1 else: self.line_edit.left(len(before) - start) self.refresh() return try: start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True) except ValueError: self.line_edit.left(len(before)) self.refresh() return self.line_edit.left(len(before) - (start + 1)) self.refresh() elif key_event.matches("alt+right") or key_event.matches("alt+f"): _, after = self.line_edit.split_at_cursor() try: _, end = reindex(after, NON_ALPHANUM_PATTERN_START) except ValueError: end = 0 else: self.line_edit.right(end) self.refresh() return try: _, end = reindex(after, NON_ALPHANUM_PATTERN) except ValueError: self.line_edit.right(len(after)) self.refresh() return self.line_edit.right(end - 1) self.refresh() elif key_event.matches("tab"): self.switch_mode() self.refresh() elif key_event.matches("up") or key_event.matches("f3"): for match_arg in self.match_args(): call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE)]) elif key_event.matches("down") or key_event.matches("shift+f3"): for match_arg in self.match_args(): call_remote_control(["kitten", match_arg, str(SCROLLMARK_FILE), "next"]) elif key_event.matches("enter"): self.quit(0) elif key_event.matches("esc"): self.quit(1) def on_interrupt(self) -> None: self.quit(1) def on_eot(self) -> None: self.quit(1) def on_resize(self, screen_size: ScreenSize) -> None: self.refresh() def match_args(self) -> list[str]: return [f"--match=id:{window_id}" for window_id in self.window_ids] def mark(self) -> None: if not self.window_ids: return text = self.line_edit.current_input if text: match_case = "i" if text.islower() else "" match_type = match_case + self.mode for match_arg in self.match_args(): try: call_remote_control( ["create-marker", match_arg, match_type, "1", text] ) except SystemExit: self.remove_mark() else: self.remove_mark() def remove_mark(self) -> None: for match_arg in self.match_args(): call_remote_control(["remove-marker", match_arg]) def quit(self, return_code: int) -> None: self.cached_values["last_search"] = self.line_edit.current_input self.remove_mark() if return_code: for match_arg in self.match_args(): call_remote_control(["scroll-window", match_arg, "end"]) self.quit_loop(return_code) def main(args: list[str]) -> None: call_remote_control( ["resize-window", "--self", "--axis=vertical", "--increment", "-100"] ) error = "" if len(args) < 2 or not args[1].isdigit(): error = "Error: Window id must be provided as the first argument." window_id = int(args[1]) window_ids = [window_id] if len(args) > 2 and args[2] == "--all-windows": ls_output = run(["kitty", "@", "ls"], stdout=PIPE) ls_json = json.loads(ls_output.stdout.decode()) current_tab = None for os_window in ls_json: for tab in os_window["tabs"]: for kitty_window in tab["windows"]: if kitty_window["id"] == window_id: current_tab = tab if current_tab: window_ids = [ w["id"] for w in current_tab["windows"] if not w["is_focused"] ] else: error = "Error: Could not find the window id provided." loop = Loop() with cached_values_for("search") as cached_values: handler = Search(cached_values, window_ids, error) loop.loop(handler) ================================================ FILE: dots/.config/konsolerc ================================================ [Desktop Entry] DefaultProfile=Profile 1.profile [General] ConfigVersion=1 [KonsoleWindow] UseSingleInstance=true [UiSettings] ColorScheme= ================================================ FILE: dots/.config/matugen/config.toml ================================================ [config] version_check = false [templates.m3colors] input_path = '~/.config/matugen/templates/colors.json' output_path = '~/.local/state/quickshell/user/generated/colors.json' [templates.hyprland] input_path = '~/.config/matugen/templates/hyprland/colors.lua' output_path = '~/.config/hypr/hyprland/colors.lua' [templates.hyprlock] input_path = '~/.config/matugen/templates/hyprland/hyprlock-colors.conf' output_path = '~/.config/hypr/hyprlock/colors.conf' [templates.fuzzel] input_path = '~/.config/matugen/templates/fuzzel/fuzzel_theme.ini' output_path = '~/.config/fuzzel/fuzzel_theme.ini' [templates.gtk3] input_path = '~/.config/matugen/templates/gtk-3.0/gtk.css' output_path = '~/.config/gtk-3.0/gtk.css' [templates.gtk4] input_path = '~/.config/matugen/templates/gtk-4.0/gtk.css' output_path = '~/.config/gtk-4.0/gtk.css' [templates.kde_colors] input_path = '~/.config/matugen/templates/kde/color.txt' output_path = '~/.local/state/quickshell/user/generated/color.txt' [templates.wallpaper] input_path = '~/.config/matugen/templates/wallpaper.txt' output_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt' ================================================ FILE: dots/.config/matugen/templates/ags/_material.scss ================================================ $darkmode: False; $transparent: False; $background: {{colors.background.default.hex}}; $onBackground: {{colors.on_background.default.hex}}; $surface: {{colors.surface.default.hex}}; $surfaceDim: {{colors.surface_dim.default.hex}}; $surfaceBright: {{colors.surface_bright.default.hex}}; $surfaceContainerLowest: {{colors.surface_container_lowest.default.hex}}; $surfaceContainerLow: {{colors.surface_container_low.default.hex}}; $surfaceContainer: {{colors.surface_container.default.hex}}; $surfaceContainerHigh: {{colors.surface_container_high.default.hex}}; $surfaceContainerHighest: {{colors.surface_container_highest.default.hex}}; $onSurface: {{colors.on_surface.default.hex}}; $surfaceVariant: {{colors.surface_variant.default.hex}}; $onSurfaceVariant: {{colors.on_surface_variant.default.hex}}; $inverseSurface: {{colors.inverse_surface.default.hex}}; $inverseOnSurface: {{colors.inverse_on_surface.default.hex}}; $outline: {{colors.outline.default.hex}}; $outlineVariant: {{colors.outline_variant.default.hex}}; $shadow: {{colors.shadow.default.hex}}; $scrim: {{colors.scrim.default.hex}}; $primary: {{colors.primary.default.hex}}; $onPrimary: {{colors.on_primary.default.hex}}; $primaryContainer: {{colors.primary_container.default.hex}}; $onPrimaryContainer: {{colors.on_primary_container.default.hex}}; $inversePrimary: {{colors.inverse_primary.default.hex}}; $secondary: {{colors.secondary.default.hex}}; $onSecondary: {{colors.on_secondary.default.hex}}; $secondaryContainer: {{colors.secondary_container.default.hex}}; $onSecondaryContainer: {{colors.on_secondary_container.default.hex}}; $tertiary: {{colors.tertiary.default.hex}}; $onTertiary: {{colors.on_tertiary.default.hex}}; $tertiaryContainer: {{colors.tertiary_container.default.hex}}; $onTertiaryContainer: {{colors.on_tertiary_container.default.hex}}; $error: {{colors.error.default.hex}}; $onError: {{colors.on_error.default.hex}}; $errorContainer: {{colors.error_container.default.hex}}; $onErrorContainer: {{colors.on_error_container.default.hex}}; $primaryFixed: {{colors.primary_fixed.default.hex}}; $primaryFixedDim: {{colors.primary_fixed_dim.default.hex}}; $onPrimaryFixed: {{colors.on_primary_fixed.default.hex}}; $onPrimaryFixedVariant: {{colors.on_primary_fixed_variant.default.hex}}; $secondaryFixed: {{colors.secondary_fixed.default.hex}}; $secondaryFixedDim: {{colors.secondary_fixed_dim.default.hex}}; $onSecondaryFixed: {{colors.on_secondary_fixed.default.hex}}; $onSecondaryFixedVariant: {{colors.on_secondary_fixed_variant.default.hex}}; $tertiaryFixed: {{colors.tertiary_fixed.default.hex}}; $tertiaryFixedDim: {{colors.tertiary_fixed_dim.default.hex}}; $onTertiaryFixed: {{colors.on_tertiary_fixed.default.hex}}; $onTertiaryFixedVariant: {{colors.on_tertiary_fixed_variant.default.hex}}; $success: #B5CCBA; $onSuccess: #213528; $successContainer: #374B3E; $onSuccessContainer: #D1E9D6; $term0: #0D1C20; $term1: #8383FF; $term2: #63DFD4; $term3: #75FCDD; $term4: #76B4BD; $term5: #7AAEEA; $term6: #81D8D7; $term7: #CCDBD5; $term8: #B1BCB5; $term9: #BCB9FF; $term10: #F6FFFD; $term11: #FFFFFF; $term12: #BEE3E5; $term13: #C8DAFF; $term14: #E5FFFE; $term15: #ADEDF6; ================================================ FILE: dots/.config/matugen/templates/ags/sourceviewtheme-light.xml ================================================ end_4 <_description>Catppuccin port but very random ` + `${NotificationUtils.processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "
")}` } onLinkActivated: (link) => { Qt.openUrlExternally(link) GlobalStates.sidebarRightOpen = false } PointingHandLinkHover {} } Item { Layout.fillWidth: true implicitWidth: actionsFlickable.implicitWidth implicitHeight: actionsFlickable.implicitHeight layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: actionsFlickable.width height: actionsFlickable.height radius: Appearance.rounding.small } } ScrollEdgeFade { target: actionsFlickable vertical: false } StyledFlickable { // Notification actions id: actionsFlickable anchors.fill: parent implicitHeight: actionRowLayout.implicitHeight contentWidth: actionRowLayout.implicitWidth Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on height { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitHeight { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } RowLayout { id: actionRowLayout Layout.alignment: Qt.AlignBottom NotificationActionButton { Layout.fillWidth: true buttonText: Translation.tr("Close") urgency: notificationObject.urgency implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : (contentItem.implicitWidth + leftPadding + rightPadding) onClicked: { root.destroyWithAnimation() } contentItem: MaterialSymbol { iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface text: "close" } } Repeater { id: actionRepeater model: notificationObject.actions NotificationActionButton { id: notifAction required property var modelData Layout.fillWidth: true buttonText: modelData.text urgency: notificationObject.urgency onClicked: { Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier); } } } NotificationActionButton { Layout.fillWidth: true urgency: notificationObject.urgency implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : (contentItem.implicitWidth + leftPadding + rightPadding) onClicked: { Quickshell.clipboardText = notificationObject.body copyIcon.text = "inventory" copyIconTimer.restart() } Timer { id: copyIconTimer interval: 1500 repeat: false onTriggered: { copyIcon.text = "content_copy" } } contentItem: MaterialSymbol { id: copyIcon iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: (notificationObject.urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface text: "content_copy" } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/NotificationListView.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common.widgets import qs.services import QtQuick import Quickshell StyledListView { // Scrollable window id: root property bool popup: false spacing: 3 model: ScriptModel { values: root.popup ? Notifications.popupAppNameList : Notifications.appNameList } delegate: NotificationGroup { required property int index required property var modelData popup: root.popup width: ListView.view.width // https://doc.qt.io/qt-6/qml-qtquick-listview.html notificationGroup: popup ? Notifications.popupGroupsByAppName[modelData] : Notifications.groupsByAppName[modelData] } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/OptionalMaterialSymbol.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets Loader { id: root required property string icon property real iconSize: Appearance.font.pixelSize.larger Layout.alignment: Qt.AlignVCenter active: root.icon && root.icon.length > 0 visible: active sourceComponent: Item { implicitWidth: materialSymbol.implicitWidth MaterialSymbol { id: materialSymbol anchors.centerIn: parent iconSize: root.iconSize color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer text: root.icon } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/PagePlaceholder.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets Item { id: root property bool shown: true property alias icon: shapeWidget.text property alias title: widgetNameText.text property alias description: widgetDescriptionText.text property alias shape: shapeWidget.shape property alias descriptionHorizontalAlignment: widgetDescriptionText.horizontalAlignment opacity: shown ? 1 : 0 visible: opacity > 0 anchors { fill: parent topMargin: -30 * (1 - opacity) bottomMargin: 30 * (1 - opacity) } Behavior on opacity { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } ColumnLayout { anchors.centerIn: parent spacing: 5 MaterialShapeWrappedMaterialSymbol { id: shapeWidget Layout.alignment: Qt.AlignHCenter padding: 12 iconSize: 56 rotation: -30 * (1 - root.opacity) } StyledText { id: widgetNameText visible: title !== "" Layout.alignment: Qt.AlignHCenter font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.larger variableAxes: Appearance.font.variableAxes.title } color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter } StyledText { id: widgetDescriptionText visible: description !== "" Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignLeft wrapMode: Text.Wrap } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/PointingHandInteraction.qml ================================================ import QtQuick MouseArea { anchors.fill: parent onPressed: (mouse) => mouse.accepted = false cursorShape: Qt.PointingHandCursor } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/PointingHandLinkHover.qml ================================================ import QtQuick MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton // Only for hover hoverEnabled: true cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Item { id: root property string text: "" property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false property real horizontalPadding: 10 property real verticalPadding: 5 property real horizontalMargin: horizontalPadding property real verticalMargin: verticalPadding function updateAnchor() { tooltipLoader.item?.anchor.updateAnchor(); } readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition property var anchorEdges: Edges.Top property var anchorGravity: anchorEdges property Item contentItem: StyledToolTipContent { id: contentItem anchors.centerIn: parent text: root.text shown: false Component.onCompleted: shown = true horizontalPadding: root.horizontalPadding verticalPadding: root.verticalPadding } Loader { id: tooltipLoader anchors.fill: parent active: root.internalVisibleCondition sourceComponent: PopupWindow { visible: true anchor { window: root.QsWindow.window item: root.parent edges: root.anchorEdges gravity: root.anchorGravity } mask: Region { item: null } color: "transparent" implicitWidth: root.contentItem.implicitWidth + root.horizontalMargin * 2 implicitHeight: root.contentItem.implicitHeight + root.verticalMargin * 2 data: [root.contentItem] } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml ================================================ import qs.modules.common import QtQuick /** * Recreation of GTK revealer. Expects one single child. */ Item { id: root property bool reveal property bool vertical: false clip: true implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 visible: reveal || (implicitWidth > 0 && !vertical) || (implicitHeight > 0 && vertical) Behavior on implicitWidth { enabled: !vertical animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on implicitHeight { enabled: vertical animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/RippleButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls /** * A button with ripple effect similar to in Material Design. */ Button { id: root property bool toggled property string buttonText property bool pointingHandCursor: true property real buttonRadius: Appearance?.rounding?.small ?? 4 property real buttonRadiusPressed: buttonRadius property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius property int rippleDuration: 1200 property bool rippleEnabled: true property var downAction // When left clicking (down) property var releaseAction // When left clicking (release) property var altAction // When right clicking property var middleClickAction // When middle clicking property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || "transparent" property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? "#E5DFED" property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" opacity: root.enabled ? 1 : 0.4 property color buttonColor: ColorUtils.transparentize(root.toggled ? (root.hovered ? colBackgroundToggledHover : colBackgroundToggled) : (root.hovered ? colBackgroundHover : colBackground), root.enabled ? 0 : 1) property color rippleColor: root.toggled ? colRippleToggled : colRipple function startRipple(x, y) { const stateY = buttonBackground.y; rippleAnim.x = x; rippleAnim.y = y - stateY; const dist = (ox,oy) => ox*ox + oy*oy const stateEndY = stateY + buttonBackground.height rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY))) rippleFadeAnim.complete(); rippleAnim.restart(); } component RippleAnim: NumberAnimation { duration: rippleDuration easing.type: Appearance?.animation.elementMoveEnter.type easing.bezierCurve: Appearance?.animationCurves.standardDecel } MouseArea { anchors.fill: parent cursorShape: root.pointingHandCursor ? Qt.PointingHandCursor : Qt.ArrowCursor acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onPressed: (event) => { if(event.button === Qt.RightButton) { if (root.altAction) root.altAction(event); return; } if(event.button === Qt.MiddleButton) { if (root.middleClickAction) root.middleClickAction(); return; } root.down = true if (root.downAction) root.downAction(); if (!root.rippleEnabled) return; const {x,y} = event startRipple(x, y) } onReleased: (event) => { root.down = false if (event.button != Qt.LeftButton) return; if (root.releaseAction) root.releaseAction(); root.click() // Because the MouseArea already consumed the event if (!root.rippleEnabled) return; rippleFadeAnim.restart(); } onCanceled: (event) => { root.down = false if (!root.rippleEnabled) return; rippleFadeAnim.restart(); } } RippleAnim { id: rippleFadeAnim duration: rippleDuration * 2 target: ripple property: "opacity" to: 0 } SequentialAnimation { id: rippleAnim property real x property real y property real radius PropertyAction { target: ripple property: "x" value: rippleAnim.x } PropertyAction { target: ripple property: "y" value: rippleAnim.y } PropertyAction { target: ripple property: "opacity" value: 1 } ParallelAnimation { RippleAnim { target: ripple properties: "implicitWidth,implicitHeight" from: 0 to: rippleAnim.radius * 2 } } } background: Rectangle { id: buttonBackground radius: root.buttonEffectiveRadius implicitHeight: 30 color: root.buttonColor Behavior on color { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: buttonBackground.width height: buttonBackground.height radius: root.buttonEffectiveRadius } } Item { id: ripple width: ripple.implicitWidth height: ripple.implicitHeight opacity: 0 visible: width > 0 && height > 0 property real implicitWidth: 0 property real implicitHeight: 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } RadialGradient { anchors.fill: parent gradient: Gradient { GradientStop { position: 0.0; color: root.rippleColor } GradientStop { position: 0.3; color: root.rippleColor } GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) } } } transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 } } } contentItem: StyledText { text: root.buttonText } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/RippleButtonWithIcon.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets RippleButton { id: buttonWithIconRoot property string nerdIcon property string materialIcon property bool materialIconFill: true property string mainText: "Button text" property Component mainContentComponent: Component { StyledText { visible: text !== "" text: buttonWithIconRoot.mainText font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnSecondaryContainer } } implicitHeight: 35 horizontalPadding: 10 buttonRadius: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 contentItem: RowLayout { Item { Layout.fillWidth: false implicitWidth: Math.max(materialIconLoader.implicitWidth, nerdIconLoader.implicitWidth) Loader { id: materialIconLoader anchors.centerIn: parent active: !nerdIcon sourceComponent: MaterialSymbol { text: buttonWithIconRoot.materialIcon iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSecondaryContainer fill: buttonWithIconRoot.materialIconFill ? 1 : 0 } } Loader { id: nerdIconLoader anchors.centerIn: parent active: nerdIcon sourceComponent: StyledText { text: buttonWithIconRoot.nerdIcon font.pixelSize: Appearance.font.pixelSize.larger font.family: Appearance.font.family.iconNerd color: Appearance.colors.colOnSecondaryContainer } } } Loader { Layout.fillWidth: true sourceComponent: buttonWithIconRoot.mainContentComponent Layout.alignment: Qt.AlignVCenter } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/RoundCorner.qml ================================================ import QtQuick import QtQuick.Shapes Item { id: root enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight } property var corner: RoundCorner.CornerEnum.TopLeft property alias leftVisualMargin: shape.anchors.leftMargin property alias topVisualMargin: shape.anchors.topMargin property alias rightVisualMargin: shape.anchors.rightMargin property alias bottomVisualMargin: shape.anchors.bottomMargin property int implicitSize: 25 property color color: "#000000" implicitWidth: implicitSize implicitHeight: implicitSize property bool isTopLeft: corner === RoundCorner.CornerEnum.TopLeft property bool isBottomLeft: corner === RoundCorner.CornerEnum.BottomLeft property bool isTopRight: corner === RoundCorner.CornerEnum.TopRight property bool isBottomRight: corner === RoundCorner.CornerEnum.BottomRight property bool isTop: isTopLeft || isTopRight property bool isBottom: isBottomLeft || isBottomRight property bool isLeft: isTopLeft || isBottomLeft property bool isRight: isTopRight || isBottomRight Shape { id: shape anchors { top: root.isTop ? parent.top : undefined bottom: root.isBottom ? parent.bottom : undefined left: root.isLeft ? parent.left : undefined right: root.isRight ? parent.right : undefined } layer.enabled: true layer.smooth: true preferredRendererType: Shape.CurveRenderer ShapePath { id: shapePath strokeWidth: 0 fillColor: root.color pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting startX: switch (root.corner) { case RoundCorner.CornerEnum.TopLeft: case RoundCorner.CornerEnum.BottomLeft: return 0; case RoundCorner.CornerEnum.TopRight: case RoundCorner.CornerEnum.BottomRight: return root.implicitSize; } startY: switch (root.corner) { case RoundCorner.CornerEnum.TopLeft: case RoundCorner.CornerEnum.TopRight: return 0; case RoundCorner.CornerEnum.BottomLeft: case RoundCorner.CornerEnum.BottomRight: return root.implicitSize; } PathAngleArc { moveToStart: false centerX: root.implicitSize - shapePath.startX centerY: root.implicitSize - shapePath.startY radiusX: root.implicitSize radiusY: root.implicitSize startAngle: switch (root.corner) { case RoundCorner.CornerEnum.TopLeft: return 180; case RoundCorner.CornerEnum.TopRight: return -90; case RoundCorner.CornerEnum.BottomLeft: return 90; case RoundCorner.CornerEnum.BottomRight: return 0; } sweepAngle: 90 } PathLine { x: shapePath.startX y: shapePath.startY } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ScrollEdgeFade.qml ================================================ import QtQuick import qs.modules.common import qs.modules.common.functions Item { id: root z: 99 required property Item target property real fadeSize: Appearance.m3colors.darkmode ? 40 : 20 property color color: Appearance.colors.colLayer1Base property bool vertical: true anchors.fill: target EndGradient { anchors { top: parent.top left: parent.left right: vertical ? parent.right : undefined bottom: vertical ? undefined : parent.bottom } shown: !(root.vertical ? root.target.atYBeginning : root.target.atXBeginning) } EndGradient { anchors { bottom: parent.bottom right: parent.right left: vertical ? parent.left : undefined top: vertical ? undefined : parent.top } shown: !(root.vertical ? root.target.atYEnd : root.target.atXEnd) rotation: 180 } component EndGradient: Rectangle { required property bool shown height: vertical ? root.fadeSize : parent.height width: vertical ? parent.width : root.fadeSize opacity: shown ? 1 : 0 visible: opacity > 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } gradient: Gradient { orientation: root.vertical ? Gradient.Vertical : Gradient.Horizontal GradientStop { position: 0.0 color: root.color } GradientStop { position: 1.0 color: ColorUtils.transparentize(root.color) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.models TabBar { id: root property real indicatorPadding: 8 Layout.fillWidth: true background: Item { WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) root.incrementCurrentIndex(); else if (event.angleDelta.y > 0) root.decrementCurrentIndex(); } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } Rectangle { id: activeIndicator z: 9999 anchors.bottom: parent.bottom topLeftRadius: height topRightRadius: height bottomLeftRadius: 0 bottomRightRadius: 0 color: Appearance.colors.colPrimary // Animation property real baseWidth: root.width / root.count AnimatedTabIndexPair { id: idxPair index: root.currentIndex } height: 3 x: Math.min(idxPair.idx1, idxPair.idx2) * baseWidth + root.indicatorPadding width: ((Math.max(idxPair.idx1, idxPair.idx2) + 1) * baseWidth - root.indicatorPadding) - x } Rectangle { // Tabbar bottom border id: tabBarBottomBorder z: 9998 anchors.bottom: parent.bottom height: 1 anchors { left: parent.left right: parent.right } color: Appearance.colors.colOutlineVariant } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml ================================================ import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts TabButton { id: root property string buttonText property string buttonIcon property int rippleDuration: 1200 property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 property color colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) property color colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, root.checked ? 1 : 0.95) property color colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95) PointingHandInteraction {} component RippleAnim: NumberAnimation { duration: rippleDuration easing.type: Appearance.animation.elementMoveEnter.type easing.bezierCurve: Appearance.animationCurves.standardDecel } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onPressed: (event) => { root.click() // Because the MouseArea already consumed the event const {x,y} = event const stateY = buttonBackground.y; rippleAnim.x = x; rippleAnim.y = y - stateY; const dist = (ox,oy) => ox*ox + oy*oy const stateEndY = stateY + buttonBackground.height rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY))) rippleFadeAnim.complete(); rippleAnim.restart(); } onReleased: (event) => { rippleFadeAnim.restart(); } } RippleAnim { id: rippleFadeAnim duration: rippleDuration * 2 target: ripple property: "opacity" to: 0 } SequentialAnimation { id: rippleAnim property real x property real y property real radius PropertyAction { target: ripple property: "x" value: rippleAnim.x } PropertyAction { target: ripple property: "y" value: rippleAnim.y } PropertyAction { target: ripple property: "opacity" value: 1 } ParallelAnimation { RippleAnim { target: ripple properties: "implicitWidth,implicitHeight" from: 0 to: rippleAnim.radius * 2 } } } background: Rectangle { id: buttonBackground anchors { fill: parent margins: 3 } radius: Appearance?.rounding.normal implicitHeight: 42 color: (root.hovered ? root.colBackgroundHover : root.colBackground) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: buttonBackground.width height: buttonBackground.height radius: buttonBackground.radius } } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } Item { id: ripple width: ripple.implicitWidth height: ripple.implicitHeight opacity: 0 property real implicitWidth: 0 property real implicitHeight: 0 visible: width > 0 && height > 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) } RadialGradient { anchors.fill: parent gradient: Gradient { GradientStop { position: 0.0; color: root.colRipple } GradientStop { position: 0.3; color: root.colRipple } GradientStop { position: 0.5 ; color: Qt.rgba(root.colRipple.r, root.colRipple.g, root.colRipple.b, 0) } } } transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 } } } contentItem: Item { anchors.centerIn: buttonBackground RowLayout { anchors.centerIn: parent spacing: 0 Loader { id: iconLoader active: buttonIcon?.length > 0 sourceComponent: buttonIcon?.length > 0 ? materialSymbolComponent : null Layout.rightMargin: 5 } Component { id: materialSymbolComponent MaterialSymbol { verticalAlignment: Text.AlignVCenter text: buttonIcon iconSize: Appearance.font.pixelSize.huge fill: root.checked ? 1 : 0 color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } StyledText { id: buttonTextWidget verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts import Quickshell Item { id: root property real dialogPadding: 15 property real dialogMargin: 30 property string titleText: "Selection Dialog" property alias items: choiceModel.values property int selectedId: choiceListView.currentIndex property var defaultChoice signal canceled(); signal selected(var result); Rectangle { // Scrim id: scrimOverlay anchors.fill: parent radius: Appearance.rounding.small color: Appearance.colors.colScrim MouseArea { hoverEnabled: true anchors.fill: parent preventStealing: true propagateComposedEvents: false } } Rectangle { // The dialog id: dialog color: Appearance.m3colors.m3surfaceContainerHigh radius: Appearance.rounding.normal anchors.fill: parent anchors.margins: dialogMargin implicitHeight: dialogColumnLayout.implicitHeight ColumnLayout { id: dialogColumnLayout anchors.fill: parent spacing: 16 StyledText { id: dialogTitle Layout.topMargin: dialogPadding Layout.leftMargin: dialogPadding Layout.rightMargin: dialogPadding Layout.alignment: Qt.AlignLeft color: Appearance.m3colors.m3onSurface font.pixelSize: Appearance.font.pixelSize.larger text: root.titleText } Rectangle { color: Appearance.m3colors.m3outline implicitHeight: 1 Layout.fillWidth: true Layout.leftMargin: dialogPadding Layout.rightMargin: dialogPadding } StyledListView { id: choiceListView Layout.fillWidth: true Layout.fillHeight: true clip: true currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1 spacing: 6 model: ScriptModel { id: choiceModel } delegate: StyledRadioButton { id: radioButton required property var modelData required property int index anchors { left: parent?.left right: parent?.right leftMargin: root.dialogPadding rightMargin: root.dialogPadding } description: modelData.toString() checked: index === choiceListView.currentIndex onCheckedChanged: { if (checked) { choiceListView.currentIndex = index; } } } } Rectangle { color: Appearance.m3colors.m3outline implicitHeight: 1 Layout.fillWidth: true Layout.leftMargin: dialogPadding Layout.rightMargin: dialogPadding } RowLayout { id: dialogButtonsRowLayout Layout.bottomMargin: dialogPadding Layout.leftMargin: dialogPadding Layout.rightMargin: dialogPadding Layout.alignment: Qt.AlignRight DialogButton { buttonText: Translation.tr("Cancel") onClicked: root.canceled() } DialogButton { buttonText: Translation.tr("OK") onClicked: root.selected( root.selectedId === -1 ? null : root.items[root.selectedId] ) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SelectionGroupButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Hyprland import qs.services import qs.modules.common import qs.modules.common.widgets GroupButton { id: root horizontalPadding: 12 verticalPadding: 8 bounce: false property string buttonIcon property bool leftmost: false property bool rightmost: false leftRadius: (toggled || leftmost) ? (height / 2) : Appearance.rounding.unsharpenmore rightRadius: (toggled || rightmost) ? (height / 2) : Appearance.rounding.unsharpenmore colBackground: Appearance.colors.colSecondaryContainer colBackgroundHover: Appearance.colors.colSecondaryContainerHover colBackgroundActive: Appearance.colors.colSecondaryContainerActive contentItem: RowLayout { spacing: 4 * (root.buttonText?.length > 0) Loader { Layout.alignment: Qt.AlignVCenter active: root.buttonIcon && root.buttonIcon.length > 0 visible: active sourceComponent: Item { implicitWidth: materialSymbol.implicitWidth MaterialSymbol { id: materialSymbol anchors.centerIn: parent text: root.buttonIcon iconSize: Appearance.font.pixelSize.larger color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer } } } Item { implicitWidth: root.buttonText?.length > 0 ? textItem.implicitWidth : 0 implicitHeight: textMetrics.height // Force height to that of regular text TextMetrics { id: textMetrics font.family: Appearance.font.family.main text: "Abc" } StyledText { id: textItem anchors.centerIn: parent color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer text: root.buttonText } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SineCookie.qml ================================================ import QtQuick import QtQuick.Shapes import Quickshell import qs.modules.common Item { id: root property real sides: 12 property int implicitSize: 100 property real amplitude: implicitSize / 50 property int renderPoints: 360 property color color: "#605790" property alias strokeWidth: shapePath.strokeWidth property bool constantlyRotate: false implicitWidth: implicitSize implicitHeight: implicitSize property real shapeRotation: 0 Loader { active: constantlyRotate sourceComponent: FrameAnimation { running: true onTriggered: { shapeRotation += 0.05 } } } Behavior on sides { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Shape { id: shape anchors.fill: parent preferredRendererType: Shape.CurveRenderer ShapePath { id: shapePath strokeWidth: 0 fillColor: root.color pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting PathPolyline { property var pointsList: { var points = [] var cx = shape.width / 2 // center x var cy = shape.height / 2 // center y var steps = root.renderPoints var radius = root.implicitSize / 2 - root.amplitude for (var i = 0; i <= steps; i++) { var angle = (i / steps) * 2 * Math.PI var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate) var wave = Math.sin(rotatedAngle) * root.amplitude var x = Math.cos(angle) * (radius + wave) + cx var y = Math.sin(angle) * (radius + wave) + cy points.push(Qt.point(x, y)) } return points } path: pointsList } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs.modules.common // Annotation similar to how Google Lens does it. Item { id: root property real scaleFactor: 1.0 property alias font: textWidget.font property alias color: textWidget.color property string text: "" property bool rotate90: false property real maxFontPixelSize: 100 visible: false Component.onCompleted: updateText() onTextChanged: updateText() property bool searching: false property real searchPixelSize: Appearance.font.pixelSize.small property real renderPixelSize: Appearance.font.pixelSize.small font.pixelSize: searching ? searchPixelSize : (renderPixelSize * scaleFactor) function updateText() { // Do we rotate? root.rotate90 = false; const textAspectRatio = textMetrics.width / textMetrics.height const areaAspectRatio = root.width / root.height if ((textAspectRatio > 1 && areaAspectRatio < 1) || (textAspectRatio < 1 && areaAspectRatio > 1)) { root.rotate90 = true; } const targetWidth = (root.rotate90 ? root.height : root.width) / root.scaleFactor; const targetHeight = (root.rotate90 ? root.width : root.height) / root.scaleFactor; // Binary search to find the correct font size var lower = 0 var upper = maxFontPixelSize root.searching = true; while (upper - lower > 0.00001) { var mid = (lower + upper) / 2; // print("bin searching", mid, "target", targetWidth, targetHeight, "actual", textWidget.contentWidth, textWidget.contentHeight); root.searchPixelSize = mid if (textWidget.contentHeight > targetHeight) { upper = mid } else { lower = mid } } root.renderPixelSize = lower root.searching = false; root.visible = true } TextMetrics { id: textMetrics text: root.text font: root.font } StyledText { id: textWidget anchors.centerIn: parent width: root.rotate90 ? parent.height : parent.width text: root.text rotation: root.rotate90 ? 90 : 0 renderType: Text.QtRendering wrapMode: Text.Wrap } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledBlurEffect.qml ================================================ import QtQuick import QtQuick.Effects MultiEffect { id: root source: wallpaper anchors.fill: source saturation: 0.2 blurEnabled: true blurMax: 100 blur: 1 } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledComboBox.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions ComboBox { id: root property string buttonIcon: "" property real buttonRadius: height / 2 property color colBackground: Appearance.colors.colSecondaryContainer property color colBackgroundHover: Appearance.colors.colSecondaryContainerHover property color colBackgroundActive: Appearance.colors.colSecondaryContainerActive implicitHeight: 40 Layout.fillWidth: true background: Rectangle { radius: root.buttonRadius color: (root.down && !root.popup.visible) ? root.colBackgroundActive : root.hovered ? root.colBackgroundHover : root.colBackground Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton cursorShape: Qt.PointingHandCursor } } indicator: MaterialSymbol { x: root.width - width - 16 y: root.height / 2 - height / 2 text: "keyboard_arrow_down" iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSecondaryContainer rotation: root.popup.visible ? 180 : 0 Behavior on rotation { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } contentItem: Item { implicitWidth: buttonLayout.implicitWidth implicitHeight: buttonLayout.implicitHeight RowLayout { id: buttonLayout anchors.fill: parent spacing: 8 anchors.leftMargin: 16 anchors.rightMargin: 16 Loader { Layout.alignment: Qt.AlignVCenter active: root.buttonIcon.length > 0 || (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon) visible: active sourceComponent: MaterialSymbol { text: { if (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon) { return root.model[root.currentIndex].icon; } return root.buttonIcon; } iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSecondaryContainer } } StyledText { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter color: Appearance.colors.colOnSecondaryContainer text: root.displayText elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } } } delegate: ItemDelegate { id: itemDelegate width: ListView.view ? ListView.view.width : root.width implicitHeight: 40 required property var model required property int index property color color: { if (root.currentIndex === itemDelegate.index) { if (itemDelegate.down) return Appearance.colors.colSecondaryContainerActive; if (itemDelegate.hovered) return Appearance.colors.colSecondaryContainerHover; return Appearance.colors.colSecondaryContainer; } else { if (itemDelegate.down) return Appearance.colors.colLayer3Active; if (itemDelegate.hovered) return Appearance.colors.colLayer3Hover; return ColorUtils.transparentize(Appearance.colors.colLayer3); } } property color colText: (root.currentIndex === itemDelegate.index) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer3 background: Rectangle { anchors.fill: parent radius: Appearance.rounding.small color: itemDelegate.color Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton cursorShape: Qt.PointingHandCursor } } contentItem: RowLayout { spacing: 8 anchors.leftMargin: 12 anchors.rightMargin: 12 Loader { Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: Appearance.font.pixelSize.larger active: typeof itemDelegate.model === 'object' && itemDelegate.model?.icon?.length > 0 visible: active sourceComponent: Item { implicitWidth: icon.implicitWidth implicitHeight: Appearance.font.pixelSize.larger MaterialSymbol { id: icon anchors.centerIn: parent text: itemDelegate.model?.icon ?? "" iconSize: Appearance.font.pixelSize.larger color: itemDelegate.colText } } } StyledText { Layout.fillWidth: true Layout.preferredHeight: Appearance.font.pixelSize.larger color: itemDelegate.colText text: itemDelegate.model[root.textRole] elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } } } popup: Popup { y: root.height + 4 width: root.width height: Math.min(listView.contentHeight + topPadding + bottomPadding, 300) padding: 8 enter: Transition { PropertyAnimation { properties: "opacity" to: 1 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } exit: Transition { PropertyAnimation { properties: "opacity" to: 0 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } background: Item { StyledRectangularShadow { target: popupBackground } Rectangle { id: popupBackground anchors.fill: parent radius: Appearance.rounding.normal color: Appearance.m3colors.m3surfaceContainerHigh } } contentItem: StyledListView { id: listView clip: true implicitHeight: contentHeight spacing: 2 model: root.popup.visible ? root.delegateModel : null currentIndex: root.highlightedIndex } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledDropShadow.qml ================================================ import QtQuick import Qt5Compat.GraphicalEffects import qs.modules.common DropShadow { required property var target source: target anchors.fill: source radius: 8 samples: radius * 2 + 1 color: Appearance.colors.colShadow transparentBorder: true } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.common Flickable { id: root maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100 property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50 property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120 // Accumulated scroll destination so wheel deltas stack while animating property real scrollTargetY: 0 ScrollBar.vertical: StyledScrollBar {} MouseArea { visible: Config?.options.interactions.scrolling.fasterTouchpadScroll anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; // The angleDelta.y of a touchpad is usually small and continuous, // while that of a mouse wheel is typically in multiples of ±120. var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; const maxY = Math.max(0, root.contentHeight - root.height); const base = scrollAnim.running ? root.scrollTargetY : root.contentY; var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY)); root.scrollTargetY = targetY; root.contentY = targetY; wheelEvent.accepted = true; } } Behavior on contentY { NumberAnimation { id: scrollAnim duration: Appearance.animation.scroll.duration easing.type: Appearance.animation.scroll.type easing.bezierCurve: Appearance.animation.scroll.bezierCurve } } // Keep target synced when not animating (e.g., drag/flick or programmatic changes) onContentYChanged: { if (!scrollAnim.running) { root.scrollTargetY = root.contentY; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledImage.qml ================================================ import QtQuick import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions Image { asynchronous: true retainWhileLoading: true visible: opacity > 0 opacity: (status === Image.Ready) ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } property list fallbacks: [] property int currentFallbackIndex: 0 onStatusChanged: { if (status === Image.Error && currentFallbackIndex < fallbacks.length) { source = fallbacks[currentFallbackIndex]; currentFallbackIndex += 1; } } sourceSize: { const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1; return Qt.size(width * dpr, height * dpr); } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledIndeterminateProgressBar.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Controls.Material import QtQuick.Controls ProgressBar { indeterminate: true Material.accent: Appearance.colors.colPrimary } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledListView.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls /** * A ListView with animations. */ ListView { id: root spacing: 5 property real removeOvershoot: 20 // Account for gaps and bouncy animations property int dragIndex: -1 property real dragDistance: 0 property bool popin: true property bool animateAppearance: true property bool animateMovement: false // Accumulated scroll destination so wheel deltas stack while animating property real scrollTargetY: 0 property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100 property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50 property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120 function resetDrag() { root.dragIndex = -1 root.dragDistance = 0 } maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds ScrollBar.vertical: StyledScrollBar {} MouseArea { visible: Config?.options.interactions.scrolling.fasterTouchpadScroll anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; // The angleDelta.y of a touchpad is usually small and continuous, // while that of a mouse wheel is typically in multiples of ±120. var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; const maxY = Math.max(0, root.contentHeight - root.height); const base = scrollAnim.running ? root.scrollTargetY : root.contentY; var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY)); root.scrollTargetY = targetY; root.contentY = targetY; wheelEvent.accepted = true; } } Behavior on contentY { NumberAnimation { id: scrollAnim alwaysRunToEnd: true duration: Appearance.animation.scroll.duration easing.type: Appearance.animation.scroll.type easing.bezierCurve: Appearance.animation.scroll.bezierCurve } } // Keep target synced when not animating (e.g., drag/flick or programmatic changes) onContentYChanged: { if (!scrollAnim.running) { root.scrollTargetY = root.contentY; } } add: Transition { animations: animateAppearance ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: popin ? "opacity,scale" : "opacity", from: 0, to: 1, }), ] : [] } addDisplaced: Transition { animations: animateAppearance ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: popin ? "opacity,scale" : "opacity", to: 1, }), ] : [] } displaced: Transition { animations: root.animateMovement ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), ] : [] } move: Transition { animations: root.animateMovement ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), ] : [] } moveDisplaced: Transition { animations: root.animateMovement ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), ] : [] } remove: Transition { animations: animateAppearance ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "x", to: root.width + root.removeOvershoot, }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "opacity", to: 0, }) ] : [] } // This is movement when something is removed, not removing animation! removeDisplaced: Transition { animations: animateAppearance ? [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { property: "y", }), Appearance?.animation.elementMove.numberAnimation.createObject(this, { properties: "opacity,scale", to: 1, }), ] : [] } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledProgressBar.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls /** * Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview */ ProgressBar { id: root property real valueBarWidth: 120 property real valueBarHeight: 4 property real valueBarGap: 4 property color highlightColor: Appearance?.colors.colPrimary ?? "#685496" property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? "#F1D3F9" property bool wavy: false // If true, the progress bar will have a wavy fill effect property bool animateWave: true property real waveAmplitudeMultiplier: wavy ? 0.5 : 0 property real waveFrequency: 6 property real waveFps: 60 Behavior on waveAmplitudeMultiplier { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on value { animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this) } background: Item { implicitHeight: valueBarHeight implicitWidth: valueBarWidth } contentItem: Item { id: contentItem anchors.fill: parent Loader { anchors { left: parent.left verticalCenter: parent.verticalCenter } active: root.wavy sourceComponent: WavyLine { id: wavyFill frequency: root.waveFrequency color: root.highlightColor amplitudeMultiplier: root.wavy ? 0.5 : 0 height: contentItem.height * 6 width: contentItem.width * root.visualPosition lineWidth: contentItem.height fullLength: root.width Connections { target: root function onValueChanged() { wavyFill.requestPaint(); } function onHighlightColorChanged() { wavyFill.requestPaint(); } } FrameAnimation { running: root.animateWave onTriggered: { wavyFill.requestPaint() } } } } Loader { active: !root.wavy sourceComponent: Rectangle { anchors.left: parent.left width: contentItem.width * root.visualPosition height: contentItem.height radius: height / 2 color: root.highlightColor } } Rectangle { // Right remaining part fill anchors.right: parent.right width: (1 - root.visualPosition) * parent.width - valueBarGap height: parent.height radius: height / 2 color: root.trackColor } Rectangle { // Stop point anchors.right: parent.right width: valueBarGap height: valueBarGap radius: height / 2 color: root.highlightColor } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledRadioButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Widgets import Quickshell.Services.Pipewire RadioButton { id: root padding: 4 implicitHeight: contentItem.implicitHeight + padding * 2 property string description property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F" PointingHandInteraction {} indicator: Item{} contentItem: RowLayout { id: contentItem Layout.fillWidth: true spacing: 12 Rectangle { id: radio Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter width: 20 height: 20 radius: Appearance?.rounding.full border.color: checked ? root.activeColor : root.inactiveColor border.width: 2 color: "transparent" // Checked indicator Rectangle { anchors.centerIn: parent width: checked ? 10 : 4 height: checked ? 10 : 4 radius: Appearance?.rounding.full color: Appearance?.colors.colPrimary opacity: checked ? 1 : 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on width { animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } Behavior on height { animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } } // Hover Rectangle { anchors.centerIn: parent width: root.hovered ? 40 : 20 height: root.hovered ? 40 : 20 radius: Appearance?.rounding.full color: Appearance?.m3colors.m3onSurface opacity: root.hovered ? 0.1 : 0 Behavior on opacity { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on width { animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } Behavior on height { animation: Appearance?.animation.elementMove.numberAnimation.createObject(this) } } } StyledText { text: root.description Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true wrapMode: Text.Wrap color: Appearance?.m3colors.m3onSurface } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml ================================================ import QtQuick import QtQuick.Effects import qs.modules.common RectangularShadow { required property var target anchors.fill: target radius: target.radius blur: 0.9 * Appearance.sizes.elevationMargin offset: Qt.vector2d(0.0, 1.0) spread: 1 color: Appearance.colors.colShadow cached: true } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.common import qs.modules.common.functions ScrollBar { id: root policy: ScrollBar.AsNeeded topPadding: Appearance.rounding.normal bottomPadding: Appearance.rounding.normal active: hovered || pressed contentItem: Rectangle { implicitWidth: 4 implicitHeight: root.visualSize radius: width / 2 color: Appearance.colors.colOnSurfaceVariant opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0 Behavior on opacity { NumberAnimation { duration: 350 easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Widgets /** * Material 3 slider. See https://m3.material.io/components/sliders/overview * It doesn't exactly match the spec because it does not make sense to have stuff on a computer that fucking huge. * Should be at 3/4 scale... */ Slider { id: root property list stopIndicatorValues: [1] property list dividerValues: [] enum Configuration { Wavy = 4, XS = 12, S = 18, M = 30, L = 42, XL = 72 } property var configuration: StyledSlider.Configuration.S property real handleDefaultWidth: 3 property real handlePressedWidth: 1.5 property color highlightColor: Appearance.colors.colPrimary property color trackColor: Appearance.colors.colSecondaryContainer property color handleColor: Appearance.colors.colPrimary property color dotColor: Appearance.m3colors.m3onSecondaryContainer property color dotColorHighlighted: Appearance.m3colors.m3onPrimary property real unsharpenRadius: Appearance.rounding.unsharpen property real trackWidth: configuration property real trackRadius: trackWidth >= StyledSlider.Configuration.XL ? 21 : trackWidth >= StyledSlider.Configuration.L ? 12 : trackWidth >= StyledSlider.Configuration.M ? 9 : trackWidth >= StyledSlider.Configuration.S ? 6 : height / 2 property real handleHeight: (configuration === StyledSlider.Configuration.Wavy) ? 24 : Math.max(33, trackWidth + 9) property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth property real handleMargins: 4 property real dividerMargins: 2 property real trackDotSize: 3 property bool usePercentTooltip: true property string tooltipContent: usePercentTooltip ? `${Math.round(((value - from) / (to - from)) * 100)}%` : `${Math.round(value)}` property bool wavy: configuration === StyledSlider.Configuration.Wavy // If true, the progress bar will have a wavy fill effect property bool animateWave: true property real waveAmplitudeMultiplier: wavy ? 0.5 : 0 property real waveFrequency: 6 property real waveFps: 60 leftPadding: handleMargins rightPadding: handleMargins property real effectiveDraggingWidth: width - leftPadding - rightPadding Layout.fillWidth: true from: 0 to: 1 Behavior on value { // This makes the adjusted value (like volume) shift smoothly SmoothedAnimation { velocity: Appearance.animation.elementMoveFast.velocity } } Behavior on handleMargins { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } component TrackDot: Rectangle { required property real value property real normalizedValue: (value - root.from) / (root.to - root.from) anchors.verticalCenter: parent.verticalCenter x: root.handleMargins + (normalizedValue * root.effectiveDraggingWidth) - (root.trackDotSize / 2) width: root.trackDotSize height: root.trackDotSize radius: Appearance.rounding.full color: normalizedValue > root.visualPosition ? root.dotColor : root.dotColorHighlighted Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } MouseArea { anchors.fill: parent onPressed: (mouse) => mouse.accepted = false cursorShape: root.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor } background: Item { id: background anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter width: root.width implicitHeight: trackWidth property var normalized: root.dividerValues.map(v => (v - root.from) / (root.to - root.from)) property var filtered: normalized.filter(v => Math.abs(v - root.visualPosition) * effectiveDraggingWidth > handleMargins + handleWidth / 2 - dividerMargins) property var leftValues: [0, ...filtered.filter(v => v < root.visualPosition), root.visualPosition] property var rightValues: [root.visualPosition, ...filtered.filter(v => v > root.visualPosition), 1] property var leftWidths: leftValues.map((v, i, a) => a[i + 1] - v).slice(0, -1) property var rightWidths: rightValues.map((v, i, a) => a[i + 1] - v).slice(0, -1) // Fill left Repeater { model: background.leftWidths.length Loader { required property real index anchors.verticalCenter: background.verticalCenter property real leftMargin: index > 0 ? root.dividerMargins : 0 property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0) width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0) height: root.trackWidth active: !root.wavy sourceComponent: Rectangle { color: root.highlightColor topLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius bottomLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius topRightRadius: root.unsharpenRadius bottomRightRadius: root.unsharpenRadius } } } Repeater { model: background.leftWidths.length Loader { required property int index anchors.verticalCenter: background.verticalCenter property real leftMargin: index > 0 ? root.dividerMargins : 0 property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0) width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0) height: root.height active: root.wavy sourceComponent: WavyLine { id: wavyFill frequency: root.waveFrequency fullLength: root.width color: root.highlightColor amplitudeMultiplier: root.wavy ? 0.5 : 0 width: parent.width height: root.trackWidth Connections { target: root function onValueChanged() { wavyFill.requestPaint(); } function onHighlightColorChanged() { wavyFill.requestPaint(); } } FrameAnimation { running: root.animateWave onTriggered: { wavyFill.requestPaint() } } } } } // Fill right Repeater { model: background.rightWidths.length Rectangle { required property int index anchors.verticalCenter: background.verticalCenter property real leftMargin: index > 0 ? root.dividerMargins : root.handleMargins property real rightMargin: index < background.rightWidths.length - 1 ? root.dividerMargins : 0 x: background.rightValues[index] * root.effectiveDraggingWidth + leftMargin + (index === 0 ? handleWidth / 2 : 0) + leftPadding width: background.rightWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === 0 ? handleWidth / 2 : 0) + (index === background.rightWidths.length - 1 ? rightPadding : 0) height: trackWidth color: root.trackColor topRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius bottomRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius topLeftRadius: root.unsharpenRadius bottomLeftRadius: root.unsharpenRadius } } // Stop indicators Repeater { model: root.stopIndicatorValues TrackDot { required property real modelData value: modelData anchors.verticalCenter: parent?.verticalCenter } } } handle: Rectangle { id: handle implicitWidth: root.handleWidth implicitHeight: root.handleHeight x: root.leftPadding + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2) anchors.verticalCenter: parent.verticalCenter radius: Appearance.rounding.full color: root.handleColor Behavior on implicitWidth { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } StyledToolTip { extraVisibleCondition: root.pressed text: root.tooltipContent font { family: Appearance.font.family.numbers variableAxes: Appearance.font.variableAxes.numbers } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledSpinBox.qml ================================================ import qs.modules.common import qs.modules.common.functions import QtQuick import QtQuick.Controls /** * Material 3 styled SpinBox component. */ SpinBox { id: root property real baseHeight: 35 property real radius: Appearance.rounding.small property real innerButtonRadius: Appearance.rounding.unsharpen editable: true opacity: root.enabled ? 1 : 0.4 background: Rectangle { color: Appearance.colors.colLayer2 radius: root.radius } contentItem: Item { implicitHeight: root.baseHeight implicitWidth: Math.max(labelText.implicitWidth, 40) StyledTextInput { id: labelText anchors.centerIn: parent text: root.value // displayText would make the numbers weird like 1,000 instead of 1000 color: Appearance.colors.colOnLayer2 font.family: Appearance.font.family.numbers font.variableAxes: Appearance.font.variableAxes.numbers font.pixelSize: Appearance.font.pixelSize.small validator: root.validator onTextChanged: { root.value = parseFloat(text); } } } down.indicator: Rectangle { anchors { verticalCenter: parent.verticalCenter left: parent.left } implicitHeight: root.baseHeight implicitWidth: root.baseHeight topLeftRadius: root.radius bottomLeftRadius: root.radius topRightRadius: root.innerButtonRadius bottomRightRadius: root.innerButtonRadius color: root.down.pressed ? Appearance.colors.colLayer2Active : root.down.hovered ? Appearance.colors.colLayer2Hover : ColorUtils.transparentize(Appearance.colors.colLayer2) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MaterialSymbol { anchors.centerIn: parent text: "remove" iconSize: 20 color: Appearance.colors.colOnLayer2 } } up.indicator: Rectangle { anchors { verticalCenter: parent.verticalCenter right: parent.right } implicitHeight: root.baseHeight implicitWidth: root.baseHeight topRightRadius: root.radius bottomRightRadius: root.radius topLeftRadius: root.innerButtonRadius bottomLeftRadius: root.innerButtonRadius color: root.up.pressed ? Appearance.colors.colLayer2Active : root.up.hovered ? Appearance.colors.colLayer2Hover : ColorUtils.transparentize(Appearance.colors.colLayer2) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MaterialSymbol { anchors.centerIn: parent text: "add" iconSize: 20 color: Appearance.colors.colOnLayer2 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Controls /** * Material 3 switch. See https://m3.material.io/components/switch/overview */ Switch { id: root property real scale: 0.75 // Default in m3 spec is huge af implicitHeight: 32 * root.scale implicitWidth: 52 * root.scale property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F" PointingHandInteraction {} // Custom track styling background: Rectangle { width: parent.width height: parent.height radius: Appearance?.rounding.full ?? 9999 color: root.checked ? root.activeColor : root.inactiveColor border.width: 2 * root.scale border.color: root.checked ? root.activeColor : Appearance.m3colors.m3outline Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } Behavior on border.color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } // Custom thumb styling indicator: Rectangle { width: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) height: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale) radius: Appearance.rounding.full color: root.checked ? Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3outline anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale) Behavior on anchors.leftMargin { NumberAnimation { duration: Appearance.animationCurves.expressiveFastSpatialDuration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } } Behavior on width { NumberAnimation { duration: Appearance.animationCurves.expressiveFastSpatialDuration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } } Behavior on height { NumberAnimation { duration: Appearance.animationCurves.expressiveFastSpatialDuration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledText.qml ================================================ import qs.modules.common import QtQuick Text { id: root property bool animateChange: false property real animationDistanceX: 0 property real animationDistanceY: 6 renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter property bool shouldUseNumberFont: /^\d+$/.test(root.text) property var defaultFont: shouldUseNumberFont ? Appearance.font.family.numbers : Appearance.font.family.main font { hintingPreference: Font.PreferDefaultHinting family: defaultFont pixelSize: Appearance?.font.pixelSize.small ?? 15 variableAxes: shouldUseNumberFont ? ({}) : Appearance.font.variableAxes.main } color: Appearance?.m3colors.m3onBackground ?? "black" linkColor: Appearance?.m3colors.m3primary component Anim: NumberAnimation { target: root duration: 300 / 2 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } Component.onCompleted: { textAnimationBehavior.originalX = root.x; textAnimationBehavior.originalY = root.y; } Behavior on text { id: textAnimationBehavior property real originalX: root.x property real originalY: root.y enabled: root.animateChange SequentialAnimation { alwaysRunToEnd: true ParallelAnimation { Anim { property: "x" to: textAnimationBehavior.originalX - root.animationDistanceX easing.type: Easing.InSine } Anim { property: "y" to: textAnimationBehavior.originalY - root.animationDistanceY easing.type: Easing.InSine } Anim { property: "opacity" to: 0 easing.type: Easing.InSine } } PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade) PropertyAction { target: root property: "x" value: textAnimationBehavior.originalX + root.animationDistanceX } PropertyAction { target: root property: "y" value: textAnimationBehavior.originalY + root.animationDistanceY } ParallelAnimation { Anim { property: "x" to: textAnimationBehavior.originalX easing.type: Easing.OutSine } Anim { property: "y" to: textAnimationBehavior.originalY easing.type: Easing.OutSine } Anim { property: "opacity" to: 1 easing.type: Easing.OutSine } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledTextArea.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Controls /** * Does not include visual layout, but includes the easily neglected colors. */ TextArea { renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer placeholderTextColor: Appearance.m3colors.m3outline color: Appearance.colors.colOnLayer0 font { family: Appearance.font.family.main pixelSize: Appearance?.font.pixelSize.small ?? 15 hintingPreference: Font.PreferFullHinting variableAxes: Appearance.font.variableAxes.main } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledTextInput.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Controls /** * Does not include visual layout, but includes the easily neglected colors. */ TextInput { color: Appearance.colors.colOnLayer1 renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer font { family: Appearance.font.family.main pixelSize: Appearance?.font.pixelSize.small ?? 15 hintingPreference: Font.PreferFullHinting variableAxes: Appearance.font.variableAxes.main } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledToolTip.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts ToolTip { id: root property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition verticalPadding: 5 horizontalPadding: 10 background: null font { family: Appearance.font.family.main variableAxes: Appearance.font.variableAxes.main pixelSize: Appearance?.font.pixelSize.smaller ?? 14 hintingPreference: Font.PreferNoHinting // Prevent shaky text } delay: 0 visible: internalVisibleCondition contentItem: StyledToolTipContent { id: contentItem font: root.font text: root.text shown: root.internalVisibleCondition horizontalPadding: root.horizontalPadding verticalPadding: root.verticalPadding } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/StyledToolTipContent.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { id: root required property string text property bool shown: false property real horizontalPadding: 10 property real verticalPadding: 5 property alias font: tooltipTextObject.font implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding property bool isVisible: backgroundRectangle.implicitHeight > 0 Rectangle { id: backgroundRectangle anchors { bottom: root.bottom horizontalCenter: root.horizontalCenter } color: Appearance?.colors.colTooltip ?? "#3C4043" radius: Appearance?.rounding.verysmall ?? 7 opacity: shown ? 1 : 0 implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0 implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0 clip: true Behavior on implicitWidth { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitHeight { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on opacity { animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) } StyledText { id: tooltipTextObject anchors.centerIn: parent text: root.text font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14 font.hintingPreference: Font.PreferNoHinting // Prevent shaky text color: Appearance?.colors.colOnTooltip ?? "#FFFFFF" wrapMode: Text.Wrap } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ThumbnailImage.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions /** * Thumbnail image. It currently generates to the right place at the right size, but does not handle metadata/maintenance on modification. * See Freedesktop's spec: https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html */ StyledImage { id: root property bool generateThumbnail: true required property string sourcePath property string thumbnailSizeName: Images.thumbnailSizeNameForDimensions(sourceSize.width, sourceSize.height) property string thumbnailPath: { if (sourcePath.length == 0) return; const resolvedUrlWithoutFileProtocol = FileUtils.trimFileProtocol(`${Qt.resolvedUrl(sourcePath)}`); const encodedUrlWithoutFileProtocol = resolvedUrlWithoutFileProtocol.split("/").map(part => encodeURIComponent(part)).join("/"); const md5Hash = Qt.md5(`file://${encodedUrlWithoutFileProtocol}`); return `${Directories.genericCache}/thumbnails/${thumbnailSizeName}/${md5Hash}.png`; } source: thumbnailPath asynchronous: true smooth: true mipmap: false opacity: status === Image.Ready ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } onSourceSizeChanged: { if (!root.generateThumbnail) return; thumbnailGeneration.running = false; thumbnailGeneration.running = true; } Process { id: thumbnailGeneration command: { const maxSize = Images.thumbnailSizes[root.thumbnailSizeName]; return ["bash", "-c", `[ -f '${FileUtils.trimFileProtocol(root.thumbnailPath)}' ] && exit 0 || { magick '${root.sourcePath}' -resize ${maxSize}x${maxSize} '${FileUtils.trimFileProtocol(root.thumbnailPath)}' && exit 1; }` ] } onExited: (exitCode, exitStatus) => { if (exitCode === 1) { // Force reload if thumbnail had to be generated root.source = ""; root.source = root.thumbnailPath; // Force reload } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/Toolbar.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets /** * Material 3 expressive style toolbar. * https://m3.material.io/components/toolbars */ Item { id: root property bool enableShadow: true property real padding: 8 property alias colBackground: background.color property alias spacing: toolbarLayout.spacing default property alias toolbarData: toolbarLayout.data implicitWidth: background.implicitWidth implicitHeight: background.implicitHeight property alias radius: background.radius Loader { active: root.enableShadow anchors.fill: background sourceComponent: StyledRectangularShadow { target: background anchors.fill: undefined } } Rectangle { id: background anchors.fill: parent color: Appearance.m3colors.m3surfaceContainer implicitHeight: 56 implicitWidth: toolbarLayout.implicitWidth + root.padding * 2 radius: height / 2 RowLayout { id: toolbarLayout spacing: 4 anchors { fill: parent margins: root.padding } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ToolbarButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common RippleButton { Layout.fillHeight: true buttonRadius: Appearance.rounding.full } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs.modules.common Item { id: root signal clicked(event: var) property alias iconText: fabWidget.iconText default property alias fabData: fabWidget.data property bool enableShadow: true anchors { verticalCenter: parent.verticalCenter } implicitWidth: fabWidget.implicitWidth implicitHeight: fabWidget.implicitHeight Loader { active: root.enableShadow anchors.fill: parent sourceComponent: StyledRectangularShadow { target: fabWidget radius: fabWidget.buttonRadius } } FloatingActionButton { id: fabWidget onClicked: e => root.clicked(e) baseSize: 48 colBackground: Appearance.colors.colTertiaryContainer colBackgroundHover: Appearance.colors.colTertiaryContainerHover colRipple: Appearance.colors.colTertiaryContainerActive colOnBackground: Appearance.colors.colOnTertiaryContainer } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.models import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { id: root property alias currentIndex: tabBar.currentIndex required property var tabButtonList function incrementCurrentIndex() { tabBar.incrementCurrentIndex(); } function decrementCurrentIndex() { tabBar.decrementCurrentIndex(); } function setCurrentIndex(index) { tabBar.setCurrentIndex(index); } Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter implicitWidth: contentItem.implicitWidth implicitHeight: 40 property Component delegate: ToolbarTabButton { required property int index required property var modelData current: index == root.currentIndex text: modelData.name materialSymbol: modelData.icon onClicked: { root.setCurrentIndex(index); } } Row { id: contentItem z: 1 anchors.centerIn: parent spacing: 4 Repeater { model: root.tabButtonList delegate: root.delegate } } Rectangle { id: activeIndicator z: 0 color: Appearance.colors.colSecondaryContainer implicitWidth: contentItem.children[root.currentIndex]?.implicitWidth ?? 0 implicitHeight: contentItem.children[root.currentIndex]?.implicitHeight ?? 0 radius: height / 2 // Animation property Item targetItem: contentItem.children[root.currentIndex] AnimatedTabIndexPair { id: leftBound idx1Duration: 50 idx2Duration: 200 index: activeIndicator.targetItem.x } AnimatedTabIndexPair { id: rightBound idx1Duration: 50 idx2Duration: 200 index: activeIndicator.targetItem.x + activeIndicator.targetItem.width } x: Math.min(leftBound.idx1, leftBound.idx2) width: Math.max(rightBound.idx1, rightBound.idx2) - x } MouseArea { anchors.fill: parent z: 2 acceptedButtons: Qt.NoButton cursorShape: Qt.PointingHandCursor onWheel: event => { if (event.angleDelta.y < 0) { root.incrementCurrentIndex(); } else { root.decrementCurrentIndex(); } } } // TabBar doesn't allow tabs to be of different sizes. That's what I thought... // We use it only for the logic and draw stuff manually TabBar { id: tabBar z: -1 background: null Repeater { // This is to fool the TabBar that it has tabs so it does the indices properly model: root.tabButtonList.length delegate: TabButton { background: null } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts RippleButton { id: root required property string materialSymbol required property bool current horizontalPadding: 10 implicitHeight: 40 implicitWidth: implicitContentWidth + horizontalPadding * 2 buttonRadius: height / 2 colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, current ? 1 : 0.95) colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95) contentItem: Row { id: contentRow anchors.centerIn: parent spacing: 6 MaterialSymbol { id: icon anchors.verticalCenter: parent.verticalCenter iconSize: 22 text: root.materialSymbol } StyledText { id: label anchors.verticalCenter: parent.verticalCenter text: root.text } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/ToolbarTextField.qml ================================================ import QtQuick import QtQuick.Layouts import QtQuick.Controls import qs.modules.common import qs.modules.common.widgets TextField { id: filterField property alias colBackground: background.color Layout.fillHeight: true implicitWidth: 200 padding: 10 placeholderTextColor: Appearance.colors.colSubtext color: Appearance.colors.colOnLayer1 font { family: Appearance.font.family.main pixelSize: Appearance.font.pixelSize.small hintingPreference: Font.PreferFullHinting variableAxes: Appearance.font.variableAxes.main } renderType: Text.NativeRendering selectedTextColor: Appearance.colors.colOnSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer background: Rectangle { id: background color: Appearance.colors.colLayer1 radius: Appearance.rounding.full } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/VerticalButtonGroup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts /** * A container that supports GroupButton children for bounciness. * See https://m3.material.io/components/button-groups/overview */ Rectangle { id: root default property alias content: columnLayout.data property real spacing: 5 property real padding: 0 property int clickIndex: columnLayout.clickIndex property real contentHeight: { let total = 0; for (let i = 0; i < columnLayout.children.length; ++i) { const child = columnLayout.children[i]; total += child.baseHeight ?? child.implicitHeight ?? child.height; } return total + columnLayout.spacing * (columnLayout.children.length - 1); } topLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[0].radius + padding) : Appearance?.rounding?.small topRightRadius: topLeftRadius bottomLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[columnLayout.children.length - 1].radius + padding) : Appearance?.rounding?.small bottomRightRadius: bottomLeftRadius color: "transparent" height: root.contentHeight + padding * 2 implicitWidth: columnLayout.implicitWidth + padding * 2 implicitHeight: root.contentHeight + padding * 2 children: [ColumnLayout { id: columnLayout anchors.fill: parent anchors.margins: root.padding spacing: root.spacing property int clickIndex: -1 }] } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/VibrantToolbarButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.functions ToolbarButton { colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) colBackgroundHover: Appearance.colors.colPrimaryContainerHover colRipple: Appearance.colors.colPrimaryContainerActive } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WaveVisualizer.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Effects Canvas { // Visualizer id: root property list points property list smoothPoints property real maxVisualizerValue: 1000 property int smoothing: 2 property bool live: true property color color: Appearance.m3colors.m3primary onPointsChanged: () => { root.requestPaint() } anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); var points = root.points; var maxVal = root.maxVisualizerValue || 1; var h = height; var w = width; var n = points.length; if (n < 2) return; // Smoothing: simple moving average (optional) var smoothWindow = root.smoothing; // adjust for more/less smoothing root.smoothPoints = []; for (var i = 0; i < n; ++i) { var sum = 0, count = 0; for (var j = -smoothWindow; j <= smoothWindow; ++j) { var idx = Math.max(0, Math.min(n - 1, i + j)); sum += points[idx]; count++; } root.smoothPoints.push(sum / count); } if (!root.live) root.smoothPoints.fill(0); // If not playing, show no points ctx.beginPath(); ctx.moveTo(0, h); for (var i = 0; i < n; ++i) { var x = i * w / (n - 1); var y = h - (root.smoothPoints[i] / maxVal) * h; ctx.lineTo(x, y); } ctx.lineTo(w, h); ctx.closePath(); ctx.fillStyle = Qt.rgba( root.color.r, root.color.g, root.color.b, 0.15 ); ctx.fill(); } layer.enabled: true layer.effect: MultiEffect { // Blur a bit to obscure away the points source: root saturation: 0.2 blurEnabled: true blurMax: 7 blur: 1 } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WavyLine.qml ================================================ import qs.modules.common import QtQuick Canvas { id: root property real amplitudeMultiplier: 0.5 property real frequency: 6 property color color: Appearance?.colors.colPrimary ?? "#685496" property real lineWidth: 4 property real fullLength: width onPaint: { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); var amplitude = root.lineWidth * root.amplitudeMultiplier; var frequency = root.frequency; var phase = Date.now() / 400.0; var centerY = height / 2; ctx.strokeStyle = root.color; ctx.lineWidth = root.lineWidth; ctx.lineCap = "round"; ctx.beginPath(); for (var x = ctx.lineWidth / 2; x <= root.width - ctx.lineWidth / 2; x += 1) { var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / root.fullLength + phase); if (x === 0) ctx.moveTo(x, waveY); else ctx.lineTo(x, waveY); } ctx.stroke(); } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WeekRow.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common.functions RowLayout { id: root // Pls supply required property date date // Any date within the week property var locale // Expose model and delegate for flexibility property list model: { // Should expose props like here: https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop // (except weekNumber because i'm lazy and it's not so important) const firstDayOfWeek = DateUtils.getFirstDayOfWeek(root.date, root.locale.firstDayOfWeek); const weekDates = []; for (let i = 0; i < 7; i++) { const dayDate = new Date(firstDayOfWeek); dayDate.setDate(firstDayOfWeek.getDate() + i); weekDates.push({ date: dayDate, day: dayDate.getDate(), month: dayDate.getMonth() + 1, year: dayDate.getFullYear(), today: DateUtils.sameDate(dayDate, DateTime.clock.date) }); } return weekDates; } property Component delegate: Text { required property var model text: model.day } // Obvious Repeater { model: root.model delegate: root.delegate } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets Rectangle { id: root property bool show: false default property alias contentData: contentColumn.data property real backgroundHeight: dialogBackground.implicitHeight property real backgroundWidth: 350 property real backgroundAnimationMovementDistance: 60 signal dismiss() Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { root.dismiss(); event.accepted = true; } } color: root.show ? Appearance.colors.colScrim : ColorUtils.transparentize(Appearance.colors.colScrim) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } visible: dialogBackground.implicitHeight > 0 onShowChanged: { dialogBackgroundHeightAnimation.easing.bezierCurve = (show ? Appearance.animationCurves.emphasizedDecel : Appearance.animationCurves.emphasizedAccel) dialogBackground.implicitHeight = show ? backgroundHeight : 0 } radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 MouseArea { // Clicking outside the dialog should dismiss anchors.fill: parent acceptedButtons: Qt.AllButtons hoverEnabled: true onPressed: root.dismiss() } Rectangle { id: dialogBackground anchors.horizontalCenter: parent.horizontalCenter radius: Appearance.rounding.large color: Appearance.m3colors.m3surfaceContainerHigh // Use opaque version of layer3 property real targetY: root.height / 2 - root.backgroundHeight / 2 y: root.show ? targetY : (targetY - root.backgroundAnimationMovementDistance) implicitWidth: root.backgroundWidth implicitHeight: contentColumn.implicitHeight + dialogBackground.radius * 2 Behavior on implicitHeight { NumberAnimation { id: dialogBackgroundHeightAnimation duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.emphasizedDecel } } Behavior on y { NumberAnimation { duration: dialogBackgroundHeightAnimation.duration easing.type: dialogBackgroundHeightAnimation.easing.type easing.bezierCurve: dialogBackgroundHeightAnimation.easing.bezierCurve } } MouseArea { // So clicking inside the dialog won't dismiss anchors.fill: parent acceptedButtons: Qt.AllButtons hoverEnabled: true } ColumnLayout { id: contentColumn anchors { fill: parent margins: dialogBackground.radius } spacing: 16 opacity: root.show ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogButtonRow.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets RowLayout { id: root spacing: 4 // These shouldn't be needed but it would be a terrible waste of space to follow the spec Layout.margins: -8 Layout.topMargin: 0 } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogParagraph.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets StyledText { text: "Some body content" color: Appearance.colors.colOnSurfaceVariant font.pixelSize: Appearance.font.pixelSize.small wrapMode: Text.Wrap } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSectionHeader.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets StyledText { text: "Section" font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.large variableAxes: Appearance.font.variableAxes.title } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSeparator.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets Rectangle { implicitHeight: 1 color: Appearance.colors.colOutline Layout.fillWidth: true Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large Layout.topMargin: -8 Layout.bottomMargin: -8 } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Widgets Column { id: root property alias text: sliderName.text property alias from: sliderWidget.from property alias to: sliderWidget.to property alias value: sliderWidget.value property alias tooltipContent: sliderWidget.tooltipContent property alias stopIndicatorValues: sliderWidget.stopIndicatorValues signal moved() spacing: -2 ContentSubsectionLabel { id: sliderName visible: text?.length > 0 text: "" anchors { left: parent.left right: parent.right } } StyledSlider { id: sliderWidget anchors { left: parent.left right: parent.right leftMargin: 4 rightMargin: 4 } configuration: StyledSlider.Configuration.S onMoved: root.moved() } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/WindowDialogTitle.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets StyledText { text: "Dialog Title" color: Appearance.colors.colOnSurface wrapMode: Text.Wrap font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.title variableAxes: Appearance.font.variableAxes.title } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractOverlayWidget.qml ================================================ import QtQuick import Quickshell import qs.modules.common /* * Abstract widgets for an overlay. Doesn't contain any visuals. */ AbstractWidget { id: root property bool pinned: false // Whether to stay visible when the overlay is dismissed property bool clickthrough: true // When pinned, whether to allow clicks go through } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractWidget.qml ================================================ import QtQuick import Quickshell import qs.modules.common /* * Widget to be placed on a WidgetCanvas */ MouseArea { id: root property alias animateXPos: xBehavior.enabled property alias animateYPos: yBehavior.enabled property bool draggable: true drag.target: draggable ? root : undefined cursorShape: (draggable && containsPress) ? Qt.ClosedHandCursor : draggable ? Qt.OpenHandCursor : Qt.ArrowCursor function center() { root.x = (root.parent.width - root.width) / 2 root.y = (root.parent.height - root.height) / 2 } Behavior on x { id: xBehavior animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on y { id: yBehavior animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } ================================================ FILE: dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/WidgetCanvas.qml ================================================ import QtQuick MouseArea { id: root // uh this is stupid turns out we don't need anything here } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/Background.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas import qs.modules.common.functions as CF import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs.modules.ii.background.widgets import qs.modules.ii.background.widgets.clock import qs.modules.ii.background.widgets.weather Variants { id: root model: Quickshell.screens PanelWindow { id: bgRoot required property var modelData // Hide when fullscreen property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name) property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen // Workspaces property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) property list relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id) property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1 property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10 property int workspaceChunkSize: Config?.options.bar.workspaces.shown ?? 10 property int totalWorkspaces: Math.ceil(lastWorkspaceId / workspaceChunkSize) * workspaceChunkSize // Wallpaper property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath property bool wallpaperSafetyTriggered: { const enabled = Config.options.workSafety.enable.wallpaper; const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords)); const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } readonly property real parallaxRation: Config.options.background.parallax.workspaceZoom property real minSuitableScale: 1 // Some reasonable init, to be updated property real effectiveWallpaperScale: minSuitableScale * parallaxRation property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated property real scaledWallpaperWidth: wallpaperWidth * effectiveWallpaperScale property real scaledWallpaperHeight: wallpaperHeight * effectiveWallpaperScale property real parallaxTotalPixelsX: Math.max(0, scaledWallpaperWidth - screen.width) property real parallaxTotalPixelsY: Math.max(0, scaledWallpaperHeight - screen.height) readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical // Colors property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable) property color dominantColor: Appearance.colors.colPrimary // Default, to be changed property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colText: { if (wallpaperSafetyTriggered) return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75); return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)); } Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } // Layer props screen: modelData exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: (GlobalStates.screenLocked && !scaleAnim.running) ? WlrLayer.Overlay : WlrLayer.Bottom // WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.namespace: "quickshell:background" anchors { top: true bottom: true left: true right: true } color: { if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo) return "transparent"; return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75); } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onWallpaperPathChanged: { bgRoot.updateZoomScale(); // Clock position gets updated after zoom scale is updated } // Wallpaper zoom scale function updateZoomScale() { getWallpaperSizeProc.path = bgRoot.wallpaperPath; getWallpaperSizeProc.running = true; } Process { id: getWallpaperSizeProc property string path: bgRoot.wallpaperPath command: ["magick", "identify", "-format", "%w %h", path] stdout: StdioCollector { id: wallpaperSizeOutputCollector onStreamFinished: { const output = wallpaperSizeOutputCollector.text; const [width, height] = output.split(" ").map(Number); const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height]; bgRoot.wallpaperWidth = width; bgRoot.wallpaperHeight = height; // Perfect image; scale = 1 // Small picture; scale > 1; will zoom in the picture // Big picture; scale < 1; will zoom out the picture // Choose max number so every side will fit bgRoot.minSuitableScale = Math.max(screenWidth / width, screenHeight / height); } } } Item { anchors.fill: parent // Wallpaper StyledImage { id: wallpaper visible: opacity > 0 && !blurLoader.active opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 cache: false smooth: false property int workspaceIndex: (bgRoot.monitor.activeWorkspace?.id ?? 1) - 1 property real middleFraction: 0.5 property real fraction: { // 0 - start of the picture // 1 - end of the picture if (bgRoot.totalWorkspaces <= 1) { return middleFraction; } return Math.max(0, Math.min(1, workspaceIndex / (bgRoot.totalWorkspaces - 1))); } property real usedFractionX: { let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { usedFraction = fraction; } if (Config.options.background.parallax.enableSidebar) { let sidebarFraction = bgRoot.parallaxRation / bgRoot.workspaceChunkSize / 2; usedFraction += (sidebarFraction * GlobalStates.sidebarRightOpen - sidebarFraction * GlobalStates.sidebarLeftOpen); } return Math.max(0, Math.min(1, usedFraction)); } property real usedFractionY: { let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { usedFraction = fraction; } return Math.max(0, Math.min(1, usedFraction)); } x: { if (bgRoot.screen.width > width) { // Center the picture return (bgRoot.screen.width - width) / 2; } return - bgRoot.parallaxTotalPixelsX * usedFractionX; } y: { if (bgRoot.screen.height > height) { // Center the picture return (bgRoot.screen.height - height) / 2; } return - bgRoot.parallaxTotalPixelsY * usedFractionY; } source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath fillMode: Image.PreserveAspectCrop Behavior on x { NumberAnimation { duration: 600 easing.type: Easing.OutCubic } } Behavior on y { NumberAnimation { duration: 600 easing.type: Easing.OutCubic } } width: bgRoot.scaledWallpaperWidth height: bgRoot.scaledWallpaperHeight } Loader { id: blurLoader active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running) anchors.fill: wallpaper scale: GlobalStates.screenLocked ? Config.options.lock.blur.extraZoom : 1 Behavior on scale { NumberAnimation { id: scaleAnim duration: 400 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial } } sourceComponent: GaussianBlur { source: wallpaper radius: GlobalStates.screenLocked ? Config.options.lock.blur.radius : 0 samples: radius * 2 + 1 Rectangle { opacity: GlobalStates.screenLocked ? 1 : 0 anchors.fill: parent color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7) } } } WidgetCanvas { id: widgetCanvas width: parent.width height: parent.height readonly property real parallaxFactor: { var f = Config.options.background.parallax.widgetsFactor; return f / bgRoot.parallaxRation; } readonly property real baseWallpaperOffsetX: (bgRoot.screen.width - wallpaper.width) / 2 readonly property real baseWallpaperOffsetY: (bgRoot.screen.height - wallpaper.height) / 2 readonly property real wallpaperTotalOffsetX: wallpaper.x - baseWallpaperOffsetX readonly property real wallpaperTotalOffsetY: wallpaper.y - baseWallpaperOffsetY readonly property bool locked: GlobalStates.screenLocked x: wallpaperTotalOffsetX * parallaxFactor * !locked y: wallpaperTotalOffsetY * parallaxFactor * !locked transitions: Transition { PropertyAnimation { properties: "width,height" duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } AnchorAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } FadeLoader { shown: Config.options.background.widgets.weather.enable sourceComponent: WeatherWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height scaledScreenWidth: bgRoot.screen.width scaledScreenHeight: bgRoot.screen.height wallpaperScale: 1 } } FadeLoader { shown: Config.options.background.widgets.clock.enable sourceComponent: ClockWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height scaledScreenWidth: bgRoot.screen.width scaledScreenHeight: bgRoot.screen.height wallpaperScale: 1 wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/AbstractBackgroundWidget.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import qs import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets.widgetCanvas AbstractWidget { id: root required property string configEntryName required property int screenWidth required property int screenHeight required property int scaledScreenWidth required property int scaledScreenHeight required property real wallpaperScale property bool visibleWhenLocked: false property var configEntry: Config.options.background.widgets[configEntryName] property string placementStrategy: configEntry.placementStrategy property real targetX: Math.max(0, Math.min(configEntry.x, scaledScreenWidth - width)) property real targetY : Math.max(0, Math.min(configEntry.y, scaledScreenHeight - height)) x: targetX y: targetY visible: opacity > 0 opacity: (GlobalStates.screenLocked && !visibleWhenLocked) ? 0 : 1 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } scale: (draggable && containsPress) ? 1.05 : 1 Behavior on scale { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } draggable: placementStrategy === "free" onReleased: { root.targetX = root.x; root.targetY = root.y; configEntry.x = root.targetX; configEntry.y = root.targetY; } property bool needsColText: false property color dominantColor: Appearance.colors.colPrimary property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property color colText: { const onNormalBackground = (GlobalStates.screenLocked && Config.options.lock.blur.enable) const adaptiveColor = ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) return onNormalBackground ? Appearance.colors.colOnLayer0 : adaptiveColor; } property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath onWallpaperPathChanged: refreshPlacementIfNeeded() onPlacementStrategyChanged: refreshPlacementIfNeeded() Connections { target: Config function onReadyChanged() { refreshPlacementIfNeeded() } } function refreshPlacementIfNeeded() { if (!Config.ready) return; if (root.placementStrategy === "free" && !root.needsColText) return; leastBusyRegionProc.wallpaperPath = root.wallpaperPath; leastBusyRegionProc.running = false; leastBusyRegionProc.running = true; } Process { id: leastBusyRegionProc property string wallpaperPath: root.wallpaperPath // TODO: make these less arbitrary property int contentWidth: 300 property int contentHeight: 300 property int horizontalPadding: 200 property int verticalPadding: 200 command: [Quickshell.shellPath("scripts/images/least-busy-region-venv.sh") // Comments to force the formatter to break lines , "--screen-width", Math.round(root.scaledScreenWidth) // , "--screen-height", Math.round(root.scaledScreenHeight) // , "--width", contentWidth // , "--height", contentHeight // , "--horizontal-padding", horizontalPadding // , "--vertical-padding", verticalPadding // , wallpaperPath // , ...(root.placementStrategy === "mostBusy" ? ["--busiest"] : []) // "--visual-output", ] stdout: StdioCollector { id: leastBusyRegionOutputCollector onStreamFinished: { const output = leastBusyRegionOutputCollector.text; // console.log("[Background] Least busy region output:", output) if (output.length === 0) return; const parsedContent = JSON.parse(output); root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary; if (root.placementStrategy === "free") return; root.targetX = parsedContent.center_x * root.wallpaperScale - root.width / 2; root.targetY = parsedContent.center_y * root.wallpaperScale - root.height / 2; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/ClockText.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts StyledText { Layout.fillWidth: true font { family: Appearance.font.family.expressive pixelSize: 20 weight: 350 // Set empty to prevent conflicts, not meaningless styleName: "" variableAxes: ({}) } style: Text.Raised styleColor: Appearance.colors.colShadow animateChange: Config.options.background.widgets.clock.digital.animateChange } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/ClockWidget.qml ================================================ import QtQuick import QtQuick.Layouts import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas import qs.modules.ii.background.widgets AbstractBackgroundWidget { id: root configEntryName: "clock" implicitHeight: contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth readonly property string clockStyle: GlobalStates.screenLocked ? Config.options.background.widgets.clock.styleLocked : Config.options.background.widgets.clock.style readonly property bool forceCenter: (GlobalStates.screenLocked && Config.options.lock.centerClock) readonly property bool shouldShow: (!Config.options.background.widgets.clock.showOnlyWhenLocked || GlobalStates.screenLocked) property bool wallpaperSafetyTriggered: false needsColText: clockStyle === "digital" x: forceCenter ? ((root.screenWidth - root.width) / 2) : targetX y: forceCenter ? ((root.screenHeight - root.height) / 2) : targetY visibleWhenLocked: true property var textHorizontalAlignment: { if (!Config.options.background.widgets.clock.digital.adaptiveAlignment || root.forceCenter || Config.options.background.widgets.clock.digital.vertical) return Text.AlignHCenter; if (root.x < root.scaledScreenWidth / 3) return Text.AlignLeft; if (root.x > root.scaledScreenWidth * 2 / 3) return Text.AlignRight; return Text.AlignHCenter; } Column { id: contentColumn anchors.centerIn: parent spacing: 10 FadeLoader { id: cookieClockLoader anchors.horizontalCenter: parent.horizontalCenter shown: root.clockStyle === "cookie" && (root.shouldShow) fade: false sourceComponent: Column { spacing: 10 CookieClock { anchors.horizontalCenter: parent.horizontalCenter } FadeLoader { anchors.horizontalCenter: parent.horizontalCenter shown: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text !== "" sourceComponent: CookieQuote {} } } } FadeLoader { id: digitalClockLoader anchors.horizontalCenter: parent.horizontalCenter shown: root.clockStyle === "digital" && (root.shouldShow) fade: false sourceComponent: DigitalClock { colText: root.colText textHorizontalAlignment: root.textHorizontalAlignment } } StatusRow { anchors.horizontalCenter: parent.horizontalCenter } } component StatusRow: Item { id: statusText implicitHeight: statusTextBg.implicitHeight implicitWidth: statusTextBg.implicitWidth StyledRectangularShadow { target: statusTextBg visible: statusTextBg.visible && root.clockStyle === "cookie" opacity: statusTextBg.opacity } Rectangle { id: statusTextBg anchors.centerIn: parent clip: true opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0 visible: opacity > 0 implicitHeight: statusTextRow.implicitHeight + 5 * 2 implicitWidth: statusTextRow.implicitWidth + 5 * 2 radius: Appearance.rounding.small color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 0 : 1) Behavior on implicitWidth { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } Behavior on implicitHeight { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } RowLayout { id: statusTextRow anchors.centerIn: parent spacing: 14 Item { Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignLeft implicitWidth: 1 } ClockStatusText { id: safetyStatusText shown: root.wallpaperSafetyTriggered statusIcon: "hide_image" statusText: Translation.tr("Wallpaper safety enforced") } ClockStatusText { id: lockStatusText shown: GlobalStates.screenLocked && Config.options.lock.showLockedText statusIcon: "lock" statusText: Translation.tr("Locked") } Item { Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignRight implicitWidth: 1 } } } } component ClockStatusText: Row { id: statusTextRow property alias statusIcon: statusIconWidget.text property alias statusText: statusTextWidget.text property bool shown: true property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : root.colText opacity: shown ? 1 : 0 visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } spacing: 4 MaterialSymbol { id: statusIconWidget anchors.verticalCenter: statusTextRow.verticalCenter iconSize: Appearance.font.pixelSize.huge color: statusTextRow.textColor style: Text.Raised styleColor: Appearance.colors.colShadow } ClockText { id: statusTextWidget color: statusTextRow.textColor horizontalAlignment: root.textHorizontalAlignment anchors.verticalCenter: statusTextRow.verticalCenter font { pixelSize: Appearance.font.pixelSize.large weight: Font.Normal } style: Text.Raised styleColor: Appearance.colors.colShadow } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/CookieClock.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Io import qs.modules.ii.background.widgets.clock.dateIndicator import qs.modules.ii.background.widgets.clock.minuteMarks Item { id: root readonly property string clockStyle: Config.options.background.widgets.clock.style property real implicitSize: 230 property color colShadow: Appearance.colors.colShadow property color colBackground: Appearance.colors.colPrimaryContainer property color colOnBackground: ColorUtils.mix(Appearance.colors.colSecondary, Appearance.colors.colPrimaryContainer, 0.15) property color colBackgroundInfo: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colPrimaryContainer, 0.55) property color colHourHand: Appearance.colors.colPrimary property color colMinuteHand: Appearance.colors.colTertiary property color colSecondHand: Appearance.colors.colPrimary readonly property list clockNumbers: DateTime.time.split(/[: ]/) readonly property int clockHour: parseInt(clockNumbers[0]) % 12 readonly property int clockMinute: DateTime.clock.minutes readonly property int clockSecond: DateTime.clock.seconds implicitWidth: implicitSize implicitHeight: implicitSize function applyStyle(sides, dialStyle, hourHandStyle, minuteHandStyle, secondHandStyle, dateStyle) { Config.options.background.widgets.clock.cookie.sides = sides Config.options.background.widgets.clock.cookie.dialNumberStyle = dialStyle Config.options.background.widgets.clock.cookie.hourHandStyle = hourHandStyle Config.options.background.widgets.clock.cookie.minuteHandStyle = minuteHandStyle Config.options.background.widgets.clock.cookie.secondHandStyle = secondHandStyle Config.options.background.widgets.clock.cookie.dateStyle = dateStyle } function setClockPreset(category) { if (!Config.options.background.widgets.clock.cookie.aiStyling) return; if (category === "") return; print("[Cookie clock] Setting clock preset for category: " + category) // "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" if (category == "abstract") { applyStyle(9, "none", "fill", "medium", "dot", "bubble") } else if (category == "anime") { applyStyle(7, "none", "fill", "bold", "dot", "bubble") } else if (category == "city" || category == "space") { applyStyle(23, "full", "hollow", "thin", "classic", "bubble") } else if (category == "minimalist") { applyStyle(6, "none", "fill", "bold", "dot", "hide") } else if (category == "landscape") { applyStyle(14, "full", "hollow", "medium", "classic", "bubble") } else if (category == "plants") { applyStyle(9, "dots", "fill", "bold", "dot", "border") } else if (category == "person") { applyStyle(14, "full", "classic", "classic", "classic", "rect") } } FileView { id: categoryFileView path: Config.ready ? Directories.generatedWallpaperCategoryPath : "" watchChanges: true onFileChanged: reload() onLoaded: { root.setClockPreset(categoryFileView.text().trim()) } } property bool useSineCookie: Config.options.background.widgets.clock.cookie.useSineCookie StyledDropShadow { target: root.useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader RotationAnimation on rotation { running: Config.options.background.widgets.clock.cookie.constantlyRotate duration: 30000 easing.type: Easing.Linear loops: Animation.Infinite from: 360 to: 0 } } Loader { id: sineCookieLoader z: 0 visible: false // The DropShadow already draws it active: root.useSineCookie sourceComponent: SineCookie { implicitSize: root.implicitSize sides: Config.options.background.widgets.clock.cookie.sides color: root.colBackground } } Loader { id: roundedPolygonCookieLoader z: 0 visible: false // The DropShadow already draws it active: !root.useSineCookie sourceComponent: MaterialCookie { implicitSize: root.implicitSize sides: Config.options.background.widgets.clock.cookie.sides color: root.colBackground } } // Hour/minutes numbers/dots/lines MinuteMarks { anchors.fill: parent color: root.colOnBackground } // Stupid extra hour marks in the middle FadeLoader { id: hourMarksLoader anchors.centerIn: parent shown: Config.options.background.widgets.clock.cookie.hourMarks sourceComponent: HourMarks { implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity) color: root.colOnBackground colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5) } } // Number column in the middle FadeLoader { id: timeColumnLoader anchors.centerIn: parent shown: Config.options.background.widgets.clock.cookie.timeIndicators scale: 1.4 - 0.4 * timeColumnLoader.shown Behavior on scale { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } sourceComponent: TimeColumn { color: root.colBackgroundInfo } } // Minute hand FadeLoader { anchors.fill: parent z: 1 shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "hide" sourceComponent: MinuteHand { anchors.fill: parent clockMinute: root.clockMinute style: Config.options.background.widgets.clock.cookie.minuteHandStyle color: root.colMinuteHand } } // Hour hand FadeLoader { anchors.fill: parent z: item?.style === "hollow" ? 0 : 2 shown: Config.options.background.widgets.clock.cookie.hourHandStyle !== "hide" sourceComponent: HourHand { clockHour: root.clockHour clockMinute: root.clockMinute style: Config.options.background.widgets.clock.cookie.hourHandStyle color: root.colHourHand } } // Second hand FadeLoader { id: secondHandLoader z: (Config.options.background.widgets.clock.cookie.secondHandStyle === "line") ? 2 : 3 shown: Config.options.time.secondPrecision && Config.options.background.widgets.clock.cookie.secondHandStyle !== "hide" anchors.fill: parent sourceComponent: SecondHand { id: secondHand clockSecond: root.clockSecond style: Config.options.background.widgets.clock.cookie.secondHandStyle color: root.colSecondHand } } // Center dot FadeLoader { z: 4 anchors.centerIn: parent shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== "bold" sourceComponent: Rectangle { color: Config.options.background.widgets.clock.cookie.minuteHandStyle === "medium" ? root.colBackground : root.colMinuteHand implicitWidth: 6 implicitHeight: implicitWidth radius: width / 2 } } // Date FadeLoader { anchors.fill: parent shown: Config.options.background.widgets.clock.cookie.dateStyle !== "hide" sourceComponent: DateIndicator { color: root.colBackgroundInfo style: Config.options.background.widgets.clock.cookie.dateStyle } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/CookieQuote.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import Qt5Compat.GraphicalEffects Item { id: root readonly property string quoteText: Config.options.background.widgets.clock.quote.text implicitWidth: quoteBox.implicitWidth implicitHeight: quoteBox.implicitHeight DropShadow { source: quoteBox anchors.fill: quoteBox horizontalOffset: 0 verticalOffset: 2 radius: 12 samples: radius * 2 + 1 color: Appearance.colors.colShadow transparentBorder: true } Rectangle { id: quoteBox implicitWidth: quoteRow.implicitWidth + 8 * 2 implicitHeight: quoteRow.implicitHeight + 4 * 2 radius: Appearance.rounding.small color: Appearance.colors.colSecondaryContainer Row { id: quoteRow anchors.centerIn: parent spacing: 4 MaterialSymbol { id: quoteIcon anchors.top: parent.top iconSize: Appearance.font.pixelSize.huge text: "format_quote" color: Appearance.colors.colOnSecondaryContainer } StyledText { id: quoteStyledText horizontalAlignment: Text.AlignLeft text: Config.options.background.widgets.clock.quote.text color: Appearance.colors.colOnSecondaryContainer font { family: Appearance.font.family.reading pixelSize: Appearance.font.pixelSize.large weight: Font.Normal } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/DigitalClock.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import QtQuick import QtQuick.Layouts ColumnLayout { id: clockColumn spacing: 4 property bool isVertical: Config.options.background.widgets.clock.digital.vertical property color colText: Appearance.colors.colOnSecondaryContainer property var textHorizontalAlignment: Text.AlignHCenter // Time ClockText { id: timeTextTop text: clockColumn.isVertical ? DateTime.time.split(":")[0].padStart(2, "0") : DateTime.time color: clockColumn.colText horizontalAlignment: Text.AlignHCenter font { pixelSize: Config.options.background.widgets.clock.digital.font.size weight: Config.options.background.widgets.clock.digital.font.weight family: Config.options.background.widgets.clock.digital.font.family variableAxes: ({ "wdth": Config.options.background.widgets.clock.digital.font.width, "ROND": Config.options.background.widgets.clock.digital.font.roundness }) } } Loader { Layout.topMargin: -40 Layout.fillWidth: true active: clockColumn.isVertical visible: active sourceComponent: ClockText { id: timeTextBottom text: DateTime.time.split(":")[1].split(" ")[0].padStart(2, "0") color: clockColumn.colText horizontalAlignment: clockColumn.textHorizontalAlignment font { pixelSize: timeTextTop.font.pixelSize weight: timeTextTop.font.weight family: timeTextTop.font.family variableAxes: timeTextTop.font.variableAxes } } } // Date ClockText { visible: Config.options.background.widgets.clock.digital.showDate Layout.topMargin: -20 Layout.fillWidth: true text: DateTime.longDate color: clockColumn.colText horizontalAlignment: clockColumn.textHorizontalAlignment } // Quote ClockText { visible: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0 font.pixelSize: Appearance.font.pixelSize.normal text: Config.options.background.widgets.clock.quote.text animateChange: false color: clockColumn.colText horizontalAlignment: clockColumn.textHorizontalAlignment } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/HourHand.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import QtQuick Item { id: root required property int clockHour required property int clockMinute property real handLength: 72 property real handWidth: 20 property string style: "fill" property color color: Appearance.colors.colPrimary property real fillColorAlpha: root.style === "hollow" ? 0 : 1 Behavior on fillColorAlpha { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60) Behavior on rotation { animation: RotationAnimation { direction: RotationAnimation.Clockwise duration: 300 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.emphasized } } Rectangle { anchors.verticalCenter: parent.verticalCenter x: (parent.width - root.handWidth) / 2 - 15 * (root.style === "classic") width: root.handLength height: root.style === "classic" ? 8 : root.handWidth radius: root.style === "classic" ? 2 : root.handWidth / 2 color : Qt.rgba(root.color.r, root.color.g, root.color.b, root.fillColorAlpha) border.color: root.color border.width: 4 Behavior on x { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/HourMarks.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick Item { id: root property real implicitSize: 135 property real markLength: 12 property real markWidth: 4 property color color: Appearance.colors.colOnSecondaryContainer property color colOnBackground: Appearance.colors.colSecondaryContainer property real padding: 8 Rectangle { color: root.color anchors.centerIn: parent implicitWidth: root.implicitSize implicitHeight: root.implicitSize radius: width / 2 // Hour mark lines Repeater { model: 12 Item { required property int index anchors.fill: parent rotation: 360 / 12 * index Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: root.padding } implicitWidth: root.markLength implicitHeight: root.markWidth radius: width / 2 color: root.colOnBackground } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/MinuteHand.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import QtQuick Item { id: root anchors.fill: parent required property int clockMinute property string style: "medium" property real handLength: 95 property real handWidth: style === "bold" ? 20 : style === "medium" ? 12 : 5 property color color: Appearance.colors.colTertiary rotation: -90 + (360 / 60) * root.clockMinute Behavior on rotation { animation: RotationAnimation { direction: RotationAnimation.Clockwise duration: 300 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.emphasized } } Rectangle { anchors.verticalCenter: parent.verticalCenter x: { let position = parent.width / 2 - root.handWidth / 2; if (root.style === "classic") position -= 15; return position; } width: root.handLength height: root.handWidth radius: root.style === "classic" ? 2 : root.handWidth / 2 color: root.color Behavior on height { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } Behavior on x { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/SecondHand.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root anchors.fill: parent required property int clockSecond property real handWidth: 2 property real handLength: 95 property real dotSize: 20 property string style: "hide" property color color: Appearance.colors.colSecondary rotation: (360 / 60 * clockSecond) + 90 Behavior on rotation { enabled: Config.options.background.widgets.clock.cookie.constantlyRotate // Animating every second is expensive... animation: RotationAnimation { direction: RotationAnimation.Clockwise duration: 1000 // 1 second easing.type: Easing.InOutQuad } } Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: 10 + (root.style === "dot" ? root.dotSize : 0) } implicitWidth: root.style === "dot" ? root.dotSize : root.handLength implicitHeight: root.style === "dot" ? root.dotSize : root.handWidth radius: Math.min(width, height) / 2 color: root.color Behavior on implicitHeight { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } Behavior on implicitWidth { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } } // Classic style dot in the middle of the hand FadeLoader { id: classicDotLoader anchors { left: parent.left verticalCenter: parent.verticalCenter } shown: root.style === "classic" Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: 40 } implicitWidth: root.style === "classic" ? 14 : 0 implicitHeight: implicitWidth color: root.color radius: Appearance.rounding.small Behavior on implicitWidth { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/TimeColumn.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick Column { id: root property list clockNumbers: DateTime.time.split(/[: ]/) property bool isEnabled: Config.options.background.widgets.clock.cookie.timeIndicators property color color: Appearance.colors.colOnSecondaryContainer property bool hourMarksEnabled: Config.options.background.widgets.clock.cookie.hourMarks spacing: -16 Repeater { model: root.clockNumbers delegate: StyledText { required property string modelData text: modelData.padStart(2, "0") property bool isAmPm: !text.match(/\d{2}/i) property real numberSizeWithoutGlow: isAmPm ? 26 : 68 property real numberSizeWithGlow: isAmPm ? 20 : 40 property real numberSize: root.hourMarksEnabled ? numberSizeWithGlow : numberSizeWithoutGlow anchors.horizontalCenter: root.horizontalCenter color: root.color font { family: Appearance.font.family.expressive weight: Font.Bold pixelSize: numberSize } Behavior on numberSize { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/BubbleDate.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property bool isMonth: false property real targetSize: 0 property alias text: bubbleText.text text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? "MM" : "d") MaterialShape { id: bubble z: 5 // sides: root.isMonth ? 1 : 4 shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon anchors.centerIn: parent color: root.isMonth ? Appearance.colors.colSecondaryContainer : Appearance.colors.colTertiaryContainer implicitSize: targetSize } StyledText { id: bubbleText z: 6 anchors.centerIn: parent color: root.isMonth ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnTertiaryContainer font { family: Appearance.font.family.expressive pixelSize: 30 weight: Font.Black } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/DateIndicator.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick Item { id: root property string style: "bubble" property color color: Appearance.colors.colOnSecondaryContainer property real dateSquareSize: 64 // Rotating date FadeLoader { anchors.fill: parent shown: Config.options.background.widgets.clock.cookie.dateStyle === "border" sourceComponent: RotatingDate { color: root.color } } // Rectangle date (only today's number) in right side of the clock FadeLoader { id: rectLoader shown: root.style === "rect" anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 40 - rectLoader.opacity * 30 } sourceComponent: RectangleDate { color: ColorUtils.mix(root.color, Appearance.colors.colSecondaryContainerHover, 0.5) radius: Appearance.rounding.small implicitWidth: 45 * rectLoader.opacity implicitHeight: 30 * rectLoader.opacity } } // Bubble style: day of month FadeLoader { id: dayBubbleLoader shown: root.style === "bubble" property real targetSize: root.dateSquareSize * opacity anchors { left: parent.left top: parent.top } sourceComponent: BubbleDate { implicitWidth: dayBubbleLoader.targetSize implicitHeight: dayBubbleLoader.targetSize isMonth: false targetSize: dayBubbleLoader.targetSize } } // Bubble style: month FadeLoader { id: monthBubbleLoader shown: root.style === "bubble" property real targetSize: root.dateSquareSize * opacity anchors { right: parent.right bottom: parent.bottom } sourceComponent: BubbleDate { implicitWidth: monthBubbleLoader.targetSize implicitHeight: monthBubbleLoader.targetSize isMonth: true targetSize: monthBubbleLoader.targetSize } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/RectangleDate.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick Rectangle { id: rect readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle StyledText { anchors.centerIn: parent color: Appearance.colors.colSecondaryHover text: Qt.locale().toString(DateTime.clock.date, "dd") font { family: Appearance.font.family.expressive pixelSize: 20 weight: 1000 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/RotatingDate.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property string style: Config.options.background.widgets.clock.cookie.dateStyle property color color: Appearance.colors.colOnSecondaryContainer property real angleStep: 12 * Math.PI / 180 property string dateText: Qt.locale().toString(DateTime.clock.date, "ddd dd") readonly property int clockSecond: DateTime.clock.seconds readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle readonly property bool timeIndicators: Config.options.background.widgets.clock.cookie.timeIndicators property real radius: style === "border" ? 90 : 0 Behavior on radius { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } rotation: { if (!Config.options.time.secondPrecision) return 0 else return (360 / 60 * clockSecond) + 180 - (angleStep / Math.PI * 180 * dateText.length) / 2 } Repeater { model: root.dateText.length delegate: Text { required property int index property real angle: index * root.angleStep - Math.PI / 2 x: root.width / 2 + root.radius * Math.cos(angle) - width / 2 y: root.height / 2 + root.radius * Math.sin(angle) - height / 2 rotation: angle * 180 / Math.PI + 90 color: root.color font { family: Appearance.font.family.title pixelSize: 30 variableAxes: Appearance.font.variableAxes.title } text: root.dateText.charAt(index) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/BigHourNumbers.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property real numberSize: 80 property real margins: 10 property color color: Appearance.colors.colOnSecondaryContainer property int hours: 12 property int numbers: 4 property int fontSize: 80 Repeater { model: root.numbers Item { id: numberItem required property int index rotation: 360 / root.numbers * (index + 1) anchors.fill: parent Item { implicitWidth: root.numberSize implicitHeight: implicitWidth anchors { top: parent.top horizontalCenter: parent.horizontalCenter topMargin: root.margins } StyledText { color: root.color anchors.centerIn: parent text: root.hours / root.numbers * (numberItem.index + 1) rotation: -numberItem.rotation font { family: Appearance.font.family.reading pixelSize: root.fontSize weight: Font.Black } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/Dots.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property real implicitSize: 12 property real margins: 10 property color color: Appearance.colors.colOnSecondaryContainer Repeater { model: 12 Item { required property int index anchors.fill: parent // Ensures rotation works properly rotation: 360 / 12 * index Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: root.margins } implicitWidth: root.implicitSize implicitHeight: implicitWidth radius: implicitWidth / 2 color: root.color } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/Lines.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property real numberSize: 80 property real margins: 10 property color color: Appearance.colors.colOnSecondaryContainer property real hourLineSize: 4 property real minuteLineSize: 2 property real hourLineLength: 18 property real minuteLineLength: 7 property int hours: 12 property int minutes: 60 // Full dial style hour lines Repeater { model: root.hours Item { required property int index rotation: 360 / root.hours * index anchors.fill: parent Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: root.margins } implicitWidth: root.hourLineLength implicitHeight: root.hourLineSize radius: implicitWidth / 2 color: root.color } } } // Minute lines Repeater { model: root.minutes Item { required property int index rotation: 360 / root.minutes * index anchors.fill: parent Rectangle { anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: root.margins } implicitWidth: root.minuteLineLength implicitHeight: root.minuteLineSize radius: implicitWidth / 2 color: root.color } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/MinuteMarks.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property color color: Appearance.colors.colOnSecondaryContainer property string style: Config.options.background.widgets.clock.cookie.dialNumberStyle // "dots", "numbers", "full", "hide" property string dateStyle : Config.options.background.widgets.clock.cookie.dateStyle // 12 Dots FadeLoader { id: dotsLoader anchors { fill: parent margins: 10 } shown: root.style === "dots" sourceComponent: Dots { color: root.color margins: 46 - dotsLoader.opacity * 34 } } // 3-6-9-12 hour numbers (pls don't realize you can have more than 4 numbers) FadeLoader { id: bigHourNumbersLoader anchors.fill: parent shown: root.style === "numbers" sourceComponent: BigHourNumbers { numberSize: 80 color: root.color margins: 20 - 10 * bigHourNumbersLoader.opacity } } // Lines FadeLoader { id: linesLoader anchors { fill: parent margins: 10 } shown: root.style === "full" sourceComponent: Lines { color: root.color margins: 46 - linesLoader.opacity * 34 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/background/widgets/weather/WeatherWidget.qml ================================================ import QtQuick import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas import qs.modules.ii.background.widgets AbstractBackgroundWidget { id: root configEntryName: "weather" implicitHeight: backgroundShape.implicitHeight implicitWidth: backgroundShape.implicitWidth StyledDropShadow { target: backgroundShape } MaterialShape { id: backgroundShape anchors.fill: parent shape: MaterialShape.Shape.Pill color: Appearance.colors.colPrimaryContainer implicitSize: 200 StyledText { font { pixelSize: 80 family: Appearance.font.family.expressive weight: Font.Medium } color: Appearance.colors.colPrimary text: Weather.data?.temp.substring(0,Weather.data?.temp.length - 1) ?? "--°" anchors { right: parent.right top: parent.top rightMargin: 16 topMargin: 20 } } MaterialSymbol { iconSize: 80 color: Appearance.colors.colOnPrimaryContainer text: Icons.getWeatherIcon(Weather.data.wCode) ?? "cloud" anchors { left: parent.left bottom: parent.bottom leftMargin: 16 bottomMargin: 20 } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/ActiveWindow.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Item { id: root readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}` property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id) implicitWidth: colLayout.implicitWidth ColumnLayout { id: colLayout anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right spacing: -4 StyledText { Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext elide: Text.ElideRight text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ? root.activeWindow?.appId : (root.biggestWindow?.class) ?? Translation.tr("Desktop") } StyledText { Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer0 elide: Text.ElideRight text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ? root.activeWindow?.title : (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${monitor?.activeWorkspace?.id ?? 1}` } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/Bar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: bar property bool showBarBackground: Config.options.bar.showBackground Variants { // For each monitor model: { const screens = Quickshell.screens; const list = Config.options.bar.screenList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); } LazyLoader { id: barLoader active: GlobalStates.barOpen && !GlobalStates.screenLocked required property ShellScreen modelData component: PanelWindow { // Bar window id: barRoot screen: barLoader.modelData Timer { id: showBarTimer interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) repeat: false onTriggered: { barRoot.superShow = true } } Connections { target: GlobalStates function onSuperDownChanged() { if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; if (GlobalStates.superDown) showBarTimer.restart(); else { showBarTimer.stop(); barRoot.superShow = false; } } } property bool superShow: false property bool mustShow: hoverRegion.containsMouse || superShow exclusionMode: ExclusionMode.Ignore exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 : Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) WlrLayershell.namespace: "quickshell:bar" implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding mask: Region { item: hoverMaskRegion } color: "transparent" // Positioning anchors { top: !Config.options.bar.bottom bottom: Config.options.bar.bottom left: true right: true } margins { right: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * -1 bottom: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1 } // Include in focus grab Component.onCompleted: { GlobalFocusGrab.addPersistent(barRoot); } Component.onDestruction: { GlobalFocusGrab.removePersistent(barRoot); } MouseArea { id: hoverRegion hoverEnabled: true anchors { fill: parent rightMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * 1 bottomMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * 1 } Item { id: hoverMaskRegion anchors { fill: barContent topMargin: -Config.options.bar.autoHide.hoverRegionWidth bottomMargin: -Config.options.bar.autoHide.hoverRegionWidth } } BarContent { id: barContent implicitHeight: Appearance.sizes.barHeight anchors { right: parent.right left: parent.left top: parent.top bottom: undefined topMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0 bottomMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1 rightMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * -1 } Behavior on anchors.topMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } states: State { name: "bottom" when: Config.options.bar.bottom AnchorChanges { target: barContent anchors { right: parent.right left: parent.left top: undefined bottom: parent.bottom } } PropertyChanges { target: barContent anchors.topMargin: 0 anchors.bottomMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0 } } } // Round decorators Loader { id: roundDecorators anchors { left: parent.left right: parent.right top: barContent.bottom bottom: undefined } height: Appearance.rounding.screenRounding active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug states: State { name: "bottom" when: Config.options.bar.bottom AnchorChanges { target: roundDecorators anchors { right: parent.right left: parent.left top: undefined bottom: barContent.top } } } sourceComponent: Item { implicitHeight: Appearance.rounding.screenRounding RoundCorner { id: leftCorner anchors { top: parent.top bottom: parent.bottom left: parent.left } implicitSize: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.TopLeft states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { leftCorner.corner: RoundCorner.CornerEnum.BottomLeft } } } RoundCorner { id: rightCorner anchors { right: parent.right top: !Config.options.bar.bottom ? parent.top : undefined bottom: Config.options.bar.bottom ? parent.bottom : undefined } implicitSize: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.TopRight states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { rightCorner.corner: RoundCorner.CornerEnum.BottomRight } } } } } } } } } IpcHandler { target: "bar" function toggle(): void { GlobalStates.barOpen = !GlobalStates.barOpen } function close(): void { GlobalStates.barOpen = false } function open(): void { GlobalStates.barOpen = true } } GlobalShortcut { name: "barToggle" description: "Toggles bar on press" onPressed: { GlobalStates.barOpen = !GlobalStates.barOpen; } } GlobalShortcut { name: "barOpen" description: "Opens bar on press" onPressed: { GlobalStates.barOpen = true; } } GlobalShortcut { name: "barClose" description: "Closes bar on press" onPressed: { GlobalStates.barOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/BarContent.qml ================================================ import qs.modules.ii.bar.weather import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.UPower import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions Item { // Bar content region id: root property var screen: root.QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen?.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen?.width) ? 1 : 0 readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth component VerticalBarSeparator: Rectangle { Layout.topMargin: Appearance.sizes.baseBarHeight / 3 Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3 Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant } // Background shadow Loader { active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 && Config.options.bar.floatStyleShadow anchors.fill: barBackground sourceComponent: StyledRectangularShadow { anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor target: barBackground } } // Background Rectangle { id: barBackground anchors { fill: parent margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed } color: Config.options.bar.showBackground ? Appearance.colors.colLayer0 : "transparent" radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0 border.color: Appearance.colors.colLayer0Border } FocusedScrollMouseArea { // Left side | scroll to change brightness id: barLeftSideMouseArea anchors { top: parent.top bottom: parent.bottom left: parent.left right: middleSection.left } implicitWidth: leftSectionRowLayout.implicitWidth implicitHeight: Appearance.sizes.baseBarHeight onScrollDown: Brightness.decreaseBrightness() onScrollUp: Brightness.increaseBrightness() onMovedAway: GlobalStates.osdBrightnessOpen = false onPressed: event => { if (event.button === Qt.LeftButton) GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } // Visual content ScrollHint { reveal: barLeftSideMouseArea.hovered icon: Hyprsunset.gamma === 100 ? "light_mode" : "wb_twilight" tooltipText: Translation.tr("Scroll to change brightness") side: "left" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter } RowLayout { id: leftSectionRowLayout anchors.fill: parent spacing: 0 LeftSidebarButton { // Left sidebar button id: leftSidebarButton Layout.alignment: Qt.AlignVCenter Layout.leftMargin: Appearance.rounding.screenRounding colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) } ActiveWindow { Layout.leftMargin: 10 + (leftSidebarButton.visible ? 0 : Appearance.rounding.screenRounding) Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: true Layout.fillHeight: true visible: root.useShortenedForm === 0 } } } Row { // Middle section id: middleSection anchors { top: parent.top bottom: parent.bottom horizontalCenter: parent.horizontalCenter } spacing: 4 BarGroup { id: leftCenterGroup anchors.verticalCenter: parent.verticalCenter implicitWidth: root.centerSideModuleWidth Resources { alwaysShowAllResources: root.useShortenedForm === 2 Layout.fillWidth: root.useShortenedForm === 2 } Media { visible: root.useShortenedForm < 2 Layout.fillWidth: true } } VerticalBarSeparator { visible: Config.options?.bar.borderless } BarGroup { id: middleCenterGroup anchors.verticalCenter: parent.verticalCenter padding: workspacesWidget.widgetPadding Workspaces { id: workspacesWidget Layout.fillHeight: true MouseArea { // Right-click to toggle overview anchors.fill: parent acceptedButtons: Qt.RightButton onPressed: event => { if (event.button === Qt.RightButton) { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } } } } VerticalBarSeparator { visible: Config.options?.bar.borderless } MouseArea { id: rightCenterGroup anchors.verticalCenter: parent.verticalCenter implicitWidth: root.centerSideModuleWidth implicitHeight: rightCenterGroupContent.implicitHeight onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } BarGroup { id: rightCenterGroupContent anchors.fill: parent ClockWidget { showDate: (Config.options.bar.verbose && root.useShortenedForm < 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true } UtilButtons { visible: (Config.options.bar.verbose && root.useShortenedForm === 0) Layout.alignment: Qt.AlignVCenter } BatteryIndicator { visible: (root.useShortenedForm < 2 && Battery.available) Layout.alignment: Qt.AlignVCenter } } } } FocusedScrollMouseArea { // Right side | scroll to change volume id: barRightSideMouseArea anchors { top: parent.top bottom: parent.bottom left: middleSection.right right: parent.right } implicitWidth: rightSectionRowLayout.implicitWidth implicitHeight: Appearance.sizes.baseBarHeight onScrollDown: Audio.decrementVolume(); onScrollUp: Audio.incrementVolume(); onMovedAway: GlobalStates.osdVolumeOpen = false; onPressed: event => { if (event.button === Qt.LeftButton) { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } } // Visual content ScrollHint { reveal: barRightSideMouseArea.hovered icon: "volume_up" tooltipText: Translation.tr("Scroll to change volume") side: "right" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter } RowLayout { id: rightSectionRowLayout anchors.fill: parent spacing: 5 layoutDirection: Qt.RightToLeft RippleButton { // Right sidebar button id: rightSidebarButton Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.rightMargin: Appearance.rounding.screenRounding Layout.fillWidth: false implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2 implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2 buttonRadius: Appearance.rounding.full colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarRightOpen property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0 Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } RowLayout { id: indicatorsRowLayout anchors.centerIn: parent property real realSpacing: 15 spacing: 0 Revealer { reveal: Audio.sink?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } MaterialSymbol { text: "volume_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } Revealer { reveal: Audio.source?.audio?.muted ?? false Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 Behavior on Layout.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } MaterialSymbol { text: "mic_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } HyprlandXkbIndicator { Layout.alignment: Qt.AlignVCenter Layout.rightMargin: indicatorsRowLayout.realSpacing color: rightSidebarButton.colText } Revealer { reveal: Notifications.silent || Notifications.unread > 0 Layout.fillHeight: true Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0 implicitHeight: reveal ? notificationUnreadCount.implicitHeight : 0 implicitWidth: reveal ? notificationUnreadCount.implicitWidth : 0 Behavior on Layout.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } NotificationUnreadCount { id: notificationUnreadCount } } MaterialSymbol { text: Network.materialSymbol iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } MaterialSymbol { Layout.leftMargin: indicatorsRowLayout.realSpacing visible: BluetoothStatus.available text: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } } SysTray { visible: root.useShortenedForm === 0 Layout.fillWidth: false Layout.fillHeight: true invertSide: Config?.options.bar.bottom } Item { Layout.fillWidth: true Layout.fillHeight: true } // Weather Loader { Layout.leftMargin: 4 active: Config.options.bar.weather.enable sourceComponent: BarGroup { WeatherBar {} } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/BarGroup.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Layouts Item { id: root property bool vertical: false property real padding: 5 implicitWidth: vertical ? Appearance.sizes.baseVerticalBarWidth : (gridLayout.implicitWidth + padding * 2) implicitHeight: vertical ? (gridLayout.implicitHeight + padding * 2) : Appearance.sizes.baseBarHeight default property alias items: gridLayout.children Rectangle { id: background anchors { fill: parent topMargin: root.vertical ? 0 : 4 bottomMargin: root.vertical ? 0 : 4 leftMargin: root.vertical ? 4 : 0 rightMargin: root.vertical ? 4 : 0 } color: Config.options?.bar.borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small } GridLayout { id: gridLayout columns: root.vertical ? 1 : -1 anchors { verticalCenter: root.vertical ? undefined : parent.verticalCenter horizontalCenter: root.vertical ? parent.horizontalCenter : undefined left: root.vertical ? undefined : parent.left right: root.vertical ? undefined : parent.right top: root.vertical ? parent.top : undefined bottom: root.vertical ? parent.bottom : undefined margins: root.padding } columnSpacing: 4 rowSpacing: 12 } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts MouseArea { id: root property bool borderless: Config.options.bar.borderless readonly property var chargeState: Battery.chargeState readonly property bool isCharging: Battery.isCharging readonly property bool isPluggedIn: Battery.isPluggedIn readonly property real percentage: Battery.percentage readonly property bool isLow: percentage <= Config.options.battery.low / 100 implicitWidth: batteryProgress.implicitWidth implicitHeight: Appearance.sizes.barHeight hoverEnabled: !Config.options.bar.tooltips.clickToShow ClippedProgressBar { id: batteryProgress anchors.centerIn: parent value: percentage highlightColor: (isLow && !isCharging) ? Appearance.m3colors.m3error : Appearance.colors.colOnSecondaryContainer Item { anchors.centerIn: parent width: batteryProgress.valueBarWidth height: batteryProgress.valueBarHeight RowLayout { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: (parent.height - height) / 2 } spacing: 0 MaterialSymbol { id: boltIcon Layout.alignment: Qt.AlignVCenter Layout.leftMargin: -2 Layout.rightMargin: -2 fill: 1 text: "bolt" iconSize: Appearance.font.pixelSize.smaller visible: isCharging && percentage < 1 // TODO: animation } StyledText { Layout.alignment: Qt.AlignVCenter font: batteryProgress.font text: batteryProgress.text } } } } BatteryPopup { id: batteryPopup hoverTarget: root } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/BatteryPopup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts StyledPopup { id: root ColumnLayout { id: columnLayout anchors.centerIn: parent spacing: 4 // Header StyledPopupHeaderRow { icon: "battery_android_full" label: Translation.tr("Battery") } StyledPopupValueRow { visible: { let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty; let power = Battery.energyRate; return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01); } icon: "schedule" label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") value: { function formatTime(seconds) { var h = Math.floor(seconds / 3600); var m = Math.floor((seconds % 3600) / 60); if (h > 0) return `${h}h, ${m}m`; else return `${m}m`; } if (Battery.isCharging) return formatTime(Battery.timeToFull); else return formatTime(Battery.timeToEmpty); } } StyledPopupValueRow { visible: !(Battery.chargeState != 4 && Battery.energyRate == 0) icon: "bolt" label: { if (Battery.chargeState == 4) { return Translation.tr("Fully charged"); } else if (Battery.chargeState == 1) { return Translation.tr("Charging:"); } else { return Translation.tr("Discharging:"); } } value: { if (Battery.chargeState == 4) { return ""; } else { return `${Battery.energyRate.toFixed(2)}W`; } } } StyledPopupValueRow { icon: "heart_check" label: Translation.tr("Health:") value: `${(Battery.health).toFixed(1)}%` } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/CircleUtilButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick RippleButton { id: button required default property Item content property bool extraActiveCondition: false implicitHeight: Math.max(content.implicitHeight, 26, content.implicitHeight) implicitWidth: implicitHeight contentItem: content } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/ClockWidget.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts Item { id: root property bool borderless: Config.options.bar.borderless property bool showDate: Config.options.bar.verbose implicitWidth: rowLayout.implicitWidth implicitHeight: Appearance.sizes.barHeight RowLayout { id: rowLayout anchors.centerIn: parent spacing: 4 StyledText { font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 text: DateTime.time } StyledText { visible: root.showDate font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: "•" } StyledText { visible: root.showDate font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: DateTime.longDate } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: !Config.options.bar.tooltips.clickToShow ClockWidgetPopup { hoverTarget: mouseArea } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/ClockWidgetPopup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts StyledPopup { id: root property string formattedDate: Qt.locale().toString(DateTime.clock.date, "dddd, MMMM dd, yyyy") property string formattedTime: DateTime.time property string formattedUptime: DateTime.uptime property string todosSection: getUpcomingTodos() function getUpcomingTodos() { const unfinishedTodos = Todo.list.filter(function (item) { return !item.done; }); if (unfinishedTodos.length === 0) { return Translation.tr("No pending tasks"); } // Limit to first 5 todos to keep popup manageable const limitedTodos = unfinishedTodos.slice(0, 5); let todoText = limitedTodos.map(function (item, index) { return ` ${index + 1}. ${item.content}`; }).join('\n'); if (unfinishedTodos.length > 5) { todoText += `\n ${Translation.tr("... and %1 more").arg(unfinishedTodos.length - 5)}`; } return todoText; } ColumnLayout { id: columnLayout anchors.centerIn: parent spacing: 4 StyledPopupHeaderRow { icon: "calendar_month" label: root.formattedDate } StyledPopupValueRow { icon: "timelapse" label: Translation.tr("System uptime:") value: root.formattedUptime } // Tasks Column { spacing: 0 Layout.fillWidth: true StyledPopupValueRow { icon: "checklist" label: Translation.tr("To Do:") value: "" } StyledText { horizontalAlignment: Text.AlignLeft wrapMode: Text.Wrap color: Appearance.colors.colOnSurfaceVariant text: root.todosSection } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/HyprlandXkbIndicator.qml ================================================ import QtQuick import qs.services import qs.modules.common import qs.modules.common.widgets Loader { id: root property bool vertical: false property color color: Appearance.colors.colOnSurfaceVariant active: HyprlandXkb.layoutCodes.length > 1 visible: active function abbreviateLayoutCode(fullCode) { return fullCode.split(':').map(layout => { const baseLayout = layout.split('-')[0]; return baseLayout.slice(0, 4); }).join('\n'); } sourceComponent: Item { implicitWidth: root.vertical ? null : layoutCodeText.implicitWidth implicitHeight: root.vertical ? layoutCodeText.implicitHeight : null StyledText { id: layoutCodeText anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: abbreviateLayoutCode(HyprlandXkb.currentLayoutCode) font.pixelSize: text.includes("\n") ? Appearance.font.pixelSize.smallie : Appearance.font.pixelSize.small color: root.color animateChange: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/LeftSidebarButton.qml ================================================ import QtQuick import qs import qs.services import qs.modules.common import qs.modules.common.widgets RippleButton { id: root property bool showPing: false property bool aiChatEnabled: Config.options.policies.ai !== 0 property bool translatorEnabled: Config.options.sidebar.translator.enable property bool animeEnabled: Config.options.policies.weeb !== 0 visible: aiChatEnabled || translatorEnabled || animeEnabled property real buttonPadding: 5 implicitWidth: distroIcon.width + buttonPadding * 2 implicitHeight: distroIcon.height + buttonPadding * 2 buttonRadius: Appearance.rounding.full colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarLeftOpen onPressed: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } Connections { target: Ai function onResponseFinished() { if (GlobalStates.sidebarLeftOpen) return; root.showPing = true; } } Connections { target: Booru function onResponseFinished() { if (GlobalStates.sidebarLeftOpen) return; root.showPing = true; } } Connections { target: GlobalStates function onSidebarLeftOpenChanged() { root.showPing = false; } } CustomIcon { id: distroIcon anchors.centerIn: parent width: 19.5 height: 19.5 source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic` colorize: true color: Appearance.colors.colOnLayer0 Rectangle { opacity: root.showPing ? 1 : 0 visible: opacity > 0 anchors { bottom: parent.bottom right: parent.right bottomMargin: -2 rightMargin: -2 } implicitWidth: 8 implicitHeight: 8 radius: Appearance.rounding.full color: Appearance.colors.colTertiary Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/Media.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import qs import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell.Services.Mpris import Quickshell.Hyprland Item { id: root property bool borderless: Config.options.bar.borderless readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr("No media") Layout.fillHeight: true implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: Appearance.sizes.barHeight Timer { running: activePlayer?.playbackState == MprisPlaybackState.Playing interval: Config.options.resources.updateInterval repeat: true onTriggered: activePlayer.positionChanged() } MouseArea { anchors.fill: parent acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton onPressed: (event) => { if (event.button === Qt.MiddleButton) { activePlayer.togglePlaying(); } else if (event.button === Qt.BackButton) { activePlayer.previous(); } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) { activePlayer.next(); } else if (event.button === Qt.LeftButton) { GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen } } } RowLayout { // Real content id: rowLayout spacing: 4 anchors.fill: parent ClippedFilledCircularProgress { id: mediaCircProg Layout.alignment: Qt.AlignVCenter lineWidth: Appearance.rounding.unsharpen value: activePlayer?.position / activePlayer?.length implicitSize: 20 colPrimary: Appearance.colors.colOnSecondaryContainer enableAnimation: false Item { anchors.centerIn: parent width: mediaCircProg.implicitSize height: mediaCircProg.implicitSize MaterialSymbol { anchors.centerIn: parent fill: 1 text: activePlayer?.isPlaying ? "pause" : "music_note" iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } } } StyledText { visible: Config.options.bar.verbose width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2) Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true // Ensures the text takes up available space Layout.rightMargin: rowLayout.spacing horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight // Truncates the text on the right color: Appearance.colors.colOnLayer1 text: `${cleanedTitle}${activePlayer?.trackArtist ? ' • ' + activePlayer.trackArtist : ''}` } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/NotificationUnreadCount.qml ================================================ import QtQuick import qs.services import qs.modules.common import qs.modules.common.widgets MaterialSymbol { id: root readonly property bool showUnreadCount: Config.options.bar.indicators.notifications.showUnreadCount text: Notifications.silent ? "notifications_paused" : "notifications" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText Rectangle { id: notifPing visible: !Notifications.silent && Notifications.unread > 0 anchors { right: parent.right top: parent.top rightMargin: root.showUnreadCount ? 0 : 1 topMargin: root.showUnreadCount ? 0 : 3 } radius: Appearance.rounding.full color: Appearance.colors.colOnLayer0 z: 1 implicitHeight: root.showUnreadCount ? Math.max(notificationCounterText.implicitWidth, notificationCounterText.implicitHeight) : 8 implicitWidth: implicitHeight StyledText { id: notificationCounterText visible: root.showUnreadCount anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.smallest color: Appearance.colors.colLayer0 text: Notifications.unread } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/Resource.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts Item { id: root required property string iconName required property double percentage property int warningThreshold: 100 property bool shown: true clip: true visible: width > 0 && height > 0 implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth implicitHeight: Appearance.sizes.barHeight property bool warning: percentage * 100 >= warningThreshold RowLayout { id: resourceRowLayout spacing: 2 x: shown ? 0 : -resourceRowLayout.width anchors { verticalCenter: parent.verticalCenter } ClippedFilledCircularProgress { id: resourceCircProg Layout.alignment: Qt.AlignVCenter lineWidth: Appearance.rounding.unsharpen value: percentage implicitSize: 20 colPrimary: root.warning ? Appearance.colors.colError : Appearance.colors.colOnSecondaryContainer accountForLightBleeding: !root.warning enableAnimation: false Item { anchors.centerIn: parent width: resourceCircProg.implicitSize height: resourceCircProg.implicitSize MaterialSymbol { anchors.centerIn: parent font.weight: Font.DemiBold fill: 1 text: iconName iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } } } Item { Layout.alignment: Qt.AlignVCenter implicitWidth: fullPercentageTextMetrics.width implicitHeight: percentageText.implicitHeight TextMetrics { id: fullPercentageTextMetrics text: "100" font.pixelSize: Appearance.font.pixelSize.small } StyledText { id: percentageText anchors.centerIn: parent color: Appearance.colors.colOnLayer1 font.pixelSize: Appearance.font.pixelSize.small text: `${Math.round(percentage * 100).toString()}` } } Behavior on x { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton enabled: resourceRowLayout.x >= 0 && root.width > 0 && root.visible } Behavior on implicitWidth { NumberAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/Resources.qml ================================================ import qs.modules.common import qs.services import QtQuick import QtQuick.Layouts MouseArea { id: root property bool borderless: Config.options.bar.borderless property bool alwaysShowAllResources: false implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: Appearance.sizes.barHeight hoverEnabled: !Config.options.bar.tooltips.clickToShow RowLayout { id: rowLayout spacing: 0 anchors.fill: parent anchors.leftMargin: 4 anchors.rightMargin: 4 Resource { iconName: "memory" percentage: ResourceUsage.memoryUsedPercentage warningThreshold: Config.options.bar.resources.memoryWarningThreshold } Resource { iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) || (MprisController.activePlayer?.trackTitle == null) || root.alwaysShowAllResources Layout.leftMargin: shown ? 6 : 0 warningThreshold: Config.options.bar.resources.swapWarningThreshold } Resource { iconName: "planner_review" percentage: ResourceUsage.cpuUsage shown: Config.options.bar.resources.alwaysShowCpu || !(MprisController.activePlayer?.trackTitle?.length > 0) || root.alwaysShowAllResources Layout.leftMargin: shown ? 6 : 0 warningThreshold: Config.options.bar.resources.cpuWarningThreshold } } ResourcesPopup { hoverTarget: root } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/ResourcesPopup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts StyledPopup { id: root // Helper function to format KB to GB function formatKB(kb) { return (kb / (1024 * 1024)).toFixed(1) + " GB"; } Row { anchors.centerIn: parent spacing: 12 Column { anchors.top: parent.top spacing: 8 StyledPopupHeaderRow { icon: "memory" label: "RAM" } Column { spacing: 4 StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.memoryUsed) } StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.memoryFree) } StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.memoryTotal) } } } Column { visible: ResourceUsage.swapTotal > 0 anchors.top: parent.top spacing: 8 StyledPopupHeaderRow { icon: "swap_horiz" label: "Swap" } Column { spacing: 4 StyledPopupValueRow { icon: "clock_loader_60" label: Translation.tr("Used:") value: root.formatKB(ResourceUsage.swapUsed) } StyledPopupValueRow { icon: "check_circle" label: Translation.tr("Free:") value: root.formatKB(ResourceUsage.swapFree) } StyledPopupValueRow { icon: "empty_dashboard" label: Translation.tr("Total:") value: root.formatKB(ResourceUsage.swapTotal) } } } Column { anchors.top: parent.top spacing: 8 StyledPopupHeaderRow { icon: "planner_review" label: "CPU" } Column { spacing: 4 StyledPopupValueRow { icon: "bolt" label: Translation.tr("Load:") value: `${Math.round(ResourceUsage.cpuUsage * 100)}%` } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/ScrollHint.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick Revealer { // Scroll hint id: root property string icon property string side: "left" property string tooltipText: "" MouseArea { id: mouseArea anchors.right: root.side === "left" ? parent.right : undefined anchors.left: root.side === "right" ? parent.left : undefined implicitWidth: contentColumn.implicitWidth implicitHeight: contentColumn.implicitHeight property bool hovered: false hoverEnabled: true onEntered: hovered = true onExited: hovered = false acceptedButtons: Qt.NoButton property bool showHintTimedOut: false onHoveredChanged: showHintTimedOut = false Timer { running: mouseArea.hovered interval: 500 onTriggered: mouseArea.showHintTimedOut = true } PopupToolTip { extraVisibleCondition: (tooltipText.length > 0 && mouseArea.showHintTimedOut) text: tooltipText } Column { id: contentColumn anchors { fill: parent } spacing: -5 MaterialSymbol { text: "keyboard_arrow_up" iconSize: 14 color: Appearance.colors.colSubtext } MaterialSymbol { text: root.icon iconSize: 14 color: Appearance.colors.colSubtext } MaterialSymbol { text: "keyboard_arrow_down" iconSize: 14 color: Appearance.colors.colSubtext } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/StyledPopup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Effects import Quickshell import Quickshell.Wayland LazyLoader { id: root property Item hoverTarget default property Item contentItem property real popupBackgroundMargin: 0 active: hoverTarget && hoverTarget.containsMouse component: PanelWindow { id: popupWindow color: "transparent" anchors.left: !Config.options.bar.vertical || (Config.options.bar.vertical && !Config.options.bar.bottom) anchors.right: Config.options.bar.vertical && Config.options.bar.bottom anchors.top: Config.options.bar.vertical || (!Config.options.bar.vertical && !Config.options.bar.bottom) anchors.bottom: !Config.options.bar.vertical && Config.options.bar.bottom implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin mask: Region { item: popupBackground } exclusionMode: ExclusionMode.Ignore exclusiveZone: 0 margins { left: { if (!Config.options.bar.vertical) return root.QsWindow?.mapFromItem( root.hoverTarget, (root.hoverTarget.width - popupBackground.implicitWidth) / 2, 0 ).x; return Appearance.sizes.verticalBarWidth } top: { if (!Config.options.bar.vertical) return Appearance.sizes.barHeight; return root.QsWindow?.mapFromItem( root.hoverTarget, (root.hoverTarget.height - popupBackground.implicitHeight) / 2, 0 ).y; } right: Appearance.sizes.verticalBarWidth bottom: Appearance.sizes.barHeight } WlrLayershell.namespace: "quickshell:popup" WlrLayershell.layer: WlrLayer.Overlay StyledRectangularShadow { target: popupBackground } Rectangle { id: popupBackground readonly property real margin: 10 anchors { fill: parent leftMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.left) rightMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.right) topMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.top) bottomMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.bottom) } implicitWidth: root.contentItem.implicitWidth + margin * 2 implicitHeight: root.contentItem.implicitHeight + margin * 2 color: Appearance.m3colors.m3surfaceContainer radius: Appearance.rounding.small children: [root.contentItem] border.width: 1 border.color: Appearance.colors.colLayer0Border } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/StyledPopupHeaderRow.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets Row { id: root required property var icon required property var label spacing: 5 MaterialSymbol { anchors.verticalCenter: parent.verticalCenter fill: 0 font.weight: Font.DemiBold text: root.icon iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnSurfaceVariant } StyledText { anchors.verticalCenter: parent.verticalCenter text: root.label font { weight: Font.DemiBold pixelSize: Appearance.font.pixelSize.normal } color: Appearance.colors.colOnSurfaceVariant } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/StyledPopupValueRow.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets RowLayout { id: root required property string icon required property string label required property string value spacing: 4 MaterialSymbol { text: root.icon color: Appearance.colors.colOnSurfaceVariant iconSize: Appearance.font.pixelSize.large } StyledText { text: root.label color: Appearance.colors.colOnSurfaceVariant } StyledText { Layout.fillWidth: true horizontalAlignment: Text.AlignRight visible: root.value !== "" color: Appearance.colors.colOnSurfaceVariant text: root.value } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Quickshell.Services.SystemTray import qs.services import qs.modules.common import qs.modules.common.widgets Item { id: root implicitWidth: gridLayout.implicitWidth implicitHeight: gridLayout.implicitHeight property bool vertical: false property bool invertSide: false property bool trayOverflowOpen: false property bool showSeparator: true property bool showOverflowMenu: true property var activeMenu: null property list pinnedItems: TrayService.pinnedItems property list unpinnedItems: TrayService.unpinnedItems onUnpinnedItemsChanged: { if (unpinnedItems.length == 0) root.closeOverflowMenu(); } function grabFocus() { focusGrab.active = true; } function setExtraWindowAndGrabFocus(window) { if (root.activeMenu && root.activeMenu !== window) { if (typeof root.activeMenu.close === "function") root.activeMenu.close(); root.activeMenu = null; } root.activeMenu = window; root.grabFocus(); } function releaseFocus() { focusGrab.active = false; } function closeOverflowMenu() { focusGrab.active = false; } onTrayOverflowOpenChanged: { if (root.trayOverflowOpen) { root.grabFocus(); } } HyprlandFocusGrab { id: focusGrab active: false windows: [trayOverflowLayout.QsWindow?.window, root.activeMenu] onCleared: { root.trayOverflowOpen = false; if (root.activeMenu) { root.activeMenu.close(); root.activeMenu = null; } } } GridLayout { id: gridLayout columns: root.vertical ? 1 : -1 anchors.fill: parent rowSpacing: 8 columnSpacing: 15 RippleButton { id: trayOverflowButton visible: root.showOverflowMenu && root.unpinnedItems.length > 0 toggled: root.trayOverflowOpen property bool containsMouse: hovered downAction: () => root.trayOverflowOpen = !root.trayOverflowOpen Layout.fillHeight: !root.vertical Layout.fillWidth: root.vertical background.implicitWidth: 24 background.implicitHeight: 24 background.anchors.centerIn: this colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive contentItem: MaterialSymbol { anchors.centerIn: parent iconSize: Appearance.font.pixelSize.larger text: "expand_more" horizontalAlignment: Text.AlignHCenter color: root.trayOverflowOpen ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer2 rotation: (root.trayOverflowOpen ? 180 : 0) - (90 * root.vertical) + (180 * root.invertSide) Behavior on rotation { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } StyledPopup { id: overflowPopup hoverTarget: trayOverflowButton active: root.trayOverflowOpen && root.unpinnedItems.length > 0 GridLayout { id: trayOverflowLayout anchors.centerIn: parent columns: Math.ceil(Math.sqrt(root.unpinnedItems.length)) columnSpacing: 10 rowSpacing: 10 Repeater { model: root.unpinnedItems delegate: SysTrayItem { required property SystemTrayItem modelData item: modelData Layout.fillHeight: !root.vertical Layout.fillWidth: root.vertical onMenuClosed: root.releaseFocus(); onMenuOpened: (qsWindow) => root.setExtraWindowAndGrabFocus(qsWindow); } } } } } Repeater { model: ScriptModel { values: root.pinnedItems } delegate: SysTrayItem { required property SystemTrayItem modelData item: modelData Layout.fillHeight: !root.vertical Layout.fillWidth: root.vertical onMenuClosed: root.releaseFocus(); onMenuOpened: (qsWindow) => { root.setExtraWindowAndGrabFocus(qsWindow); } } } StyledText { Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.colors.colSubtext text: "•" visible: root.showSeparator && SystemTray.items.values.length > 0 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Services.SystemTray import Quickshell.Widgets import Qt5Compat.GraphicalEffects import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions MouseArea { id: root required property SystemTrayItem item property bool targetMenuOpen: false signal menuOpened(qsWindow: var) signal menuClosed() hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton implicitWidth: 20 implicitHeight: 20 onPressed: (event) => { switch (event.button) { case Qt.LeftButton: item.activate(); break; case Qt.RightButton: if (item.hasMenu) if (menu.active && menu.item && typeof menu.item.close === "function") menu.item.close(); else menu.open(); break; } event.accepted = true; } onEntered: { tooltip.text = TrayService.getTooltipForItem(root.item); } Loader { id: menu function open() { menu.active = true; } active: false sourceComponent: SysTrayMenu { Component.onCompleted: this.open(); trayItemMenuHandle: root.item.menu trayItemId: root.item.id anchor { window: root.QsWindow.window item: root gravity: Config.options.bar.vertical ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) edges: Config.options.bar.vertical ? (Config.options.bar.bottom ? Edges.Left : Edges.Right) : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom) } onMenuOpened: (window) => root.menuOpened(window); onMenuClosed: { root.menuClosed(); menu.active = false; } } } IconImage { id: trayIcon visible: !Config.options.tray.monochromeIcons source: root.item.icon anchors.centerIn: parent width: parent.width height: parent.height } Loader { active: Config.options.tray.monochromeIcons anchors.fill: trayIcon sourceComponent: Item { Desaturate { id: desaturatedIcon visible: false // There's already color overlay anchors.fill: parent source: trayIcon desaturation: 0.8 // 1.0 means fully grayscale } ColorOverlay { anchors.fill: desaturatedIcon source: desaturatedIcon color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.9) } } } PopupToolTip { id: tooltip extraVisibleCondition: root.containsMouse alternativeVisibleCondition: extraVisibleCondition anchorEdges: (!Config.options.bar.bottom && !Config.options.bar.vertical) ? Edges.Bottom : Edges.Top } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell PopupWindow { id: root required property QsMenuHandle trayItemMenuHandle property string trayItemId: "" property real popupBackgroundMargin: 0 signal menuClosed signal menuOpened(qsWindow: var) // Correct type is QsWindow, but QML does not like that color: "transparent" property real padding: Appearance.sizes.elevationMargin implicitHeight: { let result = 0; for (let child of stackView.children) { result = Math.max(child.implicitHeight, result); } return result + popupBackground.padding * 2 + root.padding * 2; } implicitWidth: { let result = 0; for (let child of stackView.children) { result = Math.max(child.implicitWidth, result); } return result + popupBackground.padding * 2 + root.padding * 2; } function open() { root.visible = true; root.menuOpened(root); } function close() { root.visible = false; while (stackView.depth > 1) stackView.pop(); root.menuClosed(); } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton | Qt.RightButton onPressed: event => { if ((event.button === Qt.BackButton || event.button === Qt.RightButton) && stackView.depth > 1) stackView.pop(); } StyledRectangularShadow { target: popupBackground opacity: popupBackground.opacity } Rectangle { id: popupBackground readonly property real padding: 4 anchors { left: parent.left right: parent.right verticalCenter: Config.options.bar.vertical ? parent.verticalCenter : undefined top: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? undefined : parent.top bottom: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? parent.bottom : undefined margins: root.padding } color: Appearance.colors.colLayer0 radius: Appearance.rounding.windowRounding border.width: 1 border.color: Appearance.colors.colLayer0Border clip: true opacity: 0 Component.onCompleted: opacity = 1 implicitWidth: stackView.implicitWidth + popupBackground.padding * 2 implicitHeight: stackView.implicitHeight + popupBackground.padding * 2 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitHeight { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } Behavior on implicitWidth { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } StackView { id: stackView anchors { fill: parent margins: popupBackground.padding } pushEnter: NoAnim {} pushExit: NoAnim {} popEnter: NoAnim {} popExit: NoAnim {} implicitWidth: currentItem.implicitWidth implicitHeight: currentItem.implicitHeight initialItem: SubMenu { handle: root.trayItemMenuHandle } } } } component NoAnim: Transition { NumberAnimation { duration: 0 } } component SubMenu: ColumnLayout { id: submenu required property QsMenuHandle handle property bool isSubMenu: false property bool shown: false opacity: shown ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Component.onCompleted: shown = true StackView.onActivating: shown = true StackView.onDeactivating: shown = false StackView.onRemoved: destroy() QsMenuOpener { id: menuOpener menu: submenu.handle } spacing: 0 Loader { Layout.fillWidth: true visible: submenu.isSubMenu active: visible sourceComponent: RippleButton { id: backButton buttonRadius: popupBackground.radius - popupBackground.padding horizontalPadding: 12 implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 implicitHeight: 36 downAction: () => stackView.pop() contentItem: RowLayout { anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: backButton.horizontalPadding rightMargin: backButton.horizontalPadding } spacing: 8 MaterialSymbol { iconSize: 20 text: "chevron_left" } StyledText { Layout.fillWidth: true text: Translation.tr("Back") } } } } RippleButton { id: pinEntry buttonRadius: popupBackground.radius - popupBackground.padding horizontalPadding: 12 implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 implicitHeight: 36 Layout.topMargin: 0 Layout.bottomMargin: 0 Layout.fillWidth: true visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1 releaseAction: () => TrayService.togglePin(root.trayItemId); contentItem: RowLayout { anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: pinEntry.horizontalPadding rightMargin: pinEntry.horizontalPadding } spacing: 8 MaterialSymbol { iconSize: 18 text: "push_pin" } StyledText { Layout.fillWidth: true text: TrayService.isPinned(root.trayItemId) ? Translation.tr("Unpin") : Translation.tr("Pin") } } } Rectangle { Layout.fillWidth: true implicitHeight: 1 color: Appearance.colors.colSubtext Layout.topMargin: 4 Layout.bottomMargin: 4 } Repeater { id: menuEntriesRepeater property bool iconColumnNeeded: { for (let i = 0; i < menuOpener.children.values.length; i++) { if (menuOpener.children.values[i].icon.length > 0) return true; } return false; } property bool specialInteractionColumnNeeded: { for (let i = 0; i < menuOpener.children.values.length; i++) { if (menuOpener.children.values[i].buttonType !== QsMenuButtonType.None) return true; } return false; } model: menuOpener.children delegate: SysTrayMenuEntry { required property QsMenuEntry modelData forceIconColumn: menuEntriesRepeater.iconColumnNeeded forceSpecialInteractionColumn: menuEntriesRepeater.specialInteractionColumnNeeded menuEntry: modelData buttonRadius: popupBackground.radius - popupBackground.padding onDismiss: root.close() onOpenSubmenu: handle => { stackView.push(subMenuComponent.createObject(null, { handle: handle, isSubMenu: true })); } } } } Component { id: subMenuComponent SubMenu {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenuEntry.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets RippleButton { id: root required property QsMenuEntry menuEntry property bool forceIconColumn: false property bool forceSpecialInteractionColumn: false readonly property bool hasIcon: menuEntry.icon.length > 0 readonly property bool hasSpecialInteraction: menuEntry.buttonType !== QsMenuButtonType.None signal dismiss() signal openSubmenu(handle: QsMenuHandle) colBackground: menuEntry.isSeparator ? Appearance.m3colors.m3outlineVariant : ColorUtils.transparentize(Appearance.colors.colLayer0) enabled: !menuEntry.isSeparator opacity: 1 horizontalPadding: 12 implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 implicitHeight: menuEntry.isSeparator ? 1 : 36 Layout.topMargin: menuEntry.isSeparator ? 4 : 0 Layout.bottomMargin: menuEntry.isSeparator ? 4 : 0 Layout.fillWidth: true Component.onCompleted: { if (menuEntry.isSeparator) { root.buttonColor = root.colBackground; } } releaseAction: () => { if (menuEntry.hasChildren) { root.openSubmenu(root.menuEntry); return; } menuEntry.triggered(); root.dismiss(); } altAction: (event) => { // Not hog right-click event.accepted = false; } contentItem: RowLayout { id: contentItem anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding } spacing: 8 visible: !root.menuEntry.isSeparator // Interaction: checkbox or radio button Item { visible: root.hasSpecialInteraction || root.forceSpecialInteractionColumn implicitWidth: 20 implicitHeight: 20 Loader { anchors.fill: parent active: root.menuEntry.buttonType === QsMenuButtonType.RadioButton sourceComponent: StyledRadioButton { enabled: false padding: 0 checked: root.menuEntry.checkState === Qt.Checked } } Loader { anchors.fill: parent active: root.menuEntry.buttonType === QsMenuButtonType.CheckBox && root.menuEntry.checkState !== Qt.Unchecked sourceComponent: MaterialSymbol { text: root.menuEntry.checkState === Qt.PartiallyChecked ? "check_indeterminate_small" : "check" iconSize: 20 } } } // Button icon Item { visible: root.hasIcon || root.forceIconColumn implicitWidth: 20 implicitHeight: 20 Loader { anchors.centerIn: parent active: root.menuEntry.icon.length > 0 sourceComponent: IconImage { asynchronous: true source: root.menuEntry.icon implicitSize: 20 mipmap: true } } } StyledText { id: label text: root.menuEntry.text font.pixelSize: Appearance.font.pixelSize.smallie Layout.fillWidth: true } Loader { active: root.menuEntry.hasChildren sourceComponent: MaterialSymbol { text: "chevron_right" iconSize: 20 } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/UtilButtons.qml ================================================ import qs import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Quickshell.Services.Pipewire import Quickshell.Services.UPower Item { id: root property bool borderless: Config.options.bar.borderless implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: rowLayout.implicitHeight RowLayout { id: rowLayout spacing: 4 anchors.centerIn: parent Loader { active: Config.options.bar.utilButtons.showScreenSnip visible: Config.options.bar.utilButtons.showScreenSnip sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 text: "screenshot_region" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showScreenRecord visible: Config.options.bar.utilButtons.showScreenRecord sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Quickshell.execDetached([Directories.recordScriptPath]) MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 text: "videocam" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showColorPicker visible: Config.options.bar.utilButtons.showColorPicker sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Quickshell.execDetached(["hyprpicker", "-a"]) MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 1 text: "colorize" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showKeyboardToggle visible: Config.options.bar.utilButtons.showKeyboardToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: GlobalStates.oskOpen = !GlobalStates.oskOpen MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 0 text: "keyboard" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showMicToggle visible: Config.options.bar.utilButtons.showMicToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_SOURCE@", "toggle"]) MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 0 text: Pipewire.defaultAudioSource?.audio?.muted ? "mic_off" : "mic" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showDarkModeToggle visible: Config.options.bar.utilButtons.showDarkModeToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: event => { if (Appearance.m3colors.darkmode) { Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`]) } else { Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`]) } } MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 0 text: Appearance.m3colors.darkmode ? "light_mode" : "dark_mode" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } Loader { active: Config.options.bar.utilButtons.showPerformanceProfileToggle visible: Config.options.bar.utilButtons.showPerformanceProfileToggle sourceComponent: CircleUtilButton { Layout.alignment: Qt.AlignVCenter onClicked: event => { if (PowerProfiles.hasPerformanceProfile) { switch(PowerProfiles.profile) { case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced break; case PowerProfile.Balanced: PowerProfiles.profile = PowerProfile.Performance break; case PowerProfile.Performance: PowerProfiles.profile = PowerProfile.PowerSaver break; } } else { PowerProfiles.profile = PowerProfiles.profile == PowerProfile.Balanced ? PowerProfile.PowerSaver : PowerProfile.Balanced } } MaterialSymbol { horizontalAlignment: Qt.AlignHCenter fill: 0 text: switch(PowerProfiles.profile) { case PowerProfile.PowerSaver: return "energy_savings_leaf" case PowerProfile.Balanced: return "airwave" case PowerProfile.Performance: return "local_fire_department" } iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.models import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Widgets import Qt5Compat.GraphicalEffects Item { id: root property bool vertical: false property bool borderless: Config.options.bar.borderless readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1 readonly property int workspacesShown: Config.options.bar.workspaces.shown readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown) property list workspaceOccupied: [] property int widgetPadding: 4 property int workspaceButtonWidth: 26 property real activeWorkspaceMargin: 2 property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown property bool showNumbers: false Timer { id: showNumbersTimer interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) repeat: false onTriggered: { root.showNumbers = true } } Connections { target: GlobalStates function onSuperDownChanged() { if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; if (GlobalStates.superDown) showNumbersTimer.restart(); else { showNumbersTimer.stop(); root.showNumbers = false; } } function onSuperReleaseMightTriggerChanged() { showNumbersTimer.stop() } } // Function to update workspaceOccupied function updateWorkspaceOccupied() { workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => { return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1); }) } // Occupied workspace updates Component.onCompleted: updateWorkspaceOccupied() Connections { target: Hyprland.workspaces function onValuesChanged() { updateWorkspaceOccupied(); } } Connections { target: Hyprland function onFocusedWorkspaceChanged() { updateWorkspaceOccupied(); } } onWorkspaceGroupChanged: { updateWorkspaceOccupied(); } implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown) implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight // Scroll to switch workspaces WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) Hyprland.dispatch(`hl.dsp.focus({workspace = "r+1"})`); else if (event.angleDelta.y > 0) Hyprland.dispatch(`hl.dsp.focus({workspace = "r-1"})`); } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onPressed: (event) => { if (event.button === Qt.BackButton) { Hyprland.dispatch(`hl.dsp.workspace.toggle_special("special")`); } } } // Workspaces - background Grid { z: 1 anchors.centerIn: parent rowSpacing: 0 columnSpacing: 0 columns: root.vertical ? 1 : root.workspacesShown rows: root.vertical ? root.workspacesShown : 1 Repeater { model: root.workspacesShown Rectangle { z: 1 implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth radius: (width / 2) property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index)) property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2)) property var radiusPrev: previousOccupied ? 0 : (width / 2) property var radiusNext: rightOccupied ? 0 : (width / 2) topLeftRadius: radiusPrev bottomLeftRadius: root.vertical ? radiusNext : radiusPrev topRightRadius: root.vertical ? radiusPrev : radiusNext bottomRightRadius: radiusNext color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusPrev { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusNext { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } } } // Active workspace Rectangle { z: 2 // Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight radius: Appearance.rounding.full color: Appearance.colors.colPrimary anchors { verticalCenter: vertical ? undefined : parent.verticalCenter horizontalCenter: vertical ? parent.horizontalCenter : undefined } AnimatedTabIndexPair { id: idxPair index: root.workspaceIndexInGroup } property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2 property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2 x: root.vertical ? null : indicatorPosition implicitWidth: root.vertical ? indicatorThickness : indicatorLength y: root.vertical ? indicatorPosition : null implicitHeight: root.vertical ? indicatorLength : indicatorThickness } // Workspaces - numbers Grid { z: 3 columns: root.vertical ? 1 : root.workspacesShown rows: root.vertical ? root.workspacesShown : 1 columnSpacing: 0 rowSpacing: 0 anchors.fill: parent Repeater { model: root.workspacesShown Button { id: button property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1 implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`) width: vertical ? undefined : root.workspaceButtonWidth height: vertical ? root.workspaceButtonWidth : undefined background: Item { id: workspaceButtonBackground implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue) property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") StyledText { // Workspace number text opacity: root.showNumbers || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers)) || (root.showNumbers && !Config.options?.bar.workspaces.showAppIcons) ) ? 1 : 0 z: 3 anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font { pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) family: Config.options?.bar.workspaces.useNerdFont ? Appearance.font.family.iconNerd : defaultFont } text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue elide: Text.ElideRight color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Rectangle { // Dot instead of ws number id: wsDot opacity: (Config.options?.bar.workspaces.alwaysShowNumbers || root.showNumbers || (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) ) ? 0 : 1 visible: opacity > 0 anchors.centerIn: parent width: workspaceButtonWidth * 0.18 height: width radius: width / 2 color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Item { // Main app icon anchors.centerIn: parent width: workspaceButtonWidth height: workspaceButtonWidth opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom anchors.right: parent.right anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked source: workspaceButtonBackground.mainAppIconSource implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitSize { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Loader { active: Config.options.bar.workspaces.monochromeIcons anchors.fill: mainAppIcon sourceComponent: Item { Desaturate { id: desaturatedIcon visible: false // There's already color overlay anchors.fill: parent source: mainAppIcon desaturation: 0.8 } ColorOverlay { anchors.fill: desaturatedIcon source: desaturatedIcon color: ColorUtils.transparentize(wsDot.color, 0.9) } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherBar.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import Quickshell import QtQuick import QtQuick.Layouts MouseArea { id: root property bool hovered: false implicitWidth: rowLayout.implicitWidth + 10 * 2 implicitHeight: Appearance.sizes.barHeight acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: !Config.options.bar.tooltips.clickToShow onPressed: { if (mouse.button === Qt.RightButton) { Weather.getData(); Quickshell.execDetached(["notify-send", Translation.tr("Weather"), Translation.tr("Refreshing (manually triggered)") , "-a", "Shell" ]) mouse.accepted = false } } RowLayout { id: rowLayout anchors.centerIn: parent MaterialSymbol { fill: 0 text: Icons.getWeatherIcon(Weather.data.wCode) ?? "cloud" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 Layout.alignment: Qt.AlignVCenter } StyledText { visible: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: Weather.data?.temp ?? "--°" Layout.alignment: Qt.AlignVCenter } } WeatherPopup { id: weatherPopup hoverTarget: root } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherCard.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets Rectangle { id: root radius: Appearance.rounding.small color: Appearance.colors.colSurfaceContainerHigh implicitWidth: columnLayout.implicitWidth + 14 * 2 implicitHeight: columnLayout.implicitHeight + 14 * 2 Layout.fillWidth: parent property alias title: title.text property alias value: value.text property alias symbol: symbol.text ColumnLayout { id: columnLayout anchors.fill: parent spacing: -10 RowLayout { Layout.alignment: Qt.AlignHCenter MaterialSymbol { id: symbol fill: 0 iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnSurfaceVariant } StyledText { id: title font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnSurfaceVariant } } StyledText { id: value Layout.alignment: Qt.AlignHCenter font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnSurfaceVariant } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherPopup.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import qs.modules.ii.bar StyledPopup { id: root ColumnLayout { id: columnLayout anchors.centerIn: parent implicitWidth: Math.max(header.implicitWidth, gridLayout.implicitWidth) implicitHeight: gridLayout.implicitHeight spacing: 5 // Header ColumnLayout { id: header Layout.alignment: Qt.AlignHCenter spacing: 2 RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 6 MaterialSymbol { fill: 0 font.weight: Font.Medium text: "location_on" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnSurfaceVariant } StyledText { text: Weather.data.city font { weight: Font.Medium pixelSize: Appearance.font.pixelSize.normal } color: Appearance.colors.colOnSurfaceVariant } } StyledText { id: temp font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnSurfaceVariant text: Weather.data.temp + " • " + Translation.tr("Feels like %1").arg(Weather.data.tempFeelsLike) } } // Metrics grid GridLayout { id: gridLayout columns: 2 rowSpacing: 5 columnSpacing: 5 uniformCellWidths: true WeatherCard { title: Translation.tr("UV Index") symbol: "wb_sunny" value: Weather.data.uv } WeatherCard { title: Translation.tr("Wind") symbol: "air" value: `(${Weather.data.windDir}) ${Weather.data.wind}` } WeatherCard { title: Translation.tr("Precipitation") symbol: "rainy_light" value: Weather.data.precip } WeatherCard { title: Translation.tr("Humidity") symbol: "humidity_low" value: Weather.data.humidity } WeatherCard { title: Translation.tr("Visibility") symbol: "visibility" value: Weather.data.visib } WeatherCard { title: Translation.tr("Pressure") symbol: "readiness_score" value: Weather.data.press } WeatherCard { title: Translation.tr("Sunrise") symbol: "wb_twilight" value: Weather.data.sunrise } WeatherCard { title: Translation.tr("Sunset") symbol: "bedtime" value: Weather.data.sunset } } // Footer: last refresh StyledText { Layout.alignment: Qt.AlignHCenter text: Translation.tr("Last refresh: %1").arg(Weather.data.lastRefresh) font { weight: Font.Medium pixelSize: Appearance.font.pixelSize.smaller } color: Appearance.colors.colOnSurfaceVariant } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt.labs.synchronizer import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { // Scope id: root property var tabButtonList: [ { "icon": "keyboard", "name": Translation.tr("Keybinds") }, { "icon": "experiment", "name": Translation.tr("Elements") }, ] Loader { id: cheatsheetLoader active: false sourceComponent: PanelWindow { // Window id: cheatsheetRoot visible: cheatsheetLoader.active anchors { top: true bottom: true left: true right: true } function hide() { cheatsheetLoader.active = false; } exclusiveZone: 0 implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2 implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2 WlrLayershell.namespace: "quickshell:cheatsheet" // Setting this value makes it take its sweet time to open // WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" mask: Region { item: cheatsheetBackground } Component.onCompleted: { GlobalFocusGrab.addDismissable(cheatsheetRoot); } Component.onDestruction: { GlobalFocusGrab.removeDismissable(cheatsheetRoot); } Connections { target: GlobalFocusGrab function onDismissed() { cheatsheetRoot.hide(); } } // Background StyledRectangularShadow { target: cheatsheetBackground } Rectangle { id: cheatsheetBackground anchors.centerIn: parent color: Appearance.colors.colLayer0 border.width: 1 border.color: Appearance.colors.colLayer0Border radius: Appearance.rounding.windowRounding property real padding: 20 implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2 implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2 Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { cheatsheetRoot.hide(); } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { tabBar.incrementCurrentIndex(); event.accepted = true; } else if (event.key === Qt.Key_PageUp) { tabBar.decrementCurrentIndex(); event.accepted = true; } else if (event.key === Qt.Key_Tab) { tabBar.setCurrentIndex((tabBar.currentIndex + 1) % root.tabButtonList.length); event.accepted = true; } else if (event.key === Qt.Key_Backtab) { tabBar.setCurrentIndex((tabBar.currentIndex - 1 + root.tabButtonList.length) % root.tabButtonList.length); event.accepted = true; } } } RippleButton { // Close button id: closeButton focus: cheatsheetRoot.visible implicitWidth: 40 implicitHeight: 40 buttonRadius: Appearance.rounding.full anchors { top: parent.top right: parent.right topMargin: 20 rightMargin: 20 } onClicked: { cheatsheetRoot.hide(); } contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.title text: "close" } } ColumnLayout { // Real content id: cheatsheetColumnLayout anchors.centerIn: parent spacing: 10 Toolbar { Layout.alignment: Qt.AlignHCenter enableShadow: false ToolbarTabBar { id: tabBar tabButtonList: root.tabButtonList Synchronizer on currentIndex { property alias source: swipeView.currentIndex } } } SwipeView { // Content pages id: swipeView Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true spacing: 10 currentIndex: Persistent.states.cheatsheet.tabIndex onCurrentIndexChanged: { Persistent.states.cheatsheet.tabIndex = currentIndex; } implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0)) implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0)) clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: swipeView.width height: swipeView.height radius: Appearance.rounding.small } } CheatsheetKeybinds {} CheatsheetPeriodicTable {} } } } } } IpcHandler { target: "cheatsheet" function toggle(): void { cheatsheetLoader.active = !cheatsheetLoader.active; } function close(): void { cheatsheetLoader.active = false; } function open(): void { cheatsheetLoader.active = true; } } GlobalShortcut { name: "cheatsheetToggle" description: "Toggles cheatsheet on press" onPressed: { cheatsheetLoader.active = !cheatsheetLoader.active; } } GlobalShortcut { name: "cheatsheetOpen" description: "Opens cheatsheet on press" onPressed: { cheatsheetLoader.active = true; } } GlobalShortcut { name: "cheatsheetClose" description: "Closes cheatsheet on press" onPressed: { cheatsheetLoader.active = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell Item { id: root property real padding: 4 implicitWidth: QsWindow?.window?.screen.width * 0.7 ?? 0 implicitHeight: QsWindow?.window?.screen.height * 0.7 ?? 0 StyledFlickable { id: flickable clip: true anchors.fill: parent anchors.margins: Appearance.rounding.small contentHeight: height contentWidth: flow.implicitWidth Flow { id: flow height: flickable.height flow: Flow.TopToBottom spacing: 10 Repeater { model: [...HyprlandKeybinds.keybindCategories, ""] delegate: CheatsheetKeybindsCategory { required property var modelData categoryName: modelData } } } } ScrollEdgeFade { target: flickable vertical: false color: Appearance.colors.colLayer0Base } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell // Notes: // We deal with keybinds being numbered 1, 2, etc by discarding 2+, keeping 1 and replacing it with a generic "" Column { id: root required property string categoryName readonly property bool isCategorized: categoryName?.length > 0 property int maxBindWidth: 0 property real columnSpacing: 40 property real titleSpacing: 7 // Excellent symbol explaination and source : // http://xahlee.info/comp/unicode_computing_symbols.html // https://www.nerdfonts.com/cheat-sheet property var macSymbolMap: ({ "Ctrl": "󰘴", "Alt": "󰘵", "Shift": "󰘶", "Space": "󱁐", "Tab": "↹", "Equal": "󰇼", "Minus": "", "Print": "", "BackSpace": "󰭜", "Delete": "⌦", "Return": "󰌑", "Period": ".", "Escape": "⎋" }) property var functionSymbolMap: ({ "F1": "󱊫", "F2": "󱊬", "F3": "󱊭", "F4": "󱊮", "F5": "󱊯", "F6": "󱊰", "F7": "󱊱", "F8": "󱊲", "F9": "󱊳", "F10": "󱊴", "F11": "󱊵", "F12": "󱊶", }) property var mouseSymbolMap: ({ "mouse_up": "󱕐", "mouse_down": "󱕑", "mouse:272": "L󰍽", "mouse:273": "R󰍽", "Scroll ↑/↓": "󱕒", "Page_↑/↓": "⇞/⇟", }) property var keyBlacklist: ["SUPER_L", "SUPER_R"] property var keySubstitutions: Object.assign({ "Super": "", "mouse_up": "Scroll ↓", // ikr, weird "mouse_down": "Scroll ↑", // trust me bro "mouse:272": "LMB", "mouse:273": "RMB", "mouse:275": "MouseBack", "Slash": "/", "Hash": "#", "Return": "Enter", // "Shift": "", }, !!Config.options.cheatsheet.superKey ? { "Super": Config.options.cheatsheet.superKey, }: {}, Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {}, Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {}, Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {}, ) function modMaskToStringList(modMask: int): list { var list = []; // Funny mathematical order but we wanna have this natural user-facing order if (modMask & (1 << 2)) { list.push("Ctrl"); } if (modMask & (1 << 6)) { list.push("Super"); } if (modMask & (1 << 0)) { list.push("Shift"); } if (modMask & (1 << 3)) { list.push("Alt"); } if (modMask & (1 << 1)) { list.push("Caps"); } if (modMask & (1 << 4)) { list.push("Mod2"); } if (modMask & (1 << 5)) { list.push("Mod3"); } if (modMask & (1 << 7)) { list.push("Mod5"); } return list; } visible: repeater.model.length > 0 spacing: titleSpacing StyledText { text: root.isCategorized ? root.categoryName : "Uncategorized" font.pixelSize: Appearance.font.pixelSize.title } function hasDescription(bind) { return bind.description?.length > 0; } function isCategory(bind, categoryName) { return bind.description.substring(0, bind.description.indexOf(":")) === categoryName; } function isUncategorized(bind) { return bind.description.indexOf(":") === -1; } function containsNonFirstRepetitive(bind) { const key = bind.key; if (key.includes("mouse") || key.includes("page")) return false; // Contains non-1 number if (/\d/.test(key) && !key.includes("1")) return true; // Contains non-left direction if (/^(right|up|down)\b/i.test(key)) return true; return false; } function containsFirstRepetitive(bind) { const key = bind.key; return key.includes("1") || /left/i.test(key); } function transformKey(key) { const replaced = root.keySubstitutions[key] || key; const denumbered = replaced.replace("1", ""); const dedirectioned = denumbered.replace("Left", ""); return dedirectioned; } function transformDescription(bind, categoryName) { const description = bind.description const regex = new RegExp("\\s*" + categoryName + "\\s*:\\s*"); const decategorized = description.replace(regex, ""); if (!containsFirstRepetitive(bind)) return decategorized; const denumbered = decategorized.replace("1", ""); const dedirectioned = denumbered.replace(/ \b(left|right|up|down)\b/i, " "); return dedirectioned; } Column { spacing: 4 Repeater { id: repeater model: { if (!root.isCategorized) { return HyprlandKeybinds.keybinds.filter(bind => root.hasDescription(bind) && root.isUncategorized(bind) && !root.containsNonFirstRepetitive(bind)); } return HyprlandKeybinds.keybinds.filter(bind => root.hasDescription(bind) && root.isCategory(bind, root.categoryName) && !root.containsNonFirstRepetitive(bind)); } delegate: BindLine { required property var modelData keyData: modelData categoryName: root.categoryName } } } component BindLine: Row { id: bindLine required property var keyData property string categoryName: "" Row { spacing: 16 Row { id: modRow Component.onCompleted: root.maxBindWidth = Math.max(root.maxBindWidth, implicitWidth) width: root.maxBindWidth spacing: 4 Repeater { model: { const modList = root.modMaskToStringList(bindLine.keyData.modmask).map(mod => root.keySubstitutions[mod] || mod) if (modList.length == 0) return [] if (Config.options.cheatsheet.splitButtons) return modList; return [modList.join(" ")] } delegate: KeyboardKey { required property var modelData key: root.transformKey(modelData) pixelSize: Config.options.cheatsheet.fontSize.key } } StyledText { id: keybindPlus anchors.verticalCenter: parent.verticalCenter visible: !keyBlacklist.includes(bindLine.keyData.key) && bindLine.keyData.modmask > 0 text: "+" } KeyboardKey { id: keybindKey anchors.verticalCenter: parent.verticalCenter visible: !keyBlacklist.includes(bindLine.keyData.key) key: root.transformKey(bindLine.keyData.key) pixelSize: Config.options.cheatsheet.fontSize.key color: Appearance.colors.colOnLayer0 } } Item { anchors.verticalCenter: parent.verticalCenter implicitWidth: commentText.implicitWidth + root.columnSpacing implicitHeight: commentText.implicitHeight StyledText { id: commentText anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller text: root.transformDescription(bindLine.keyData, bindLine.categoryName) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetPeriodicTable.qml ================================================ import "periodic_table.js" as PTable import QtQuick Item { id: root readonly property var elements: PTable.elements readonly property var series: PTable.series property real spacing: 6 implicitWidth: mainLayout.implicitWidth implicitHeight: mainLayout.implicitHeight Column { id: mainLayout anchors.centerIn: parent spacing: root.spacing Repeater { // Main table rows model: root.elements delegate: Row { // Table cells id: tableRow spacing: root.spacing required property var modelData Repeater { model: tableRow.modelData delegate: ElementTile { required property var modelData element: modelData } } } } Item { id: gap implicitHeight: 20 } Repeater { // Main table rows model: root.series delegate: Row { // Table cells id: seriesTableRow spacing: root.spacing required property var modelData Repeater { model: seriesTableRow.modelData delegate: ElementTile { required property var modelData element: modelData } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/ElementTile.qml ================================================ import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import QtQuick RippleButton { id: root required property var element opacity: element.type != "empty" ? 1 : 0 implicitHeight: 70 implicitWidth: 70 colBackground: Appearance.colors.colLayer2 buttonRadius: Appearance.rounding.small Rectangle { anchors { top: parent.top left: parent.left topMargin: 4 leftMargin: 4 } color: ColorUtils.transparentize(Appearance.colors.colLayer2) radius: Appearance.rounding.full implicitWidth: Math.max(20, elementNumber.implicitWidth) implicitHeight: Math.max(20, elementNumber.implicitHeight) width: height StyledText { id: elementNumber anchors.left: parent.left color: Appearance.colors.colOnLayer2 text: root.element.number font.pixelSize: Appearance.font.pixelSize.smallest } } Rectangle { anchors { top: parent.top right: parent.right topMargin: 4 rightMargin: 4 } color: ColorUtils.transparentize(Appearance.colors.colLayer2) radius: Appearance.rounding.full implicitWidth: Math.max(20, elementWeight.implicitWidth) implicitHeight: Math.max(20, elementWeight.implicitHeight) width: height StyledText { id: elementWeight anchors.right: parent.right color: Appearance.colors.colOnLayer2 text: root.element.weight font.pixelSize: Appearance.font.pixelSize.smallest } } StyledText { id: elementSymbol anchors.centerIn: parent color: Appearance.colors.colSecondary font.pixelSize: Appearance.font.pixelSize.huge text: root.element.symbol } StyledText { id: elementName anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 4 } font.pixelSize: Appearance.font.pixelSize.smallest color: Appearance.colors.colOnLayer2 text: root.element.name } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/cheatsheet/periodic_table.js ================================================ // List of rows const elements = [ [ { name: 'Hydrogen', symbol: 'H', number: 1, weight: 1.01, type: 'nonmetal' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: 'Helium', symbol: 'He', number: 2, weight: 4.00, type: 'noblegas' }, ], [ { name: 'Lithium', symbol: 'Li', number: 3, weight: 6.94, type: 'metal' }, { name: 'Beryllium', symbol: 'Be', number: 4, weight: 9.01, type: 'metal' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: 'Boron', symbol: 'B', number: 5, weight: 10.81, type: 'nonmetal' }, { name: 'Carbon', symbol: 'C', number: 6, weight: 12.01, type: 'nonmetal' }, { name: 'Nitrogen', symbol: 'N', number: 7, weight: 14.01, type: 'nonmetal' }, { name: 'Oxygen', symbol: 'O', number: 8, weight: 16, type: 'nonmetal' }, { name: 'Fluorine', symbol: 'F', number: 9, weight: 19, type: 'nonmetal' }, { name: 'Neon', symbol: 'Ne', number: 10, weight: 20.18, type: 'noblegas' }, ], [ { name: 'Sodium', symbol: 'Na', number: 11, weight: 22.99, type: 'metal' }, { name: 'Magnesium', symbol: 'Mg', number: 12, weight: 24.31, type: 'metal' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: 'Aluminum', symbol: 'Al', number: 13, weight: 26.98, type: 'metal' }, { name: 'Silicon', symbol: 'Si', number: 14, weight: 28.09, type: 'nonmetal' }, { name: 'Phosphorus', symbol: 'P', number: 15, weight: 30.97, type: 'nonmetal' }, { name: 'Sulfur', symbol: 'S', number: 16, weight: 32.07, type: 'nonmetal' }, { name: 'Chlorine', symbol: 'Cl', number: 17, weight: 35.45, type: 'nonmetal' }, { name: 'Argon', symbol: 'Ar', number: 18, weight: 39.95, type: 'noblegas' }, ], [ { name: 'Potassium', symbol: 'K', number: 19, weight: 39.098, type: 'metal' }, { name: 'Calcium', symbol: 'Ca', number: 20, weight: 40.078, type: 'metal' }, { name: 'Scandium', symbol: 'Sc', number: 21, weight: 44.956, type: 'metal' }, { name: 'Titanium', symbol: 'Ti', number: 22, weight: 47.87, type: 'metal' }, { name: 'Vanadium', symbol: 'V', number: 23, weight: 50.94, type: 'metal' }, { name: 'Chromium', symbol: 'Cr', number: 24, weight: 52, type: 'metal'/*, icon: 'chromium-browser'*/ }, { name: 'Manganese', symbol: 'Mn', number: 25, weight: 54.94, type: 'metal' }, { name: 'Iron', symbol: 'Fe', number: 26, weight: 55.85, type: 'metal' }, { name: 'Cobalt', symbol: 'Co', number: 27, weight: 58.93, type: 'metal' }, { name: 'Nickel', symbol: 'Ni', number: 28, weight: 58.69, type: 'metal' }, { name: 'Copper', symbol: 'Cu', number: 29, weight: 63.55, type: 'metal' }, { name: 'Zinc', symbol: 'Zn', number: 30, weight: 65.38, type: 'metal' }, { name: 'Gallium', symbol: 'Ga', number: 31, weight: 69.72, type: 'metal' }, { name: 'Germanium', symbol: 'Ge', number: 32, weight: 72.63, type: 'metal' }, { name: 'Arsenic', symbol: 'As', number: 33, weight: 74.92, type: 'nonmetal' }, { name: 'Selenium', symbol: 'Se', number: 34, weight: 78.96, type: 'nonmetal' }, { name: 'Bromine', symbol: 'Br', number: 35, weight: 79.904, type: 'nonmetal' }, { name: 'Krypton', symbol: 'Kr', number: 36, weight: 83.8, type: 'noblegas' }, ], [ { name: 'Rubidium', symbol: 'Rb', number: 37, weight: 85.47, type: 'metal' }, { name: 'Strontium', symbol: 'Sr', number: 38, weight: 87.62, type: 'metal' }, { name: 'Yttrium', symbol: 'Y', number: 39, weight: 88.91, type: 'metal' }, { name: 'Zirconium', symbol: 'Zr', number: 40, weight: 91.22, type: 'metal' }, { name: 'Niobium', symbol: 'Nb', number: 41, weight: 92.91, type: 'metal' }, { name: 'Molybdenum', symbol: 'Mo', number: 42, weight: 95.94, type: 'metal' }, { name: 'Technetium', symbol: 'Tc', number: 43, weight: 98, type: 'metal' }, { name: 'Ruthenium', symbol: 'Ru', number: 44, weight: 101.07, type: 'metal' }, { name: 'Rhodium', symbol: 'Rh', number: 45, weight: 102.91, type: 'metal' }, { name: 'Palladium', symbol: 'Pd', number: 46, weight: 106.42, type: 'metal' }, { name: 'Silver', symbol: 'Ag', number: 47, weight: 107.87, type: 'metal' }, { name: 'Cadmium', symbol: 'Cd', number: 48, weight: 112.41, type: 'metal' }, { name: 'Indium', symbol: 'In', number: 49, weight: 114.82, type: 'metal' }, { name: 'Tin', symbol: 'Sn', number: 50, weight: 118.71, type: 'metal' }, { name: 'Antimony', symbol: 'Sb', number: 51, weight: 121.76, type: 'metal' }, { name: 'Tellurium', symbol: 'Te', number: 52, weight: 127.6, type: 'nonmetal' }, { name: 'Iodine', symbol: 'I', number: 53, weight: 126.9, type: 'nonmetal' }, { name: 'Xenon', symbol: 'Xe', number: 54, weight: 131.29, type: 'noblegas' }, ], [ { name: 'Cesium', symbol: 'Cs', number: 55, weight: 132.91, type: 'metal' }, { name: 'Barium', symbol: 'Ba', number: 56, weight: 137.33, type: 'metal' }, { name: 'Lanthanum', symbol: 'La', number: 57, weight: 138.91, type: 'lanthanum' }, { name: 'Hafnium', symbol: 'Hf', number: 72, weight: 178.49, type: 'metal' }, { name: 'Tantalum', symbol: 'Ta', number: 73, weight: 180.95, type: 'metal' }, { name: 'Tungsten', symbol: 'W', number: 74, weight: 183.84, type: 'metal' }, { name: 'Rhenium', symbol: 'Re', number: 75, weight: 186.21, type: 'metal' }, { name: 'Osmium', symbol: 'Os', number: 76, weight: 190.23, type: 'metal' }, { name: 'Iridium', symbol: 'Ir', number: 77, weight: 192.22, type: 'metal' }, { name: 'Platinum', symbol: 'Pt', number: 78, weight: 195.09, type: 'metal' }, { name: 'Gold', symbol: 'Au', number: 79, weight: 196.97, type: 'metal' }, { name: 'Mercury', symbol: 'Hg', number: 80, weight: 200.59, type: 'metal' }, { name: 'Thallium', symbol: 'Tl', number: 81, weight: 204.38, type: 'metal' }, { name: 'Lead', symbol: 'Pb', number: 82, weight: 207.2, type: 'metal' }, { name: 'Bismuth', symbol: 'Bi', number: 83, weight: 208.98, type: 'metal' }, { name: 'Polonium', symbol: 'Po', number: 84, weight: 209, type: 'metal' }, { name: 'Astatine', symbol: 'At', number: 85, weight: 210, type: 'nonmetal' }, { name: 'Radon', symbol: 'Rn', number: 86, weight: 222, type: 'noblegas' }, ], [ { name: 'Francium', symbol: 'Fr', number: 87, weight: 223, type: 'metal' }, { name: 'Radium', symbol: 'Ra', number: 88, weight: 226, type: 'metal' }, { name: 'Actinium', symbol: 'Ac', number: 89, weight: 227, type: 'actinium' }, { name: 'Rutherfordium', symbol: 'Rf', number: 104, weight: 267, type: 'metal' }, { name: 'Dubnium', symbol: 'Db', number: 105, weight: 268, type: 'metal' }, { name: 'Seaborgium', symbol: 'Sg', number: 106, weight: 271, type: 'metal' }, { name: 'Bohrium', symbol: 'Bh', number: 107, weight: 272, type: 'metal' }, { name: 'Hassium', symbol: 'Hs', number: 108, weight: 277, type: 'metal' }, { name: 'Meitnerium', symbol: 'Mt', number: 109, weight: 278, type: 'metal' }, { name: 'Darmstadtium', symbol: 'Ds', number: 110, weight: 281, type: 'metal' }, { name: 'Roentgenium', symbol: 'Rg', number: 111, weight: 280, type: 'metal' }, { name: 'Copernicium', symbol: 'Cn', number: 112, weight: 285, type: 'metal' }, { name: 'Nihonium', symbol: 'Nh', number: 113, weight: 286, type: 'metal' }, { name: 'Flerovium', symbol: 'Fl', number: 114, weight: 289, type: 'metal' }, { name: 'Moscovium', symbol: 'Mc', number: 115, weight: 290, type: 'metal' }, { name: 'Livermorium', symbol: 'Lv', number: 116, weight: 293, type: 'metal' }, { name: 'Tennessine', symbol: 'Ts', number: 117, weight: 294, type: 'metal' }, { name: 'Oganesson', symbol: 'Og', number: 118, weight: 294, type: 'noblegas' }, ], ] const series = [ [ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: 'Cerium', symbol: 'Ce', number: 58, weight: 140.12, type: 'lanthanum' }, { name: 'Praseodymium', symbol: 'Pr', number: 59, weight: 140.91, type: 'lanthanum' }, { name: 'Neodymium', symbol: 'Nd', number: 60, weight: 144.24, type: 'lanthanum' }, { name: 'Promethium', symbol: 'Pm', number: 61, weight: 145, type: 'lanthanum' }, { name: 'Samarium', symbol: 'Sm', number: 62, weight: 150.36, type: 'lanthanum' }, { name: 'Europium', symbol: 'Eu', number: 63, weight: 151.96, type: 'lanthanum' }, { name: 'Gadolinium', symbol: 'Gd', number: 64, weight: 157.25, type: 'lanthanum' }, { name: 'Terbium', symbol: 'Tb', number: 65, weight: 158.93, type: 'lanthanum' }, { name: 'Dysprosium', symbol: 'Dy', number: 66, weight: 162.5, type: 'lanthanum' }, { name: 'Holmium', symbol: 'Ho', number: 67, weight: 164.93, type: 'lanthanum' }, { name: 'Erbium', symbol: 'Er', number: 68, weight: 167.26, type: 'lanthanum' }, { name: 'Thulium', symbol: 'Tm', number: 69, weight: 168.93, type: 'lanthanum' }, { name: 'Ytterbium', symbol: 'Yb', number: 70, weight: 173.04, type: 'lanthanum' }, { name: 'Lutetium', symbol: 'Lu', number: 71, weight: 174.97, type: 'lanthanum' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, ], [ { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, { name: 'Thorium', symbol: 'Th', number: 90, weight: 232.04, type: 'actinium' }, { name: 'Protactinium', symbol: 'Pa', number: 91, weight: 231.04, type: 'actinium' }, { name: 'Uranium', symbol: 'U', number: 92, weight: 238.03, type: 'actinium' }, { name: 'Neptunium', symbol: 'Np', number: 93, weight: 237, type: 'actinium' }, { name: 'Plutonium', symbol: 'Pu', number: 94, weight: 244, type: 'actinium' }, { name: 'Americium', symbol: 'Am', number: 95, weight: 243, type: 'actinium' }, { name: 'Curium', symbol: 'Cm', number: 96, weight: 247, type: 'actinium' }, { name: 'Berkelium', symbol: 'Bk', number: 97, weight: 247, type: 'actinium' }, { name: 'Californium', symbol: 'Cf', number: 98, weight: 251, type: 'actinium' }, { name: 'Einsteinium', symbol: 'Es', number: 99, weight: 252, type: 'actinium' }, { name: 'Fermium', symbol: 'Fm', number: 100, weight: 257, type: 'actinium' }, { name: 'Mendelevium', symbol: 'Md', number: 101, weight: 258, type: 'actinium' }, { name: 'Nobelium', symbol: 'No', number: 102, weight: 259, type: 'actinium' }, { name: 'Lawrencium', symbol: 'Lr', number: 103, weight: 262, type: 'actinium' }, { name: '', symbol: '', number: -1, weight: 0, type: 'empty' }, ], ]; const niceTypes = { 'metal': "Metal", 'nonmetal': "Nonmetal", 'noblegas': "Noble gas", 'lanthanum': "Lanthanum", 'actinium': "Actinium" } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/dock/Dock.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Effects import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland Scope { // Scope id: root property bool pinned: Config.options?.dock.pinnedOnStartup ?? false Variants { // For each monitor model: Quickshell.screens PanelWindow { id: dockRoot // Window required property var modelData screen: modelData visible: !GlobalStates.screenLocked property bool reveal: root.pinned || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow || (!ToplevelManager.activeToplevel?.activated) anchors { bottom: true left: true right: true } exclusiveZone: root.pinned ? implicitHeight - (Appearance.sizes.hyprlandGapsOut) - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0 implicitWidth: dockBackground.implicitWidth WlrLayershell.namespace: "quickshell:dock" color: "transparent" implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut mask: Region { item: dockMouseArea } MouseArea { id: dockMouseArea height: parent.height anchors { top: parent.top topMargin: dockRoot.reveal ? 0 : Config.options?.dock.hoverToReveal ? (dockRoot.implicitHeight - Config.options.dock.hoverRegionHeight) : (dockRoot.implicitHeight + 1) horizontalCenter: parent.horizontalCenter } implicitWidth: dockHoverRegion.implicitWidth + Appearance.sizes.elevationMargin * 2 hoverEnabled: true Behavior on anchors.topMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Item { id: dockHoverRegion anchors.fill: parent implicitWidth: dockBackground.implicitWidth Item { // Wrapper for the dock background id: dockBackground anchors { top: parent.top bottom: parent.bottom horizontalCenter: parent.horizontalCenter } implicitWidth: dockRow.implicitWidth + 5 * 2 height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut StyledRectangularShadow { target: dockVisualBackground } Rectangle { // The real rectangle that is visible id: dockVisualBackground property real margin: Appearance.sizes.elevationMargin anchors.fill: parent anchors.topMargin: Appearance.sizes.elevationMargin anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut color: Appearance.colors.colLayer0 border.width: 1 border.color: Appearance.colors.colLayer0Border radius: Appearance.rounding.large } RowLayout { id: dockRow anchors.top: parent.top anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter spacing: 3 property real padding: 5 VerticalButtonGroup { Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work GroupButton { // Pin button baseWidth: 35 baseHeight: 35 clickedWidth: baseWidth clickedHeight: baseHeight + 20 buttonRadius: Appearance.rounding.normal toggled: root.pinned onClicked: root.pinned = !root.pinned contentItem: MaterialSymbol { text: "keep" horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 } } } DockSeparator {} DockApps { id: dockApps buttonPadding: dockRow.padding } DockSeparator {} DockButton { Layout.fillHeight: true onClicked: GlobalStates.overviewOpen = !GlobalStates.overviewOpen topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding contentItem: MaterialSymbol { anchors.fill: parent horizontalAlignment: Text.AlignHCenter font.pixelSize: parent.width / 2 text: "apps" color: Appearance.colors.colOnLayer0 } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/dock/DockAppButton.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets DockButton { id: root property var appToplevel property var appListRoot property int lastFocused: -1 property real iconSize: 35 property real countDotWidth: 10 property real countDotHeight: 4 property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined readonly property bool isSeparator: appToplevel.appId === "SEPARATOR" property var desktopEntry: DesktopEntries.heuristicLookup(appToplevel.appId) enabled: !isSeparator implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset Connections { target: DesktopEntries function onApplicationsChanged() { root.desktopEntry = DesktopEntries.heuristicLookup(appToplevel.appId); } } Loader { active: isSeparator anchors { fill: parent topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal } sourceComponent: DockSeparator {} } Loader { anchors.fill: parent active: appToplevel.toplevels.length > 0 sourceComponent: MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton onEntered: { appListRoot.lastHoveredButton = root appListRoot.buttonHovered = true lastFocused = appToplevel.toplevels.length - 1 } onExited: { if (appListRoot.lastHoveredButton === root) { appListRoot.buttonHovered = false } } } } onClicked: { if (appToplevel.toplevels.length === 0) { root.desktopEntry?.execute(); return; } lastFocused = (lastFocused + 1) % appToplevel.toplevels.length appToplevel.toplevels[lastFocused].activate() } middleClickAction: () => { root.desktopEntry?.execute(); } altAction: () => { TaskbarApps.togglePin(appToplevel.appId); } contentItem: Loader { active: !isSeparator sourceComponent: Item { anchors.centerIn: parent Loader { id: iconImageLoader anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } active: !root.isSeparator sourceComponent: IconImage { source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing") implicitSize: root.iconSize } } Loader { active: Config.options.dock.monochromeIcons anchors.fill: iconImageLoader sourceComponent: Item { Desaturate { id: desaturatedIcon visible: false // There's already color overlay anchors.fill: parent source: iconImageLoader desaturation: 0.8 } ColorOverlay { anchors.fill: desaturatedIcon source: desaturatedIcon color: ColorUtils.transparentize(Appearance.colors.colPrimary, 0.9) } } } RowLayout { spacing: 3 anchors { top: iconImageLoader.bottom topMargin: 2 horizontalCenter: parent.horizontalCenter } Repeater { model: Math.min(appToplevel.toplevels.length, 3) delegate: Rectangle { required property int index radius: Appearance.rounding.full implicitWidth: (appToplevel.toplevels.length <= 3) ? root.countDotWidth : root.countDotHeight // Circles when too many implicitHeight: root.countDotHeight color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4) } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/dock/DockApps.qml ================================================ pragma ComponentBehavior: Bound import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets import Quickshell.Wayland import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions Item { id: root property real maxWindowPreviewHeight: 200 property real maxWindowPreviewWidth: 300 property real windowControlsHeight: 30 property real buttonPadding: 5 property Item lastHoveredButton: null property bool buttonHovered: false property bool requestDockShow: previewPopup.show Layout.fillHeight: true Layout.topMargin: Appearance.sizes.hyprlandGapsOut implicitWidth: listView.implicitWidth function popupCenterXForButton(button) { if (!button || !root.QsWindow) return 0; return root.QsWindow.mapFromItem(button, button.width / 2, 0).x; } StyledListView { id: listView spacing: 2 orientation: ListView.Horizontal anchors { top: parent.top bottom: parent.bottom } implicitWidth: contentWidth Behavior on implicitWidth { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } model: ScriptModel { objectProp: "appId" values: TaskbarApps.apps } delegate: DockAppButton { required property var modelData appToplevel: modelData appListRoot: root topInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding bottomInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding } } PopupWindow { id: previewPopup property var appTopLevel: root.lastHoveredButton?.appToplevel property bool shouldShow: (popupMouseArea.containsMouse || root.buttonHovered) && appTopLevel && appTopLevel.toplevels && appTopLevel.toplevels.length > 0 property bool show: false property real cachedCenterX: 0 Connections { target: root function onLastHoveredButtonChanged() { if (root.lastHoveredButton && root.QsWindow) previewPopup.cachedCenterX = root.popupCenterXForButton(root.lastHoveredButton); } function onButtonHoveredChanged() { if (root.buttonHovered && root.lastHoveredButton && root.QsWindow) previewPopup.cachedCenterX = root.popupCenterXForButton(root.lastHoveredButton); updateTimer.restart(); } } onShouldShowChanged: { updateTimer.restart(); } Timer { id: updateTimer interval: 100 onTriggered: { previewPopup.show = previewPopup.shouldShow; } } anchor { window: root.QsWindow.window adjustment: PopupAdjustment.None gravity: Edges.Top | Edges.Right edges: Edges.Top | Edges.Left } visible: popupBackground.opacity > 0 color: "transparent" implicitWidth: root.QsWindow.window?.width ?? 1 implicitHeight: popupMouseArea.implicitHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 MouseArea { id: popupMouseArea anchors.bottom: parent.bottom implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2 hoverEnabled: true x: previewPopup.cachedCenterX - width / 2 StyledRectangularShadow { target: popupBackground opacity: previewPopup.show ? 1 : 0 visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Rectangle { id: popupBackground property real padding: 5 opacity: previewPopup.show ? 1 : 0 visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } clip: true color: Appearance.m3colors.m3surfaceContainer radius: Appearance.rounding.normal anchors.bottom: parent.bottom anchors.bottomMargin: Appearance.sizes.elevationMargin anchors.horizontalCenter: parent.horizontalCenter implicitHeight: previewRowLayout.implicitHeight + padding * 2 implicitWidth: previewRowLayout.implicitWidth + padding * 2 Behavior on implicitWidth { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on implicitHeight { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } RowLayout { id: previewRowLayout anchors.centerIn: parent Repeater { model: ScriptModel { values: previewPopup.appTopLevel?.toplevels ?? [] } RippleButton { id: windowButton Layout.fillHeight: true required property var modelData padding: 0 middleClickAction: () => { windowButton.modelData?.close(); } onClicked: { windowButton.modelData?.activate(); } contentItem: ColumnLayout { implicitWidth: screencopyView.implicitWidth implicitHeight: screencopyView.implicitHeight ButtonGroup { contentWidth: parent.width - anchors.margins * 2 StyledText { Layout.margins: 5 Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small text: windowButton.modelData?.title elide: Text.ElideRight color: Appearance.m3colors.m3onSurface } GroupButton { id: closeButton colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) baseWidth: root.windowControlsHeight baseHeight: root.windowControlsHeight buttonRadius: Appearance.rounding.full contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: "close" iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSurface } onClicked: { windowButton.modelData?.close(); } } } Item { Layout.fillWidth: true Layout.fillHeight: true implicitHeight: screencopyView.height implicitWidth: screencopyView.width ScreencopyView { id: screencopyView anchors.centerIn: parent captureSource: windowButton.modelData live: true paintCursor: true constraintSize: Qt.size(root.maxWindowPreviewWidth, root.maxWindowPreviewHeight) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: screencopyView.width height: screencopyView.height radius: Appearance.rounding.small } } } } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/dock/DockButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts RippleButton { Layout.fillHeight: true Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut implicitWidth: implicitHeight - topInset - bottomInset buttonRadius: Appearance.rounding.normal background.implicitHeight: 50 } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/dock/DockSeparator.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Layouts Rectangle { Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/lock/Lock.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.panels.lock import QtQuick import Quickshell import Quickshell.Hyprland LockScreen { id: root // Monitor name -> workspace id to restore on unlock (set when locking) property var savedWorkspaces: ({}) Timer { id: restoreTimer interval: 150 repeat: false onTriggered: { var batch = "" for (var j = 0; j < Quickshell.screens.length; ++j) { var monName = Quickshell.screens[j].name var wsId = root.savedWorkspaces[monName] if (wsId !== undefined) { batch += `hyprctl dispatch 'hl.dsp.focus({monitor="${monName}"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${wsId}})';` } } if (batch.length > 0) { Quickshell.execDetached(["bash", "-c", batch]) } } } lockSurface: LockSurface { context: root.context } // Single batch for lock and unlock so we don't race multiple hyprctl calls Connections { target: GlobalStates function onScreenLockedChanged() { if (GlobalStates.screenLocked) { // Lock: save workspace per monitor and move all to temp workspace in one batch var next = {} var batch = "keyword animation workspaces,1,7,menu_decel,slidevert; " for (var i = 0; i < Quickshell.screens.length; ++i) { var mon = Quickshell.screens[i].name var mData = HyprlandData.monitors.find(m => m.name === mon) if (mData?.activeWorkspace == undefined) { return; } var ws = (mData?.activeWorkspace?.id ?? 1) next[mon] = ws batch += `hyprctl dispatch 'hl.dsp.focus({monitor="${mon}"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${2147483647 - ws}})';` } root.savedWorkspaces = next Quickshell.execDetached(["bash", "-c", batch]) } else { restoreTimer.start() } } } // Push everything down (visual only; workspace switch is in Connections above) Variants { model: Quickshell.screens delegate: Scope { required property ShellScreen modelData property bool shouldPush: GlobalStates.screenLocked property string targetMonitorName: modelData.name property int verticalMovementDistance: modelData.height property int horizontalSqueeze: modelData.width * 0.2 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml ================================================ import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Services.UPower import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.common.panels.lock import qs.modules.ii.bar as Bar import Quickshell import Quickshell.Services.SystemTray MouseArea { id: root required property LockContext context property bool active: false property bool showInputField: active || context.currentText.length > 0 readonly property bool requirePasswordToPower: Config.options.lock.security.requirePasswordToPower // Force focus on entry function forceFieldFocus() { passwordBox.forceActiveFocus(); } Connections { target: context function onShouldReFocus() { forceFieldFocus(); } } hoverEnabled: true acceptedButtons: Qt.LeftButton onPressed: mouse => { forceFieldFocus(); } onPositionChanged: mouse => { forceFieldFocus(); } // Toolbar appearing animation property real toolbarScale: 0.9 property real toolbarOpacity: 0 Behavior on toolbarScale { NumberAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } } Behavior on toolbarOpacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } // Init Component.onCompleted: { forceFieldFocus(); toolbarScale = 1; toolbarOpacity = 1; } // Key presses property bool ctrlHeld: false Keys.onPressed: event => { root.context.resetClearTimer(); if (event.key === Qt.Key_Control) { root.ctrlHeld = true; } if (event.key === Qt.Key_Escape) { // Esc to clear root.context.currentText = ""; } forceFieldFocus(); } Keys.onReleased: event => { if (event.key === Qt.Key_Control) { root.ctrlHeld = false; } forceFieldFocus(); } // RippleButton { // anchors { // top: parent.top // left: parent.left // leftMargin: 10 // topMargin: 10 // } // implicitHeight: 40 // colBackground: Appearance.colors.colLayer2 // onClicked: { // context.unlocked(LockContext.ActionEnum.Unlock); // GlobalStates.screenLocked = false; // } // contentItem: StyledText { // text: "[[ DEBUG BYPASS ]]" // } // } // Main toolbar: password box Toolbar { id: mainIsland anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 20 } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } scale: root.toolbarScale opacity: root.toolbarOpacity // Fingerprint Loader { Layout.leftMargin: 10 Layout.rightMargin: 6 Layout.alignment: Qt.AlignVCenter active: root.context.fingerprintsConfigured visible: active sourceComponent: MaterialSymbol { id: fingerprintIcon fill: 1 text: "fingerprint" iconSize: Appearance.font.pixelSize.hugeass color: Appearance.colors.colOnSurfaceVariant } } ToolbarTextField { id: passwordBox Layout.rightMargin: -Layout.leftMargin placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password") // Style clip: true font.pixelSize: Appearance.font.pixelSize.small selectedTextColor: materialShapeChars ? "transparent" : Appearance.colors.colOnSecondaryContainer selectionColor: materialShapeChars ? "transparent" : Appearance.colors.colSecondaryContainer // Password enabled: !root.context.unlockInProgress echoMode: TextInput.Password inputMethodHints: Qt.ImhSensitiveData // Synchronizing (across monitors) and unlocking onTextChanged: root.context.currentText = this.text onAccepted: { root.context.tryUnlock(ctrlHeld); } Connections { target: root.context function onCurrentTextChanged() { passwordBox.text = root.context.currentText; } } Keys.onPressed: event => { root.context.resetClearTimer(); } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: passwordBox.width - 8 height: passwordBox.height radius: height / 2 } } // Shake when wrong password ErrorShakeAnimation { id: wrongPasswordShakeAnim target: passwordBox } Connections { target: GlobalStates function onScreenUnlockFailedChanged() { if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart(); } } // We're drawing dots manually property bool materialShapeChars: Config.options.lock.materialShapeChars color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0) Loader { active: passwordBox.materialShapeChars anchors { fill: parent leftMargin: passwordBox.padding rightMargin: passwordBox.padding } sourceComponent: PasswordChars { length: root.context.currentText.length selectionStart: passwordBox.selectionStart selectionEnd: passwordBox.selectionEnd cursorPosition: passwordBox.cursorPosition } } } ToolbarButton { id: confirmButton implicitWidth: height toggled: true enabled: !root.context.unlockInProgress colBackgroundToggled: Appearance.colors.colPrimary onClicked: root.context.tryUnlock() contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter iconSize: 24 text: { if (root.context.targetAction === LockContext.ActionEnum.Unlock) { return root.ctrlHeld ? "coffee" : "arrow_right_alt"; } else if (root.context.targetAction === LockContext.ActionEnum.Poweroff) { return "power_settings_new"; } else if (root.context.targetAction === LockContext.ActionEnum.Reboot) { return "restart_alt"; } } color: confirmButton.enabled ? Appearance.colors.colOnPrimary : Appearance.colors.colSubtext } } } // Left toolbar Toolbar { id: leftIsland anchors { right: mainIsland.left top: mainIsland.top bottom: mainIsland.bottom rightMargin: 10 } scale: root.toolbarScale opacity: root.toolbarOpacity // Username IconAndTextPair { Layout.leftMargin: 8 icon: "account_circle" text: SystemInfo.username } // Keyboard layout (Xkb) Loader { Layout.rightMargin: 8 Layout.fillHeight: true active: true visible: active sourceComponent: Row { spacing: 8 MaterialSymbol { id: keyboardIcon anchors.verticalCenter: parent.verticalCenter fill: 1 text: "keyboard_alt" iconSize: Appearance.font.pixelSize.huge color: Appearance.colors.colOnSurfaceVariant } Loader { anchors.verticalCenter: parent.verticalCenter sourceComponent: StyledText { text: HyprlandXkb.currentLayoutCode color: Appearance.colors.colOnSurfaceVariant animateChange: true } } } } // Keyboard layout (Fcitx) Bar.SysTray { Layout.rightMargin: 10 Layout.alignment: Qt.AlignVCenter showSeparator: false showOverflowMenu: false pinnedItems: SystemTray.items.values.filter(i => i.id == "Fcitx") visible: pinnedItems.length > 0 } } // Right toolbar Toolbar { id: rightIsland anchors { left: mainIsland.right top: mainIsland.top bottom: mainIsland.bottom leftMargin: 10 } scale: root.toolbarScale opacity: root.toolbarOpacity IconAndTextPair { visible: Battery.available icon: Battery.isCharging ? "bolt" : "battery_android_full" text: Math.round(Battery.percentage * 100) color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant } IconToolbarButton { id: sleepButton onClicked: Session.suspend() text: "dark_mode" } PasswordGuardedIconToolbarButton { id: powerButton text: "power_settings_new" targetAction: LockContext.ActionEnum.Poweroff } PasswordGuardedIconToolbarButton { id: rebootButton text: "restart_alt" targetAction: LockContext.ActionEnum.Reboot } } component PasswordGuardedIconToolbarButton: IconToolbarButton { id: guardedBtn required property var targetAction toggled: root.context.targetAction === guardedBtn.targetAction onClicked: { if (!root.requirePasswordToPower) { root.context.unlocked(guardedBtn.targetAction); return; } if (root.context.targetAction === guardedBtn.targetAction) { root.context.resetTargetAction(); } else { root.context.targetAction = guardedBtn.targetAction; root.context.shouldReFocus(); } } } component IconAndTextPair: Row { id: pair required property string icon required property string text property color color: Appearance.colors.colOnSurfaceVariant spacing: 4 Layout.fillHeight: true Layout.leftMargin: 10 Layout.rightMargin: 10 MaterialSymbol { anchors.verticalCenter: parent.verticalCenter fill: 1 text: pair.icon iconSize: Appearance.font.pixelSize.huge animateChange: true color: pair.color } StyledText { anchors.verticalCenter: parent.verticalCenter text: pair.text color: pair.color } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/lock/PasswordChars.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import Quickshell StyledFlickable { id: root required property int length property int selectionStart property int selectionEnd property int cursorPosition property color color: Appearance.colors.colPrimary property color selectedTextColor: Appearance.colors.colOnSecondaryContainer property color selectionColor: Appearance.colors.colSecondaryContainer property int charSize: 20 contentWidth: dotsRow.implicitWidth contentX: (Math.max(contentWidth - width, 0)) Behavior on contentX { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Rectangle { id: cursor anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: root.charSize * root.cursorPosition } color: root.color implicitWidth: 2 implicitHeight: root.charSize Behavior on anchors.leftMargin { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(cursor) } } Row { id: dotsRow anchors { left: parent.left verticalCenter: parent.verticalCenter leftMargin: 4 - 5 // -5 to account for spacing being simulated by char item width } spacing: 0 Repeater { model: ScriptModel { // TODO: use proper custom object model to insert new char at the correct pos values: Array(root.length) } delegate: Rectangle { id: charItem required property int index implicitWidth: root.charSize implicitHeight: root.charSize property bool selected: index >= root.selectionStart && index < root.selectionEnd color: ColorUtils.transparentize(root.selectionColor, selected ? 0 : 1) MaterialShape { id: materialShape anchors.centerIn: parent property list charShapes: [ MaterialShape.Shape.Clover4Leaf, MaterialShape.Shape.Arrow, MaterialShape.Shape.Pill, MaterialShape.Shape.SoftBurst, MaterialShape.Shape.Diamond, MaterialShape.Shape.ClamShell, MaterialShape.Shape.Pentagon, ] shape: charShapes[charItem.index % charShapes.length] // Animate on appearance color: charItem.selected ? root.selectedTextColor : root.color implicitSize: 0 opacity: 0 scale: 0.5 Component.onCompleted: { appearAnim.start(); } ParallelAnimation { id: appearAnim NumberAnimation { target: materialShape properties: "opacity" to: 1 duration: 50 easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } NumberAnimation { target: materialShape properties: "scale" to: 1 duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } NumberAnimation { target: materialShape properties: "implicitSize" to: 18 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } ColorAnimation { target: materialShape properties: "color" from: Appearance.colors.colPrimary to: Appearance.colors.colOnLayer1 duration: 1000 easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property bool visible: false readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property var realPlayers: MprisController.players readonly property var meaningfulPlayers: filterDuplicatePlayers(realPlayers) readonly property real osdWidth: Appearance.sizes.osdWidth readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 property list visualizerPoints: [] function filterDuplicatePlayers(players) { let filtered = []; let used = new Set(); for (let i = 0; i < players.length; ++i) { if (used.has(i)) continue; let p1 = players[i]; let group = [i]; // Find duplicates by trackTitle prefix for (let j = i + 1; j < players.length; ++j) { let p2 = players[j]; if (p1.trackTitle && p2.trackTitle && (p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle)) || (p1.position - p2.position <= 2 && p1.length - p2.length <= 2)) { group.push(j); } } // Pick the one with non-empty trackArtUrl, or fallback to the first let chosenIdx = group.find(idx => players[idx].trackArtUrl && players[idx].trackArtUrl.length > 0); if (chosenIdx === undefined) chosenIdx = group[0]; filtered.push(players[chosenIdx]); group.forEach(idx => used.add(idx)); } return filtered; } Process { id: cavaProc running: mediaControlsLoader.active onRunningChanged: { if (!cavaProc.running) { root.visualizerPoints = []; } } command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.scriptPath)}/cava/raw_output_config.txt`] stdout: SplitParser { onRead: data => { // Parse `;`-separated values into the visualizerPoints array let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p)); root.visualizerPoints = points; } } } Loader { id: mediaControlsLoader active: GlobalStates.mediaControlsOpen onActiveChanged: { if (!mediaControlsLoader.active && root.realPlayers.length === 0) { GlobalStates.mediaControlsOpen = false; } } sourceComponent: PanelWindow { id: panelWindow visible: true exclusionMode: ExclusionMode.Ignore exclusiveZone: 0 implicitWidth: root.widgetWidth implicitHeight: playerColumnLayout.implicitHeight color: "transparent" WlrLayershell.namespace: "quickshell:mediaControls" anchors { top: !Config.options.bar.bottom || Config.options.bar.vertical bottom: Config.options.bar.bottom && !Config.options.bar.vertical left: !(Config.options.bar.vertical && Config.options.bar.bottom) right: Config.options.bar.vertical && Config.options.bar.bottom } margins { top: Config.options.bar.vertical ? ((panelWindow.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight bottom: Appearance.sizes.barHeight left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((panelWindow.screen.width / 2) - (osdWidth / 2) - widgetWidth) right: Appearance.sizes.barHeight } mask: Region { item: playerColumnLayout } Component.onCompleted: { GlobalFocusGrab.addDismissable(panelWindow); } Component.onDestruction: { GlobalFocusGrab.removeDismissable(panelWindow); } Connections { target: GlobalFocusGrab function onDismissed() { GlobalStates.mediaControlsOpen = false; } } ColumnLayout { id: playerColumnLayout anchors.fill: parent spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay Repeater { model: ScriptModel { values: root.meaningfulPlayers } delegate: PlayerControl { required property MprisPlayer modelData player: modelData visualizerPoints: root.visualizerPoints implicitWidth: root.widgetWidth implicitHeight: root.widgetHeight radius: root.popupRounding } } Item { // No player placeholder Layout.alignment: { if (panelWindow.anchors.left) return Qt.AlignLeft; if (panelWindow.anchors.right) return Qt.AlignRight; return Qt.AlignHCenter; } Layout.leftMargin: Appearance.sizes.hyprlandGapsOut Layout.rightMargin: Appearance.sizes.hyprlandGapsOut visible: root.meaningfulPlayers.length === 0 implicitWidth: placeholderBackground.implicitWidth + Appearance.sizes.elevationMargin implicitHeight: placeholderBackground.implicitHeight + Appearance.sizes.elevationMargin StyledRectangularShadow { target: placeholderBackground } Rectangle { id: placeholderBackground anchors.centerIn: parent color: Appearance.colors.colLayer0 radius: root.popupRounding property real padding: 20 implicitWidth: placeholderLayout.implicitWidth + padding * 2 implicitHeight: placeholderLayout.implicitHeight + padding * 2 ColumnLayout { id: placeholderLayout anchors.centerIn: parent StyledText { text: Translation.tr("No active player") font.pixelSize: Appearance.font.pixelSize.large } StyledText { color: Appearance.colors.colSubtext text: Translation.tr("Make sure your player has MPRIS support\nor try turning off duplicate player filtering") font.pixelSize: Appearance.font.pixelSize.small } } } } } } } IpcHandler { target: "mediaControls" function toggle(): void { mediaControlsLoader.active = !mediaControlsLoader.active; if (mediaControlsLoader.active) Notifications.timeoutAll(); } function close(): void { mediaControlsLoader.active = false; } function open(): void { mediaControlsLoader.active = true; Notifications.timeoutAll(); } } GlobalShortcut { name: "mediaControlsToggle" description: "Toggles media controls on press" onPressed: { GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen; } } GlobalShortcut { name: "mediaControlsOpen" description: "Opens media controls on press" onPressed: { GlobalStates.mediaControlsOpen = true; } } GlobalShortcut { name: "mediaControlsClose" description: "Closes media controls on press" onPressed: { GlobalStates.mediaControlsOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/mediaControls/PlayerControl.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.models import qs.modules.common.widgets import qs.services import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Services.Mpris Item { // Player instance id: root required property MprisPlayer player property var artUrl: player?.trackArtUrl property string artDownloadLocation: Directories.coverArt property string artFileName: Qt.md5(artUrl) property string artFilePath: `${artDownloadLocation}/${artFileName}` property color artDominantColor: ColorUtils.mix((colorQuantizer?.colors[0] ?? Appearance.colors.colPrimary), Appearance.colors.colPrimaryContainer, 0.8) || Appearance.m3colors.m3secondaryContainer property bool downloaded: false property list visualizerPoints: [] property real maxVisualizerValue: 1000 // Max value in the data points property int visualizerSmoothing: 2 // Number of points to average for smoothing property real radius property string displayedArtFilePath: root.downloaded ? Qt.resolvedUrl(artFilePath) : "" component TrackChangeButton: RippleButton { implicitWidth: 24 implicitHeight: 24 property var iconName colBackground: ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1) colBackgroundHover: blendedColors.colSecondaryContainerHover colRipple: blendedColors.colSecondaryContainerActive contentItem: MaterialSymbol { iconSize: Appearance.font.pixelSize.huge fill: 1 horizontalAlignment: Text.AlignHCenter color: blendedColors.colOnSecondaryContainer text: iconName Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } Timer { // Force update for revision running: root.player?.playbackState == MprisPlaybackState.Playing interval: Config.options.resources.updateInterval repeat: true onTriggered: { root.player.positionChanged() } } onArtFilePathChanged: { if (root.artUrl.length == 0) { root.artDominantColor = Appearance.m3colors.m3secondaryContainer return; } // Binding does not work in Process coverArtDownloader.targetFile = root.artUrl coverArtDownloader.artFilePath = root.artFilePath // Download root.downloaded = false coverArtDownloader.running = true } Process { // Cover art downloader id: coverArtDownloader property string targetFile: root.artUrl property string artFilePath: root.artFilePath command: [ "bash", "-c", `[ -f ${artFilePath} ] || curl -4 -sSL '${targetFile}' -o '${artFilePath}'` ] onExited: (exitCode, exitStatus) => { root.downloaded = true } } ColorQuantizer { id: colorQuantizer source: root.displayedArtFilePath depth: 0 // 2^0 = 1 color rescaleSize: 1 // Rescale to 1x1 pixel for faster processing } property QtObject blendedColors: AdaptedMaterialScheme { color: artDominantColor } StyledRectangularShadow { target: background } Rectangle { // Background id: background anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin color: ColorUtils.applyAlpha(blendedColors.colLayer0, 1) radius: root.radius layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: background.width height: background.height radius: background.radius } } StyledImage { id: blurredArt anchors.fill: parent source: root.displayedArtFilePath fillMode: Image.PreserveAspectCrop cache: false antialiasing: true asynchronous: true layer.enabled: true layer.effect: StyledBlurEffect { source: blurredArt } Rectangle { anchors.fill: parent color: ColorUtils.transparentize(blendedColors.colLayer0, 0.3) radius: root.radius } } WaveVisualizer { id: visualizerCanvas anchors.fill: parent live: root.player?.isPlaying points: root.visualizerPoints maxVisualizerValue: root.maxVisualizerValue smoothing: root.visualizerSmoothing color: blendedColors.colPrimary } RowLayout { anchors.fill: parent anchors.margins: 13 spacing: 15 Rectangle { // Art background id: artBackground Layout.fillHeight: true implicitWidth: height radius: Appearance.rounding.verysmall color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5) layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: artBackground.width height: artBackground.height radius: artBackground.radius } } StyledImage { // Art image id: mediaArt property int size: parent.height anchors.fill: parent source: root.displayedArtFilePath fillMode: Image.PreserveAspectCrop cache: false antialiasing: true width: size height: size } } ColumnLayout { // Info & controls Layout.fillHeight: true spacing: 2 StyledText { id: trackTitle Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.large color: blendedColors.colOnLayer0 elide: Text.ElideRight text: StringUtils.cleanMusicTitle(root.player?.trackTitle) || "Untitled" animateChange: true animationDistanceX: 6 animationDistanceY: 0 } StyledText { id: trackArtist Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color: blendedColors.colSubtext elide: Text.ElideRight text: root.player?.trackArtist animateChange: true animationDistanceX: 6 animationDistanceY: 0 } Item { Layout.fillHeight: true } Item { Layout.fillWidth: true implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight StyledText { id: trackTime anchors.bottom: sliderRow.top anchors.bottomMargin: 5 anchors.left: parent.left font.pixelSize: Appearance.font.pixelSize.small color: blendedColors.colSubtext elide: Text.ElideRight text: `${StringUtils.friendlyTimeForSeconds(root.player?.position)} / ${StringUtils.friendlyTimeForSeconds(root.player?.length)}` } RowLayout { id: sliderRow anchors { bottom: parent.bottom left: parent.left right: parent.right } TrackChangeButton { iconName: "skip_previous" downAction: () => root.player?.previous() } Item { id: progressBarContainer Layout.fillWidth: true implicitHeight: Math.max(sliderLoader.implicitHeight, progressBarLoader.implicitHeight) Loader { id: sliderLoader anchors.fill: parent active: root.player?.canSeek ?? false sourceComponent: StyledSlider { configuration: StyledSlider.Configuration.Wavy highlightColor: blendedColors.colPrimary trackColor: blendedColors.colSecondaryContainer handleColor: blendedColors.colPrimary value: root.player?.position / root.player?.length onMoved: { root.player.position = value * root.player.length; } } } Loader { id: progressBarLoader anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right } active: !(root.player?.canSeek ?? false) sourceComponent: StyledProgressBar { wavy: root.player?.isPlaying highlightColor: blendedColors.colPrimary trackColor: blendedColors.colSecondaryContainer value: root.player?.position / root.player?.length } } } TrackChangeButton { iconName: "skip_next" downAction: () => root.player?.next() } } RippleButton { id: playPauseButton anchors.right: parent.right anchors.bottom: sliderRow.top anchors.bottomMargin: 5 property real size: 44 implicitWidth: size implicitHeight: size downAction: () => root.player.togglePlaying(); buttonRadius: root.player?.isPlaying ? Appearance?.rounding.normal : size / 2 colBackground: root.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer colBackgroundHover: root.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover colRipple: root.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive contentItem: MaterialSymbol { iconSize: Appearance.font.pixelSize.huge fill: 1 horizontalAlignment: Text.AlignHCenter color: root.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer text: root.player?.isPlaying ? "pause" : "play_arrow" Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/notificationPopup/NotificationPopup.qml ================================================ import qs import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { id: notificationPopup PanelWindow { id: root visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked screen: Quickshell.screens.find(s => Config.options.notifications.forceMonitor.enable ? s.name === Config.options.notifications.forceMonitor.name : s.name === Hyprland.focusedMonitor?.name) ?? null WlrLayershell.namespace: "quickshell:notificationPopup" WlrLayershell.layer: WlrLayer.Overlay exclusiveZone: 0 anchors { top: true right: true bottom: true } mask: Region { item: listview.contentItem } color: "transparent" implicitWidth: Appearance.sizes.notificationPopupWidth NotificationListView { id: listview anchors { top: parent.top bottom: parent.bottom right: parent.right rightMargin: 4 topMargin: 4 } implicitWidth: parent.width - Appearance.sizes.elevationMargin * 2 popup: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenDisplay/OnScreenDisplay.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property string protectionMessage: "" property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) property string currentIndicator: "volume" property var indicators: [ { id: "volume", sourceUrl: "indicators/VolumeIndicator.qml" }, { id: "brightness", sourceUrl: "indicators/BrightnessIndicator.qml" }, { id: "gamma", sourceUrl: "indicators/GammaIndicator.qml" }, ] function triggerOsd() { GlobalStates.osdVolumeOpen = true; osdTimeout.restart(); } Timer { id: osdTimeout interval: Config.options.osd.timeout repeat: false running: false onTriggered: { GlobalStates.osdVolumeOpen = false; root.protectionMessage = ""; } } Connections { target: Brightness function onBrightnessChanged() { root.protectionMessage = ""; root.currentIndicator = "brightness"; root.triggerOsd(); } } Connections { target: Hyprsunset function onGammaChangeAttempt() { root.protectionMessage = ""; root.currentIndicator = "gamma"; root.triggerOsd(); } } Connections { // Listen to volume changes target: Audio.sink?.audio ?? null function onVolumeChanged() { if (!Audio.ready) return; root.currentIndicator = "volume"; root.triggerOsd(); } function onMutedChanged() { if (!Audio.ready) return; root.currentIndicator = "volume"; root.triggerOsd(); } } Connections { // Listen to protection triggers target: Audio function onSinkProtectionTriggered(reason) { root.protectionMessage = reason; root.currentIndicator = "volume"; root.triggerOsd(); } } Loader { id: osdLoader active: GlobalStates.osdVolumeOpen sourceComponent: PanelWindow { id: osdRoot color: "transparent" Connections { target: root function onFocusedScreenChanged() { osdRoot.screen = root.focusedScreen; } } WlrLayershell.namespace: "quickshell:onScreenDisplay" WlrLayershell.layer: WlrLayer.Overlay anchors { top: !Config.options.bar.bottom bottom: Config.options.bar.bottom } mask: Region { item: osdValuesWrapper } exclusionMode: ExclusionMode.Ignore exclusiveZone: 0 margins { top: Appearance.sizes.barHeight bottom: Appearance.sizes.barHeight } implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight visible: osdLoader.active ColumnLayout { id: columnLayout anchors.horizontalCenter: parent.horizontalCenter Item { id: osdValuesWrapper // Extra space for shadow implicitHeight: contentColumnLayout.implicitHeight implicitWidth: contentColumnLayout.implicitWidth clip: true MouseArea { anchors.fill: parent hoverEnabled: true onEntered: GlobalStates.osdVolumeOpen = false } Column { id: contentColumnLayout anchors { top: parent.top left: parent.left right: parent.right } spacing: 0 Loader { id: osdIndicatorLoader source: root.indicators.find(i => i.id === root.currentIndicator)?.sourceUrl } Item { id: protectionMessageWrapper anchors.horizontalCenter: parent.horizontalCenter implicitHeight: protectionMessageBackground.implicitHeight implicitWidth: protectionMessageBackground.implicitWidth opacity: root.protectionMessage !== "" ? 1 : 0 StyledRectangularShadow { target: protectionMessageBackground } Rectangle { id: protectionMessageBackground anchors.centerIn: parent color: Appearance.m3colors.m3error property real padding: 10 implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2 implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2 radius: Appearance.rounding.normal RowLayout { id: protectionMessageRowLayout anchors.centerIn: parent MaterialSymbol { id: protectionMessageIcon text: "dangerous" iconSize: Appearance.font.pixelSize.hugeass color: Appearance.m3colors.m3onError } StyledText { id: protectionMessageTextWidget horizontalAlignment: Text.AlignHCenter color: Appearance.m3colors.m3onError wrapMode: Text.Wrap text: root.protectionMessage } } } } } } } } } IpcHandler { target: "osdVolume" function trigger() { root.triggerOsd(); } function hide() { GlobalStates.osdVolumeOpen = false; } function toggle() { GlobalStates.osdVolumeOpen = !GlobalStates.osdVolumeOpen; } } GlobalShortcut { name: "osdVolumeTrigger" description: "Triggers volume OSD on press" onPressed: { root.triggerOsd(); } } GlobalShortcut { name: "osdVolumeHide" description: "Hides volume OSD on press" onPressed: { GlobalStates.osdVolumeOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenDisplay/OsdValueIndicator.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell.Widgets Item { id: root required property real value required property string icon required property string name property bool rotateIcon: false property bool scaleIcon: false property alias from: valueProgressBar.from property alias to: valueProgressBar.to property real valueIndicatorVerticalPadding: 9 property real valueIndicatorLeftPadding: 10 property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding implicitWidth: Appearance.sizes.osdWidth + 2 * Appearance.sizes.elevationMargin implicitHeight: valueIndicator.implicitHeight + 2 * Appearance.sizes.elevationMargin StyledRectangularShadow { target: valueIndicator } Rectangle { id: valueIndicator anchors { fill: parent margins: Appearance.sizes.elevationMargin } radius: Appearance.rounding.full color: Appearance.colors.colLayer0 implicitWidth: valueRow.implicitWidth implicitHeight: valueRow.implicitHeight RowLayout { // Icon on the left, stuff on the right id: valueRow Layout.margins: 10 anchors.fill: parent spacing: 10 Item { implicitWidth: 30 implicitHeight: 30 Layout.alignment: Qt.AlignVCenter Layout.leftMargin: valueIndicatorLeftPadding Layout.topMargin: valueIndicatorVerticalPadding Layout.bottomMargin: valueIndicatorVerticalPadding MaterialSymbol { // Icon anchors { centerIn: parent alignWhenCentered: !root.rotateIcon } color: Appearance.colors.colOnLayer0 renderType: Text.QtRendering text: root.icon iconSize: 20 + 10 * (root.scaleIcon ? value : 1) rotation: 180 * (root.rotateIcon ? value : 0) Behavior on iconSize { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on rotation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } } ColumnLayout { // Stuff Layout.alignment: Qt.AlignVCenter Layout.rightMargin: valueIndicatorRightPadding spacing: 5 RowLayout { // Name fill left, value on the right end Layout.leftMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end Layout.rightMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end StyledText { color: Appearance.colors.colOnLayer0 font.pixelSize: Appearance.font.pixelSize.small Layout.fillWidth: true text: root.name } StyledText { color: Appearance.colors.colOnLayer0 font.pixelSize: Appearance.font.pixelSize.small Layout.fillWidth: false text: Math.round(root.value * 100) } } StyledProgressBar { id: valueProgressBar Layout.fillWidth: true value: root.value } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/BrightnessIndicator.qml ================================================ import qs.services import QtQuick import Quickshell import Quickshell.Hyprland import qs.modules.ii.onScreenDisplay OsdValueIndicator { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) icon: Hyprsunset.temperatureActive ? "routine" : "light_mode" rotateIcon: true scaleIcon: true name: Translation.tr("Brightness") value: root.brightnessMonitor?.brightness ?? 50 } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/GammaIndicator.qml ================================================ import qs.services import QtQuick import Quickshell import Quickshell.Hyprland import qs.modules.ii.onScreenDisplay OsdValueIndicator { id: rotateIcon icon: "wb_twilight" name: Translation.tr("Gamma") from: Hyprsunset.gammaLowerLimit / 100 value: Hyprsunset.gamma / 100 ?? 0.5 } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/VolumeIndicator.qml ================================================ import qs.services import QtQuick import qs.modules.ii.onScreenDisplay OsdValueIndicator { id: osdValues value: Audio.sink?.audio.volume ?? 0 icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" name: Translation.tr("Volume") } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { // Scope id: root property bool pinned: Config.options?.osk.pinnedOnStartup ?? false component OskControlButton: GroupButton { // Pin button baseWidth: 40 baseHeight: 40 clickedWidth: baseWidth clickedHeight: baseHeight + 10 buttonRadius: Appearance.rounding.normal } Loader { id: oskLoader active: GlobalStates.oskOpen onActiveChanged: { if (!oskLoader.active) { Ydotool.releaseAllKeys(); } } sourceComponent: PanelWindow { // Window id: oskRoot visible: oskLoader.active && !GlobalStates.screenLocked anchors { bottom: true left: true right: true } function hide() { GlobalStates.oskOpen = false } exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0 implicitWidth: oskBackground.width + Appearance.sizes.elevationMargin * 2 implicitHeight: oskBackground.height + Appearance.sizes.elevationMargin * 2 WlrLayershell.namespace: "quickshell:osk" WlrLayershell.layer: WlrLayer.Overlay // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: "transparent" mask: Region { item: oskBackground } // Make it usable with other panels Component.onCompleted: { GlobalFocusGrab.addPersistent(oskRoot); } Component.onDestruction: { GlobalFocusGrab.removePersistent(oskRoot); } // Background StyledRectangularShadow { target: oskBackground } Rectangle { id: oskBackground anchors.centerIn: parent color: Appearance.colors.colLayer0 radius: Appearance.rounding.windowRounding property real padding: 10 implicitWidth: oskRowLayout.implicitWidth + padding * 2 implicitHeight: oskRowLayout.implicitHeight + padding * 2 Keys.onPressed: (event) => { // Esc to close if (event.key === Qt.Key_Escape) { oskRoot.hide() } } RowLayout { id: oskRowLayout anchors.centerIn: parent spacing: 5 VerticalButtonGroup { OskControlButton { // Pin button toggled: root.pinned downAction: () => root.pinned = !root.pinned contentItem: MaterialSymbol { text: "keep" horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 } } OskControlButton { onClicked: () => { oskRoot.hide() } contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter text: "keyboard_hide" iconSize: Appearance.font.pixelSize.larger } } } Rectangle { Layout.topMargin: 20 Layout.bottomMargin: 20 Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant } OskContent { id: oskContent Layout.fillWidth: true } } } } } IpcHandler { target: "osk" function toggle(): void { GlobalStates.oskOpen = !GlobalStates.oskOpen; } function close(): void { GlobalStates.oskOpen = false } function open(): void { GlobalStates.oskOpen = true } } GlobalShortcut { name: "oskToggle" description: "Toggles on screen keyboard on press" onPressed: { GlobalStates.oskOpen = !GlobalStates.oskOpen; } } GlobalShortcut { name: "oskOpen" description: "Opens on screen keyboard on press" onPressed: { GlobalStates.oskOpen = true } } GlobalShortcut { name: "oskClose" description: "Closes on screen keyboard on press" onPressed: { GlobalStates.oskOpen = false } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OskContent.qml ================================================ import qs.modules.common import "layouts.js" as Layouts import QtQuick import QtQuick.Layouts Item { id: root property var layouts: Layouts.byName property var activeLayoutName: (layouts.hasOwnProperty(Config.options?.osk.layout)) ? Config.options?.osk.layout : Layouts.defaultLayout property var currentLayout: layouts[activeLayoutName] implicitWidth: keyRows.implicitWidth implicitHeight: keyRows.implicitHeight ColumnLayout { id: keyRows anchors.fill: parent spacing: 5 Repeater { model: root.currentLayout.keys delegate: RowLayout { id: keyRow required property var modelData spacing: 5 Repeater { model: modelData // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"} delegate: OskKey { required property var modelData keyData: modelData } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OskKey.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts RippleButton { id: root property var keyData property string key: keyData.label property string type: keyData.keytype property var keycode: keyData.keycode property string shape: keyData.shape property bool isShift: Ydotool.shiftKeys.includes(keycode) property bool isBackspace: (key.toLowerCase() == "backspace") property bool isEnter: (key.toLowerCase() == "enter" || key.toLowerCase() == "return") property real baseWidth: 45 property real baseHeight: 45 property var widthMultiplier: ({ "normal": 1, "fn": 1, "tab": 1.6, "caps": 1.9, "shift": 2.5, "control": 1.3 }) property var heightMultiplier: ({ "normal": 1, "fn": 0.7, "tab": 1, "caps": 1, "shift": 1, "control": 1 }) toggled: isShift ? Ydotool.shiftMode : false enabled: shape != "empty" colBackground: shape == "empty" ? ColorUtils.transparentize(Appearance.colors.colLayer1) : Appearance.colors.colLayer1 buttonRadius: Appearance.rounding.small implicitWidth: baseWidth * widthMultiplier[shape] || baseWidth implicitHeight: baseHeight * heightMultiplier[shape] || baseHeight Layout.fillWidth: shape == "space" || shape == "expand" Connections { target: Ydotool enabled: isShift function onShiftModeChanged() { if (Ydotool.shiftMode == 0) { capsLockTimer.hasStarted = false; } } } Timer { id: capsLockTimer property bool hasStarted: false property bool canCaps: false interval: 300 function startWaiting() { hasStarted = true; canCaps = true; start(); } onTriggered: { canCaps = false; } } downAction: () => { Ydotool.press(root.keycode); if (isShift && Ydotool.shiftMode == 0) Ydotool.shiftMode = 1; } releaseAction: () => { if (root.type == "normal") { Ydotool.release(root.keycode); if (Ydotool.shiftMode == 1) { Ydotool.releaseShiftKeys() } } else if (isShift) { if (Ydotool.shiftMode == 1) { if (!capsLockTimer.hasStarted) { capsLockTimer.startWaiting(); } else { if (capsLockTimer.canCaps) { Ydotool.shiftMode = 2; // Caps lock mode } else { Ydotool.releaseShiftKeys() } } } else if (Ydotool.shiftMode == 2) { Ydotool.releaseShiftKeys(); } } else if (root.type == "modkey") { root.toggled = !root.toggled; if (!root.toggled) { if (isShift) { Ydotool.releaseShiftKeys(); } else { Ydotool.release(root.keycode); } } } } contentItem: StyledText { id: keyText anchors.fill: parent font.family: (isBackspace || isEnter) ? Appearance.font.family.iconMaterial : Appearance.font.family.main font.pixelSize: root.shape == "fn" ? Appearance.font.pixelSize.small : (isBackspace || isEnter) ? Appearance.font.pixelSize.huge : Appearance.font.pixelSize.large horizontalAlignment: Text.AlignHCenter color: root.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 text: root.isBackspace ? "backspace" : root.isEnter ? "subdirectory_arrow_left" : Ydotool.shiftMode == 2 ? (root.keyData.labelCaps || root.keyData.labelShift || root.keyData.label) : Ydotool.shiftMode == 1 ? (root.keyData.labelShift || root.keyData.label) : root.keyData.label } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/layouts.js ================================================ // We're going to use ydotool // See /usr/include/linux/input-event-codes.h for keycodes const defaultLayout = "English (US)"; const byName = { "English (US)": { name_short: "US", description: "QWERTY - Full", comment: "Like physical keyboard", // A key looks like this: { k: "a", ks: "A", t: "normal" } (key, key-shift, type) // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand // keys: [ // [{ k: "Esc", t: "fn" }, { k: "F1", t: "fn" }, { k: "F2", t: "fn" }, { k: "F3", t: "fn" }, { k: "F4", t: "fn" }, { k: "F5", t: "fn" }, { k: "F6", t: "fn" }, { k: "F7", t: "fn" }, { k: "F8", t: "fn" }, { k: "F9", t: "fn" }, { k: "F10", t: "fn" }, { k: "F11", t: "fn" }, { k: "F12", t: "fn" }, { k: "PrtSc", t: "fn" }, { k: "Del", t: "fn" }], // [{ k: "`", ks: "~", t: "normal" }, { k: "1", ks: "!", t: "normal" }, { k: "2", ks: "@", t: "normal" }, { k: "3", ks: "#", t: "normal" }, { k: "4", ks: "$", t: "normal" }, { k: "5", ks: "%", t: "normal" }, { k: "6", ks: "^", t: "normal" }, { k: "7", ks: "&", t: "normal" }, { k: "8", ks: "*", t: "normal" }, { k: "9", ks: "(", t: "normal" }, { k: "0", ks: ")", t: "normal" }, { k: "-", ks: "_", t: "normal" }, { k: "=", ks: "+", t: "normal" }, { k: "Backspace", t: "shift" }], // [{ k: "Tab", t: "tab" }, { k: "q", ks: "Q", t: "normal" }, { k: "w", ks: "W", t: "normal" }, { k: "e", ks: "E", t: "normal" }, { k: "r", ks: "R", t: "normal" }, { k: "t", ks: "T", t: "normal" }, { k: "y", ks: "Y", t: "normal" }, { k: "u", ks: "U", t: "normal" }, { k: "i", ks: "I", t: "normal" }, { k: "o", ks: "O", t: "normal" }, { k: "p", ks: "P", t: "normal" }, { k: "[", ks: "{", t: "normal" }, { k: "]", ks: "}", t: "normal" }, { k: "\\", ks: "|", t: "expand" }], // [{ k: "Caps", t: "caps" }, { k: "a", ks: "A", t: "normal" }, { k: "s", ks: "S", t: "normal" }, { k: "d", ks: "D", t: "normal" }, { k: "f", ks: "F", t: "normal" }, { k: "g", ks: "G", t: "normal" }, { k: "h", ks: "H", t: "normal" }, { k: "j", ks: "J", t: "normal" }, { k: "k", ks: "K", t: "normal" }, { k: "l", ks: "L", t: "normal" }, { k: ";", ks: ":", t: "normal" }, { k: "'", ks: '"', t: "normal" }, { k: "Enter", t: "expand" }], // [{ k: "Shift", t: "shift" }, { k: "z", ks: "Z", t: "normal" }, { k: "x", ks: "X", t: "normal" }, { k: "c", ks: "C", t: "normal" }, { k: "v", ks: "V", t: "normal" }, { k: "b", ks: "B", t: "normal" }, { k: "n", ks: "N", t: "normal" }, { k: "m", ks: "M", t: "normal" }, { k: ",", ks: "<", t: "normal" }, { k: ".", ks: ">", t: "normal" }, { k: "/", ks: "?", t: "normal" }, { k: "Shift", t: "expand" }], // [{ k: "Ctrl", t: "control" }, { k: "Fn", t: "normal" }, { k: "Win", t: "normal" }, { k: "Alt", t: "normal" }, { k: "Space", t: "space" }, { k: "Alt", t: "normal" }, { k: "Menu", t: "normal" }, { k: "Ctrl", t: "control" }] // ] // A normal key looks like this: {label: "a", labelShift: "A", shape: "normal", keycode: 30, type: "normal"} // A modkey looks like this: {label: "Ctrl", shape: "control", keycode: 29, type: "modkey"} // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand keys: [ [ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, { keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 }, { keytype: "normal", label: "Del", shape: "fn", keycode: 111 } ], [ { keytype: "normal", label: "`", labelShift: "~", shape: "normal", keycode: 41 }, { keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 }, { keytype: "normal", label: "2", labelShift: "@", shape: "normal", keycode: 3 }, { keytype: "normal", label: "3", labelShift: "#", shape: "normal", keycode: 4 }, { keytype: "normal", label: "4", labelShift: "$", shape: "normal", keycode: 5 }, { keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 }, { keytype: "normal", label: "6", labelShift: "^", shape: "normal", keycode: 7 }, { keytype: "normal", label: "7", labelShift: "&", shape: "normal", keycode: 8 }, { keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 }, { keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 }, { keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 }, { keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 }, { keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 }, { keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 } ], [ { keytype: "normal", label: "Tab", shape: "tab", keycode: 15 }, { keytype: "normal", label: "q", labelShift: "Q", shape: "normal", keycode: 16 }, { keytype: "normal", label: "w", labelShift: "W", shape: "normal", keycode: 17 }, { keytype: "normal", label: "e", labelShift: "E", shape: "normal", keycode: 18 }, { keytype: "normal", label: "r", labelShift: "R", shape: "normal", keycode: 19 }, { keytype: "normal", label: "t", labelShift: "T", shape: "normal", keycode: 20 }, { keytype: "normal", label: "y", labelShift: "Y", shape: "normal", keycode: 21 }, { keytype: "normal", label: "u", labelShift: "U", shape: "normal", keycode: 22 }, { keytype: "normal", label: "i", labelShift: "I", shape: "normal", keycode: 23 }, { keytype: "normal", label: "o", labelShift: "O", shape: "normal", keycode: 24 }, { keytype: "normal", label: "p", labelShift: "P", shape: "normal", keycode: 25 }, { keytype: "normal", label: "[", labelShift: "{", shape: "normal", keycode: 26 }, { keytype: "normal", label: "]", labelShift: "}", shape: "normal", keycode: 27 }, { keytype: "normal", label: "\\", labelShift: "|", shape: "expand", keycode: 43 } ], [ //{ keytype: "normal", label: "Caps", shape: "caps", keycode: 58 }, // not needed as double-pressing shift does that { keytype: "spacer", label: "", shape: "empty" }, { keytype: "spacer", label: "", shape: "empty" }, { keytype: "normal", label: "a", labelShift: "A", shape: "normal", keycode: 30 }, { keytype: "normal", label: "s", labelShift: "S", shape: "normal", keycode: 31 }, { keytype: "normal", label: "d", labelShift: "D", shape: "normal", keycode: 32 }, { keytype: "normal", label: "f", labelShift: "F", shape: "normal", keycode: 33 }, { keytype: "normal", label: "g", labelShift: "G", shape: "normal", keycode: 34 }, { keytype: "normal", label: "h", labelShift: "H", shape: "normal", keycode: 35 }, { keytype: "normal", label: "j", labelShift: "J", shape: "normal", keycode: 36 }, { keytype: "normal", label: "k", labelShift: "K", shape: "normal", keycode: 37 }, { keytype: "normal", label: "l", labelShift: "L", shape: "normal", keycode: 38 }, { keytype: "normal", label: ";", labelShift: ":", shape: "normal", keycode: 39 }, { keytype: "normal", label: "'", labelShift: '"', shape: "normal", keycode: 40 }, { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 } ], [ { keytype: "modkey", label: "Shift", labelShift: "Shift", labelCaps: "Caps", shape: "shift", keycode: 42 }, { keytype: "normal", label: "z", labelShift: "Z", shape: "normal", keycode: 44 }, { keytype: "normal", label: "x", labelShift: "X", shape: "normal", keycode: 45 }, { keytype: "normal", label: "c", labelShift: "C", shape: "normal", keycode: 46 }, { keytype: "normal", label: "v", labelShift: "V", shape: "normal", keycode: 47 }, { keytype: "normal", label: "b", labelShift: "B", shape: "normal", keycode: 48 }, { keytype: "normal", label: "n", labelShift: "N", shape: "normal", keycode: 49 }, { keytype: "normal", label: "m", labelShift: "M", shape: "normal", keycode: 50 }, { keytype: "normal", label: ",", labelShift: "<", shape: "normal", keycode: 51 }, { keytype: "normal", label: ".", labelShift: ">", shape: "normal", keycode: 52 }, { keytype: "normal", label: "/", labelShift: "?", shape: "normal", keycode: 53 }, { keytype: "modkey", label: "Shift", labelShift: "Shift", labelCaps: "Caps", shape: "expand", keycode: 54 } // optional ], [ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 }, // { label: "Super", shape: "normal", keycode: 125 }, // dangerous { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, { keytype: "normal", label: "Space", shape: "space", keycode: 57 }, { keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 }, // { label: "Super", shape: "normal", keycode: 126 }, // dangerous { keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 } ] ] }, "German": { name_short: "DE", description: "QWERTZ - Full", comment: "Keyboard layout commonly used in German-speaking countries", keys: [ [ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, { keytype: "normal", label: "Druck", shape: "fn", keycode: 99 }, { keytype: "normal", label: "Entf", shape: "fn", keycode: 111 } ], [ { keytype: "normal", label: "^", labelShift: "°", labelAlt: "′", shape: "normal", keycode: 41 }, { keytype: "normal", label: "1", labelShift: "!", labelAlt: "¹", shape: "normal", keycode: 2 }, { keytype: "normal", label: "2", labelShift: "\"", labelAlt: "²", shape: "normal", keycode: 3 }, { keytype: "normal", label: "3", labelShift: "§", labelAlt: "³", shape: "normal", keycode: 4 }, { keytype: "normal", label: "4", labelShift: "$", labelAlt: "¼", shape: "normal", keycode: 5 }, { keytype: "normal", label: "5", labelShift: "%", labelAlt: "½", shape: "normal", keycode: 6 }, { keytype: "normal", label: "6", labelShift: "&", labelAlt: "¬", shape: "normal", keycode: 7 }, { keytype: "normal", label: "7", labelShift: "/", labelAlt: "{", shape: "normal", keycode: 8 }, { keytype: "normal", label: "8", labelShift: "(", labelAlt: "[", shape: "normal", keycode: 9 }, { keytype: "normal", label: "9", labelShift: ")", labelAlt: "]", shape: "normal", keycode: 10 }, { keytype: "normal", label: "0", labelShift: "=", labelAlt: "}", shape: "normal", keycode: 11 }, { keytype: "normal", label: "ß", labelShift: "?", labelAlt: "\\", shape: "normal", keycode: 12 }, { keytype: "normal", label: "´", labelShift: "`", labelAlt: "¸", shape: "normal", keycode: 13 }, { keytype: "normal", label: "⟵", shape: "expand", keycode: 14 } ], [ { keytype: "normal", label: "Tab ⇆", shape: "tab", keycode: 15 }, { keytype: "normal", label: "q", labelShift: "Q", labelAlt: "@", shape: "normal", keycode: 16 }, { keytype: "normal", label: "w", labelShift: "W", labelAlt: "ſ", shape: "normal", keycode: 17 }, { keytype: "normal", label: "e", labelShift: "E", labelAlt: "€", shape: "normal", keycode: 18 }, { keytype: "normal", label: "r", labelShift: "R", labelAlt: "¶", shape: "normal", keycode: 19 }, { keytype: "normal", label: "t", labelShift: "T", labelAlt: "ŧ", shape: "normal", keycode: 20 }, { keytype: "normal", label: "z", labelShift: "Z", labelAlt: "←", shape: "normal", keycode: 21 }, { keytype: "normal", label: "u", labelShift: "U", labelAlt: "↓", shape: "normal", keycode: 22 }, { keytype: "normal", label: "i", labelShift: "I", labelAlt: "→", shape: "normal", keycode: 23 }, { keytype: "normal", label: "o", labelShift: "O", labelAlt: "ø", shape: "normal", keycode: 24 }, { keytype: "normal", label: "p", labelShift: "P", labelAlt: "þ", shape: "normal", keycode: 25 }, { keytype: "normal", label: "ü", labelShift: "Ü", labelAlt: "¨", shape: "normal", keycode: 26 }, { keytype: "normal", label: "+", labelShift: "*", labelAlt: "~", shape: "normal", keycode: 27 }, { keytype: "normal", label: "↵", shape: "expand", keycode: 28 } ], [ //{ keytype: "normal", label: "Umschalt ⇩", shape: "caps", keycode: 58 }, { keytype: "spacer", label: "", shape: "empty" }, { keytype: "spacer", label: "", shape: "empty" }, { keytype: "normal", label: "a", labelShift: "A", labelAlt: "æ", shape: "normal", keycode: 30 }, { keytype: "normal", label: "s", labelShift: "S", labelAlt: "ſ", shape: "normal", keycode: 31 }, { keytype: "normal", label: "d", labelShift: "D", labelAlt: "ð", shape: "normal", keycode: 32 }, { keytype: "normal", label: "f", labelShift: "F", labelAlt: "đ", shape: "normal", keycode: 33 }, { keytype: "normal", label: "g", labelShift: "G", labelAlt: "ŋ", shape: "normal", keycode: 34 }, { keytype: "normal", label: "h", labelShift: "H", labelAlt: "ħ", shape: "normal", keycode: 35 }, { keytype: "normal", label: "j", labelShift: "J", labelAlt: "", shape: "normal", keycode: 36 }, { keytype: "normal", label: "k", labelShift: "K", labelAlt: "ĸ", shape: "normal", keycode: 37 }, { keytype: "normal", label: "l", labelShift: "L", labelAlt: "ł", shape: "normal", keycode: 38 }, { keytype: "normal", label: "ö", labelShift: "Ö", labelAlt: "˝", shape: "normal", keycode: 39 }, { keytype: "normal", label: "ä", labelShift: 'Ä', labelAlt: "^", shape: "normal", keycode: 40 }, { keytype: "normal", label: "#", labelShift: '\'', labelAlt: "’", shape: "normal", keycode: 43 }, { keytype: "spacer", label: "", shape: "empty" }, //{ keytype: "normal", label: "↵", shape: "expand", keycode: 28 } ], [ { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "shift", keycode: 42 }, { keytype: "normal", label: "<", labelShift: ">", labelAlt: "|", shape: "normal", keycode: 86 }, { keytype: "normal", label: "y", labelShift: "Y", labelAlt: "»", shape: "normal", keycode: 44 }, { keytype: "normal", label: "x", labelShift: "X", labelAlt: "«", shape: "normal", keycode: 45 }, { keytype: "normal", label: "c", labelShift: "C", labelAlt: "¢", shape: "normal", keycode: 46 }, { keytype: "normal", label: "v", labelShift: "V", labelAlt: "„", shape: "normal", keycode: 47 }, { keytype: "normal", label: "b", labelShift: "B", labelAlt: "“", shape: "normal", keycode: 48 }, { keytype: "normal", label: "n", labelShift: "N", labelAlt: "”", shape: "normal", keycode: 49 }, { keytype: "normal", label: "m", labelShift: "M", labelAlt: "µ", shape: "normal", keycode: 50 }, { keytype: "normal", label: ",", labelShift: ";", labelAlt: "·", shape: "normal", keycode: 51 }, { keytype: "normal", label: ".", labelShift: ":", labelAlt: "…", shape: "normal", keycode: 52 }, { keytype: "normal", label: "-", labelShift: "_", labelAlt: "–", shape: "normal", keycode: 53 }, { keytype: "modkey", label: "Shift", labelShift: "Shift ⇧", labelCaps: "Locked ⇩", shape: "expand", keycode: 54 }, // optional ], [ { keytype: "modkey", label: "Strg", shape: "control", keycode: 29 }, //{ keytype: "normal", label: "", shape: "normal", keycode: 125 }, // dangerous { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, { keytype: "normal", label: "Leertaste", shape: "space", keycode: 57 }, { keytype: "modkey", label: "Alt Gr", shape: "normal", keycode: 100 }, // { label: "Super", shape: "normal", keycode: 126 }, // dangerous //{ keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, // doesn't work? { keytype: "modkey", label: "Strg", shape: "control", keycode: 97 }, { keytype: "normal", label: "⇦", shape: "normal", keycode: 105 }, { keytype: "normal", label: "⇨", shape: "normal", keycode: 106 }, ] ] }, "Russian": { name_short: "RU", description: "ЙЦУКЕН - Full", comment: "Standard Russian keyboard layout", keys: [ [ { keytype: "normal", label: "Esc", shape: "fn", keycode: 1 }, { keytype: "normal", label: "F1", shape: "fn", keycode: 59 }, { keytype: "normal", label: "F2", shape: "fn", keycode: 60 }, { keytype: "normal", label: "F3", shape: "fn", keycode: 61 }, { keytype: "normal", label: "F4", shape: "fn", keycode: 62 }, { keytype: "normal", label: "F5", shape: "fn", keycode: 63 }, { keytype: "normal", label: "F6", shape: "fn", keycode: 64 }, { keytype: "normal", label: "F7", shape: "fn", keycode: 65 }, { keytype: "normal", label: "F8", shape: "fn", keycode: 66 }, { keytype: "normal", label: "F9", shape: "fn", keycode: 67 }, { keytype: "normal", label: "F10", shape: "fn", keycode: 68 }, { keytype: "normal", label: "F11", shape: "fn", keycode: 87 }, { keytype: "normal", label: "F12", shape: "fn", keycode: 88 }, { keytype: "normal", label: "PrtSc", shape: "fn", keycode: 99 }, { keytype: "normal", label: "Del", shape: "fn", keycode: 111 } ], [ { keytype: "normal", label: "ё", labelShift: "Ё", shape: "normal", keycode: 41 }, { keytype: "normal", label: "1", labelShift: "!", shape: "normal", keycode: 2 }, { keytype: "normal", label: "2", labelShift: "\"", shape: "normal", keycode: 3 }, { keytype: "normal", label: "3", labelShift: "№", shape: "normal", keycode: 4 }, { keytype: "normal", label: "4", labelShift: ";", shape: "normal", keycode: 5 }, { keytype: "normal", label: "5", labelShift: "%", shape: "normal", keycode: 6 }, { keytype: "normal", label: "6", labelShift: ":", shape: "normal", keycode: 7 }, { keytype: "normal", label: "7", labelShift: "?", shape: "normal", keycode: 8 }, { keytype: "normal", label: "8", labelShift: "*", shape: "normal", keycode: 9 }, { keytype: "normal", label: "9", labelShift: "(", shape: "normal", keycode: 10 }, { keytype: "normal", label: "0", labelShift: ")", shape: "normal", keycode: 11 }, { keytype: "normal", label: "-", labelShift: "_", shape: "normal", keycode: 12 }, { keytype: "normal", label: "=", labelShift: "+", shape: "normal", keycode: 13 }, { keytype: "normal", label: "Backspace", shape: "expand", keycode: 14 } ], [ { keytype: "normal", label: "Tab", shape: "tab", keycode: 15 }, { keytype: "normal", label: "й", labelShift: "Й", shape: "normal", keycode: 16 }, { keytype: "normal", label: "ц", labelShift: "Ц", shape: "normal", keycode: 17 }, { keytype: "normal", label: "у", labelShift: "У", shape: "normal", keycode: 18 }, { keytype: "normal", label: "к", labelShift: "К", shape: "normal", keycode: 19 }, { keytype: "normal", label: "е", labelShift: "Е", shape: "normal", keycode: 20 }, { keytype: "normal", label: "н", labelShift: "Н", shape: "normal", keycode: 21 }, { keytype: "normal", label: "г", labelShift: "Г", shape: "normal", keycode: 22 }, { keytype: "normal", label: "ш", labelShift: "Ш", shape: "normal", keycode: 23 }, { keytype: "normal", label: "щ", labelShift: "Щ", shape: "normal", keycode: 24 }, { keytype: "normal", label: "з", labelShift: "З", shape: "normal", keycode: 25 }, { keytype: "normal", label: "х", labelShift: "Х", shape: "normal", keycode: 26 }, { keytype: "normal", label: "ъ", labelShift: "Ъ", shape: "normal", keycode: 27 }, { keytype: "normal", label: "\\", labelShift: "/", shape: "expand", keycode: 43 } ], [ { keytype: "spacer", label: "", shape: "empty" }, { keytype: "spacer", label: "", shape: "empty" }, { keytype: "normal", label: "ф", labelShift: "Ф", shape: "normal", keycode: 30 }, { keytype: "normal", label: "ы", labelShift: "Ы", shape: "normal", keycode: 31 }, { keytype: "normal", label: "в", labelShift: "В", shape: "normal", keycode: 32 }, { keytype: "normal", label: "а", labelShift: "А", shape: "normal", keycode: 33 }, { keytype: "normal", label: "п", labelShift: "П", shape: "normal", keycode: 34 }, { keytype: "normal", label: "р", labelShift: "Р", shape: "normal", keycode: 35 }, { keytype: "normal", label: "о", labelShift: "О", shape: "normal", keycode: 36 }, { keytype: "normal", label: "л", labelShift: "Л", shape: "normal", keycode: 37 }, { keytype: "normal", label: "д", labelShift: "Д", shape: "normal", keycode: 38 }, { keytype: "normal", label: "ж", labelShift: "Ж", shape: "normal", keycode: 39 }, { keytype: "normal", label: "э", labelShift: "Э", shape: "normal", keycode: 40 }, { keytype: "normal", label: "Enter", shape: "expand", keycode: 28 } ], [ { keytype: "modkey", label: "Shift", shape: "shift", keycode: 42 }, { keytype: "normal", label: "я", labelShift: "Я", shape: "normal", keycode: 44 }, { keytype: "normal", label: "ч", labelShift: "Ч", shape: "normal", keycode: 45 }, { keytype: "normal", label: "с", labelShift: "С", shape: "normal", keycode: 46 }, { keytype: "normal", label: "м", labelShift: "М", shape: "normal", keycode: 47 }, { keytype: "normal", label: "и", labelShift: "И", shape: "normal", keycode: 48 }, { keytype: "normal", label: "т", labelShift: "Т", shape: "normal", keycode: 49 }, { keytype: "normal", label: "ь", labelShift: "Ь", shape: "normal", keycode: 50 }, { keytype: "normal", label: "б", labelShift: "Б", shape: "normal", keycode: 51 }, { keytype: "normal", label: "ю", labelShift: "Ю", shape: "normal", keycode: 52 }, { keytype: "normal", label: ".", labelShift: ",", shape: "normal", keycode: 53 }, { keytype: "modkey", label: "Shift", shape: "expand", keycode: 54 } ], [ { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 29 }, { keytype: "modkey", label: "Alt", shape: "normal", keycode: 56 }, { keytype: "normal", label: "Space", shape: "space", keycode: 57 }, { keytype: "modkey", label: "Alt", shape: "normal", keycode: 100 }, { keytype: "normal", label: "Menu", shape: "normal", keycode: 139 }, { keytype: "modkey", label: "Ctrl", shape: "control", keycode: 97 } ] ] } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/Overlay.qml ================================================ import qs import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property Component regionComponent: Component { Region {} } Loader { id: overlayLoader active: GlobalStates.overlayOpen || OverlayContext.hasPinnedWidgets sourceComponent: PanelWindow { id: overlayWindow exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:overlay" WlrLayershell.layer: WlrLayer.Overlay // Use OnDemand for pinned widgets to allow focus switching with mouse clicks WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : (OverlayContext.clickableWidgets.length > 0 ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None) visible: true color: "transparent" mask: Region { item: GlobalStates.overlayOpen ? overlayContent : null regions: OverlayContext.clickableWidgets.map((widget) => regionComponent.createObject(this, { item: widget })); } anchors { top: true bottom: true left: true right: true } HyprlandFocusGrab { id: grab windows: [overlayWindow] active: false onCleared: () => { if (!active) GlobalStates.overlayOpen = false; } } Connections { target: GlobalStates function onOverlayOpenChanged() { delayedGrabTimer.restart(); } } Timer { id: delayedGrabTimer interval: Appearance.animation.elementMoveFast.duration onTriggered: { grab.active = GlobalStates.overlayOpen; } } OverlayContent { id: overlayContent anchors.fill: parent } } } IpcHandler { target: "overlay" function toggle(): void { GlobalStates.overlayOpen = !GlobalStates.overlayOpen; } } GlobalShortcut { name: "overlayToggle" description: "Toggles overlay on press" onPressed: { GlobalStates.overlayOpen = !GlobalStates.overlayOpen; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/OverlayBackground.qml ================================================ import QtQuick import qs.modules.common Rectangle { id: contentItem anchors.fill: parent color: Appearance.m3colors.m3surfaceContainer } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/OverlayContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas Item { id: root focus: true readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true Keys.onPressed: (event) => { // Esc to close if (event.key === Qt.Key_Escape) { GlobalStates.overlayOpen = false; } } property real initScale: Config.options.overlay.openingZoomAnimation ? 1.08 : 1.000001 scale: initScale Component.onCompleted: { scale = 1 } Behavior on scale { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Rectangle { id: bg anchors.fill: parent color: Appearance.colors.colScrim visible: Config.options.overlay.darkenScreen && opacity > 0 opacity: (GlobalStates.overlayOpen && root.scale !== initScale) ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } WidgetCanvas { anchors.fill: parent onClicked: GlobalStates.overlayOpen = false OverlayTaskbar { anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 50 } } Repeater { model: ScriptModel { values: Persistent.states.overlay.open.map(identifier => { return OverlayContext.availableWidgets.find(w => w.identifier === identifier); }) objectProp: "identifier" } delegate: OverlayWidgetDelegateChooser { } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/OverlayContext.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import Quickshell Singleton { id: root signal requestCenter(string identifier) readonly property list availableWidgets: [ { identifier: "crosshair", materialSymbol: "point_scan" }, { identifier: "fpsLimiter", materialSymbol: "animation" }, { identifier: "floatingImage", materialSymbol: "imagesmode" }, { identifier: "recorder", materialSymbol: "screen_record" }, { identifier: "resources", materialSymbol: "browse_activity" }, { identifier: "notes", materialSymbol: "note_stack" }, { identifier: "volumeMixer", materialSymbol: "volume_up" }, ] readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0 property list pinnedWidgetIdentifiers: [] property list clickableWidgets: [] function pin(identifier: string, pin = true) { if (pin) { if (!root.pinnedWidgetIdentifiers.includes(identifier)) { root.pinnedWidgetIdentifiers.push(identifier) } } else { root.pinnedWidgetIdentifiers = root.pinnedWidgetIdentifiers.filter(id => id !== identifier) } } function registerClickableWidget(widget: var, clickable = true) { if (clickable) { if (!root.clickableWidgets.includes(widget)) { root.clickableWidgets.push(widget) } } else { root.clickableWidgets = root.clickableWidgets.filter(w => w !== widget) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/OverlayTaskbar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas Rectangle { id: root property real padding: 8 opacity: GlobalStates.overlayOpen ? 1 : 0 implicitWidth: contentRow.implicitWidth + (padding * 2) implicitHeight: contentRow.implicitHeight + (padding * 2) color: Appearance.m3colors.m3surfaceContainer radius: Appearance.rounding.large border.color: Appearance.colors.colOutlineVariant border.width: 1 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } RowLayout { id: contentRow anchors { fill: parent margins: root.padding } spacing: 6 Row { spacing: 4 Repeater { model: ScriptModel { values: OverlayContext.availableWidgets } delegate: WidgetButton { required property var modelData identifier: modelData.identifier materialSymbol: modelData.materialSymbol } } } Separator {} TimeWidget {} Separator { visible: Battery.available } BatteryWidget { visible: Battery.available } } component Separator: Rectangle { implicitWidth: 1 color: Appearance.colors.colOutlineVariant Layout.fillHeight: true Layout.topMargin: 10 Layout.bottomMargin: 10 } component TimeWidget: StyledText { Layout.alignment: Qt.AlignVCenter Layout.leftMargin: 8 Layout.rightMargin: 6 text: DateTime.time color: Appearance.colors.colOnSurface font { family: Appearance.font.family.numbers variableAxes: Appearance.font.variableAxes.numbers pixelSize: 22 } } component BatteryWidget: Row { id: batteryWidget Layout.alignment: Qt.AlignVCenter Layout.leftMargin: 6 Layout.rightMargin: 6 spacing: 2 property color colText: Battery.isLowAndNotCharging ? Appearance.colors.colError : Appearance.colors.colOnSurface MaterialSymbol { id: boltIcon anchors.verticalCenter: parent.verticalCenter fill: 1 text: Battery.isCharging ? "bolt" : "battery_android_full" color: batteryWidget.colText iconSize: 24 animateChange: true } StyledText { id: batteryText anchors.verticalCenter: parent.verticalCenter text: Math.round(Battery.percentage * 100) + "%" color: batteryWidget.colText font { family: Appearance.font.family.numbers variableAxes: Appearance.font.variableAxes.numbers pixelSize: 18 } } } component WidgetButton: RippleButton { id: widgetButton required property string identifier required property string materialSymbol Layout.alignment: Qt.AlignVCenter toggled: Persistent.states.overlay.open.includes(identifier) altAction: () => OverlayContext.requestCenter(identifier) onClicked: { if (widgetButton.toggled) { Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== identifier); } else { Persistent.states.overlay.open.push(identifier); } } implicitWidth: implicitHeight colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive buttonRadius: root.radius - (root.height - height) / 2 contentItem: Item { anchors.centerIn: parent implicitWidth: 32 implicitHeight: 32 MaterialSymbol { id: iconWidget anchors.centerIn: parent iconSize: 24 text: widgetButton.materialSymbol color: widgetButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/OverlayWidgetDelegateChooser.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import qs.modules.ii.overlay.crosshair import qs.modules.ii.overlay.volumeMixer import qs.modules.ii.overlay.floatingImage import qs.modules.ii.overlay.fpsLimiter import qs.modules.ii.overlay.recorder import qs.modules.ii.overlay.resources import qs.modules.ii.overlay.notes DelegateChooser { id: root role: "identifier" DelegateChoice { roleValue: "crosshair"; Crosshair {} } DelegateChoice { roleValue: "floatingImage"; FloatingImage {} } DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} } DelegateChoice { roleValue: "recorder"; Recorder {} } DelegateChoice { roleValue: "resources"; Resources {} } DelegateChoice { roleValue: "notes"; Notes {} } DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/StyledOverlayWidget.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Qt5Compat.GraphicalEffects import qs import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas /* * To make an overlay widget: * 1. Create a modules/overlay//.qml, using this as the base class and declare your widget content as contentItem * 2. Add an entry to OverlayContext.availableWidgets with identifier= * 3. Add an entry in Persistent.states.overlay. with x, y, width, height, pinned, clickthrough properties set to reasonable defaults * 4. Add an entry in OverlayWidgetDelegateChooser with roleValue= and Declare your widget in there * Use existing entries as reference. */ AbstractOverlayWidget { id: root // To be defined by subclasses required property Item contentItem property bool fancyBorders: true property bool showCenterButton: false property bool showClickabilityButton: true // Defaults n stuff required property var modelData readonly property string identifier: modelData.identifier readonly property string materialSymbol: modelData.materialSymbol ?? "widgets" property string title: identifier.replace(/([A-Z])/g, " $1").replace(/^./, function(str){ return str.toUpperCase(); }) property var persistentStateEntry: Persistent.states.overlay[identifier] property real radius: Appearance.rounding.windowRounding property real minimumWidth: contentItem.implicitWidth property real minimumHeight: contentItem.implicitHeight property real resizeMargin: 8 property real padding: 6 property real contentRadius: radius - padding // Resizing function getXResizeDirection(x) { return (x < root.resizeMargin) ? -1 : (x > root.width - root.resizeMargin) ? 1 : 0 } function getYResizeDirection(y) { return (y < root.resizeMargin) ? -1 : (y > root.height - root.resizeMargin) ? 1 : 0 } hoverEnabled: true property bool resizable: true property bool resizing: false property int resizeXDirection: getXResizeDirection(mouseX) property int resizeYDirection: getYResizeDirection(mouseY) draggable: GlobalStates.overlayOpen drag.target: undefined animateXPos: !dragHandler.active animateYPos: !dragHandler.active z: dragHandler.active ? 2 : 1 cursorShape: { if (dragHandler.active) return root.resizing ? cursorShape : Qt.ArrowCursor; if (resizeMargin < mouseX && mouseX < width - resizeMargin && resizeMargin < mouseY && mouseY < height - resizeMargin) { return Qt.ArrowCursor; } else { if (!root.resizable) return Qt.ArrowCursor; const dragIsLeft = mouseX < width / 2 const dragIsTop = mouseY < height / 2 if ((dragIsLeft && dragIsTop) || (!dragIsLeft && !dragIsTop)) { return Qt.SizeFDiagCursor } else { return Qt.SizeBDiagCursor } } } // Positioning & sizing x: Math.round(persistentStateEntry.x) // Round or it'll be blurry y: Math.round(persistentStateEntry.y) // Round or it'll be blurry pinned: persistentStateEntry.pinned clickthrough: persistentStateEntry.clickthrough drag { minimumX: 0 minimumY: 0 maximumX: root.parent?.width - root.width maximumY: root.parent?.height - root.height } opacity: (GlobalStates.overlayOpen || !clickthrough) ? 1.0 : Config.options.overlay.clickthroughOpacity // Guarded states & registration funcs property bool open: Persistent.states.overlay.open property bool actuallyPinned: pinned && open property bool actuallyClickable: !clickthrough && actuallyPinned && open onActuallyPinnedChanged: reportPinnedState(); onActuallyClickableChanged: reportClickableState(); function reportPinnedState() { OverlayContext.pin(identifier, actuallyPinned); } function reportClickableState() { OverlayContext.registerClickableWidget(contentItem, actuallyClickable); } // Self-registeration with OverlayContext Component.onCompleted: { reportPinnedState(); reportClickableState(); } Connections { target: OverlayContext function onRequestCenter(identifier) { if (identifier === root.identifier) { root.center() } } } // Hooks onPressed: (event) => { // We're only interested in handling resize here // Early returns if (!root.resizable) return; if (root.resizeMargin < event.x && event.x < root.width - root.resizeMargin && root.resizeMargin < event.y && event.y < root.height - root.resizeMargin) { return; } // Resizing setup root.resizing = true; root.resizeXDirection = getXResizeDirection(event.x); root.resizeYDirection = getYResizeDirection(event.y); if (root.resizeYDirection !== 0 && root.resizeXDirection === 0) { root.resizeXDirection = event.x < root.width / 2 ? -1 : 1; } else if (root.resizeXDirection !== 0 && root.resizeYDirection === 0) { root.resizeYDirection = event.y < root.height / 2 ? -1 : 1; } } onPositionChanged: (event) => { if (!resizing) return; contentContainer.implicitWidth = Math.max(root.persistentStateEntry.width + dragHandler.xAxis.activeValue * root.resizeXDirection, root.minimumWidth); contentContainer.implicitHeight = Math.max(root.persistentStateEntry.height + dragHandler.yAxis.activeValue * root.resizeYDirection, root.minimumHeight); const negativeXDrag = root.resizeXDirection === -1; const negativeYDrag = root.resizeYDirection === -1; const wantedX = root.persistentStateEntry.x + (negativeXDrag ? dragHandler.xAxis.activeValue : 0) const wantedY = root.persistentStateEntry.y + (negativeYDrag ? dragHandler.yAxis.activeValue : 0) const negativeXDragLimit = root.persistentStateEntry.x + root.persistentStateEntry.width - contentContainer.implicitWidth; const negativeYDragLimit = root.persistentStateEntry.y + root.persistentStateEntry.height - contentContainer.implicitHeight; root.x = negativeXDrag ? Math.min(wantedX, negativeXDragLimit) : wantedX; root.y = negativeYDrag ? Math.min(wantedY, negativeYDragLimit) : wantedY; } DragHandler { id: dragHandler acceptedButtons: Qt.LeftButton | Qt.RightButton target: (root.draggable && !root.resizing) ? root : null onActiveChanged: { // Handle drag release if (!active) { root.resizing = false; root.savePosition(); } } xAxis.minimum: 0 xAxis.maximum: root.parent?.width - root.width yAxis.minimum: 0 yAxis.maximum: root.parent?.height - root.height } function close() { Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== root.identifier); } function togglePinned() { persistentStateEntry.pinned = !persistentStateEntry.pinned; } function toggleClickthrough() { persistentStateEntry.clickthrough = !persistentStateEntry.clickthrough; } function savePosition(xPos = root.x, yPos = root.y, width = contentContainer.implicitWidth, height = contentContainer.implicitHeight) { persistentStateEntry.x = Math.round(xPos); persistentStateEntry.y = Math.round(yPos); persistentStateEntry.width = Math.round(width); persistentStateEntry.height = Math.round(height); } function center() { const targetX = (root.parent.width - contentColumn.width) / 2 - root.resizeMargin const targetY = (root.parent.height - contentContainer.height) / 2 - titleBar.implicitHeight + border.border.width - root.resizeMargin root.x = targetX root.y = targetY root.savePosition(targetX, targetY) } visible: GlobalStates.overlayOpen || actuallyPinned implicitWidth: contentColumn.implicitWidth + resizeMargin * 2 implicitHeight: contentColumn.implicitHeight + resizeMargin * 2 Rectangle { id: border anchors { fill: parent margins: root.resizeMargin } color: ColorUtils.transparentize(Appearance.colors.colLayer1Base, (root.fancyBorders && GlobalStates.overlayOpen) ? 0 : 1) radius: root.radius border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1) border.width: 1 layer.enabled: GlobalStates.overlayOpen layer.effect: OpacityMask { maskSource: Rectangle { width: border.width height: border.height radius: root.radius } } ColumnLayout { id: contentColumn z: root.fancyBorders ? 0 : -1 anchors.fill: parent spacing: 0 // Title bar Rectangle { id: titleBar opacity: GlobalStates.overlayOpen ? 1 : 0 Layout.fillWidth: true implicitWidth: titleBarRow.implicitWidth + root.padding * 2 implicitHeight: titleBarRow.implicitHeight + root.padding * 2 color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1Base // border.color: Appearance.colors.colOutlineVariant // border.width: 1 RowLayout { id: titleBarRow anchors { fill: parent margins: root.padding } spacing: 2 MaterialSymbol { text: root.materialSymbol Layout.leftMargin: 6 iconSize: 20 Layout.alignment: Qt.AlignVCenter Layout.rightMargin: 4 } StyledText { Layout.fillWidth: true text: root.title elide: Text.ElideRight } TitlebarButton { visible: root.showCenterButton materialSymbol: "recenter" onClicked: root.center() StyledToolTip { text: "Center" } } TitlebarButton { visible: (root.pinned && root.showClickabilityButton) materialSymbol: "mouse" toggled: !root.clickthrough onClicked: root.toggleClickthrough() StyledToolTip { text: "Clickable when pinned" } } TitlebarButton { materialSymbol: "keep" toggled: root.pinned onClicked: root.togglePinned() StyledToolTip { text: "Pin" } } TitlebarButton { materialSymbol: "close" onClicked: root.close() StyledToolTip { text: "Close" } } } } // Content Item { id: contentContainer Layout.fillWidth: true Layout.fillHeight: true Layout.margins: root.fancyBorders ? root.padding : 0 Layout.topMargin: -border.border.width // Border of a rectangle is drawn inside its bounds, so we do this to make the gap not too big Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter implicitWidth: Math.max(root.persistentStateEntry.width, root.minimumWidth) implicitHeight: Math.max(root.persistentStateEntry.height, root.minimumHeight) children: [root.contentItem] } } } component TitlebarButton: RippleButton { id: titlebarButton required property string materialSymbol buttonRadius: height / 2 implicitHeight: contentItem.implicitHeight implicitWidth: implicitHeight padding: 0 colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive contentItem: Item { anchors.centerIn: parent implicitWidth: 30 implicitHeight: 30 MaterialSymbol { id: iconWidget anchors.centerIn: parent iconSize: 20 text: titlebarButton.materialSymbol fill: titlebarButton.toggled color: titlebarButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurface } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/crosshair/Crosshair.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.ii.overlay StyledOverlayWidget { id: root fancyBorders: false // Crosshair should be see-through showCenterButton: true opacity: 1 // The crosshair itself already has transparency if configured showClickabilityButton: false clickthrough: true resizable: false contentItem: CrosshairContent { anchors.centerIn: parent } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/crosshair/CrosshairContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs.modules.common import qs.modules.common.functions Item { id: root // Keys to props // f, 0f, 1f, m are irrelevant as they're firing error stuff // 0 is irrelevant because it's some profile stuff property var propertyMap: ({ "c": "color", "u": "colorCode", "h": "outline", "o": "outlineOpacity", "t": "outlineThickness", "d": "centerDot", "a": "centerDotOpacity", "z": "centerDotSize", "0a": "innerLineOpacity", "0l": "innerLineLength", "0v": "innerLineVerticalLength", "0g": "innerLineUnbindAxesLengths", "0t": "innerLineThickness", "0o": "innerLineOffset", "1b": "outerLines", "1a": "outerLineOpacity", "1l": "outerLineLength", "1v": "outerLineVerticalLength", "1g": "outerLineUnbindAxesLengths", "1t": "outerLineThickness", "1o": "outerLineOffset", }) property var colorMap: ({ 0: "#FFFFFF", 1: "#00FF00", 2: "#7FFF00", 3: "#DFFF00", 4: "#FFFF00", 5: "#00FFFF", 6: "#FF00FF", 7: "#FF0000" }) // Raw props property int color: 0 property string colorCode: "#FFFFFF" property bool outline: true property real outlineOpacity: 0.5 property int outlineThickness: 1 property bool centerDot: false property real centerDotOpacity: 1 property int centerDotSize: 2 property bool innerLines: true property real innerLineOpacity: 0.8 property int innerLineLength: 6 property int innerLineVerticalLength: innerLineLength property bool innerLineUnbindAxesLengths: false property int innerLineThickness: 2 property int innerLineOffset: 3 property bool outerLines: true property real outerLineOpacity: 0.35 property int outerLineLength: 2 property int outerLineVerticalLength: outerLineLength property bool outerLineUnbindAxesLengths: false property int outerLineThickness: 2 property int outerLineOffset: 10 property string defaultCode: "c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10" function loadFromCode(code: string): void { let args = code.split(";"); for (let i = 0; i < args.length; i+= 2) { let key = args[i]; let value = args[i+1]; let targetKey = root.propertyMap[key]; let targetType = typeof root[targetKey]; if (targetKey === undefined) continue; if (targetType === "number") { value = parseFloat(value); } else if (targetType === "boolean") { value = (value === "1"); } if (targetKey === "colorCode") { value = "#" + value.slice(0, 6); } root[targetKey] = value; } if (!root.innerLineUnbindAxesLengths) { root.innerLineVerticalLength = root.innerLineLength; } if (!root.outerLineUnbindAxesLengths) { root.outerLineVerticalLength = root.outerLineLength; } } // Update values from code property var code: Config.options.crosshair.code Component.onCompleted: reloadFromCode(); onCodeChanged: reloadFromCode(); function reloadFromCode() { root.loadFromCode(root.defaultCode); root.loadFromCode(root.code); } // Aggregated props property color crosshairColor: { if (colorMap[color] !== undefined) return root.colorMap[color]; if (color === 8) return colorCode; return "#FFFFFF"; } property int borderWidth: outline ? outlineThickness : 0 property color borderColor: ColorUtils.transparentize("black", 1 - root.outlineOpacity) property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity) property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity) property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2 property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2 property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2 implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction implicitHeight: implicitWidth // width: implicitWidth // height: implicitHeight Rectangle { id: centerDot visible: root.centerDot anchors.centerIn: parent color: root.crosshairColor opacity: root.centerDotOpacity width: centerDotTotalSize height: width border.width: root.borderWidth border.color: root.borderColor } Repeater { id: innerLines model: 4 Item { id: innerHair z: index % 2 // Vertical lines above horizontal lines required property int index property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0 property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength) visible: root.innerLines && hairLength > 0 anchors.fill: parent rotation: index * 90 Rectangle { x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection y: parent.height / 2 - height / 2 color: root.innerLineColor width: innerHair.hairLength + root.borderWidth * 2 height: root.innerLineThickness + root.borderWidth * 2 border.width: root.borderWidth border.color: root.borderColor } } } Repeater { id: outerLines model: 4 Item { id: outerHair z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines required property int index property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0 property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength) visible: root.outerLines && hairLength > 0 anchors.fill: parent rotation: index * 90 Rectangle { x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection y: parent.height / 2 - height / 2 color: root.outerLineColor width: hairLength + root.borderWidth * 2 height: root.outerLineThickness + root.borderWidth * 2 border.width: root.borderWidth border.color: root.borderColor } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/floatingImage/FloatingImage.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import Quickshell import Qt5Compat.GraphicalEffects import qs.modules.common import qs.modules.common.functions import qs.modules.common.utils import qs.modules.ii.overlay StyledOverlayWidget { id: root showClickabilityButton: false resizable: false clickthrough: true property string imageSource: Config.options.overlay.floatingImage.imageSource property real scaleFactor: Config.options.overlay.floatingImage.scale property int imageWidth: 0 property int imageHeight: 0 // Override to always save 0 size function savePosition(xPos = root.x, yPos = root.y, width = 0, height = 0) { root.persistentStateEntry.x = Math.round(xPos); root.persistentStateEntry.y = Math.round(yPos); root.persistentStateEntry.width = 0 root.persistentStateEntry.height = 0 } onImageSourceChanged: { imageDownloader.running = false; imageDownloader.sourceUrl = root.imageSource; imageDownloader.filePath = Qt.resolvedUrl(Directories.tempImages + "/" + Qt.md5(root.imageSource)) imageDownloader.running = true; } onScaleFactorChanged: { setSize(); } function setSize() { bg.implicitWidth = root.imageWidth * root.scaleFactor; bg.implicitHeight = root.imageHeight * root.scaleFactor; } contentItem: OverlayBackground { id: bg color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer, root.actuallyPinned ? 1 : 0) radius: root.contentRadius WheelHandler { onWheel: (event) => { if (event.angleDelta.y < 0) { Config.options.overlay.floatingImage.scale = Math.max(0.1, Config.options.overlay.floatingImage.scale - 0.1); } else if (event.angleDelta.y > 0) { Config.options.overlay.floatingImage.scale = Math.min(5.0, Config.options.overlay.floatingImage.scale + 0.1); } } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: bg.width height: bg.height radius: bg.radius } } AnimatedImage { id: animatedImage anchors.centerIn: parent width: root.imageWidth * root.scaleFactor height: root.imageHeight * root.scaleFactor sourceSize: { const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1; return Qt.size(width * dpr, height * dpr); } playing: visible asynchronous: true source: "" ImageDownloaderProcess { id: imageDownloader filePath: Qt.resolvedUrl(Directories.tempImages + "/" + Qt.md5(root.imageSource)) sourceUrl: root.imageSource onDone: (path, width, height) => { root.imageWidth = width; root.imageHeight = height; root.setSize(); animatedImage.source = path; } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/fpsLimiter/FpsLimiter.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.ii.overlay StyledOverlayWidget { id: root title: "MangoHud FPS" minimumWidth: 275 minimumHeight: 100 contentItem: FpsLimiterContent { radius: root.contentRadius } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/fpsLimiter/FpsLimiterContent.qml ================================================ import qs.services import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Io import qs.modules.common import qs.modules.common.widgets import qs.modules.ii.overlay OverlayBackground { id: root enum State { Normal, Success, Error } property real padding: 16 property var currentState: FpsLimiterContent.State.Normal implicitWidth: content.implicitWidth + (padding * 2) implicitHeight: content.implicitHeight + (padding * 2) Timer { id: iconResetTimer interval: 1000 onTriggered: { root.currentState = FpsLimiterContent.State.Normal; } } function applyLimit() { var fpsValue = parseInt(fpsField.text); if (isNaN(fpsValue) || fpsValue < 0) { root.currentState = FpsLimiterContent.State.Error; iconResetTimer.restart(); fpsField.text = ""; return; } var cfgPaths = [ "~/.config/MangoHud/MangoHud.conf", ]; // MangoHud config files var updateCommands = cfgPaths.map(path => { return "if grep -q '^fps_limit=' " + path + "; " + "then sed -i 's/^fps_limit=.*/fps_limit=" + fpsValue + "/' " + path + "; " + "else echo 'fps_limit=" + fpsValue + "' >> " + path + "; fi"; }).join("; "); var cmd = updateCommands + "; pkill -SIGUSR2 mangohud"; fpsSetter.command = ["bash", "-c", cmd]; fpsSetter.startDetached(); root.currentState = FpsLimiterContent.State.Success; iconResetTimer.restart(); // Clear the field after applying fpsField.text = ""; } Process { id: fpsSetter } RowLayout { id: content anchors.centerIn: parent spacing: 4 ToolbarTextField { id: fpsField Layout.fillWidth: true Layout.preferredWidth: 200 placeholderText: root.currentState === FpsLimiterContent.State.Error ? Translation.tr("Enter a valid number") : Translation.tr("Set FPS limit") inputMethodHints: Qt.ImhDigitsOnly focus: true onAccepted: { root.applyLimit(); } } IconToolbarButton { id: applyButton text: switch (root.currentState) { case FpsLimiterContent.State.Error: return "close"; case FpsLimiterContent.State.Success: return "check"; case FpsLimiterContent.State.Normal: default: return "save"; } enabled: root.currentState === FpsLimiterContent.State.Normal && fpsField.text.length > 0 onClicked: { root.applyLimit(); } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/notes/Notes.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.ii.overlay StyledOverlayWidget { id: root title: Translation.tr("Notes") showCenterButton: true contentItem: NotesContent { radius: root.contentRadius isClickthrough: root.clickthrough } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/notes/NotesContent.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.ii.overlay OverlayBackground { id: root property alias content: textInput.text property bool pendingReload: false property var copyListEntries: [] property string lastParsedCopylistText: "" property var parsedCopylistLines: [] property bool isClickthrough: false property real maxCopyButtonSize: 20 Component.onCompleted: { noteFile.reload(); updateCopyListEntries(); } function saveContent() { if (!textInput) return; noteFile.setText(root.content); } function focusAtEnd() { if (!textInput) return; textInput.forceActiveFocus(); const endPos = root.content.length; applySelection(endPos, endPos); } function applySelection(cursorPos, anchorPos) { if (!textInput) return; const textLength = root.content.length; const cursor = Math.max(0, Math.min(cursorPos, textLength)); const anchor = Math.max(0, Math.min(anchorPos, textLength)); textInput.select(anchor, cursor); if (cursor === anchor) textInput.deselect(); } function scheduleCopylistUpdate(immediate = false) { if (!textInput) return; if (immediate) { copyListDebounce?.stop(); updateCopyListEntries(); } else { copyListDebounce.restart(); } } function updateCopyListEntries() { if (!textInput) return; const textValue = root.content; if (!textValue || textValue.length === 0) { lastParsedCopylistText = ""; parsedCopylistLines = []; root.copyListEntries = []; return; } if (textValue !== lastParsedCopylistText) { const lineRegex = /(.*?)(\r?\n|$)/g; let match = null; const parsed = []; while ((match = lineRegex.exec(textValue)) !== null) { const lineText = match[1]; const newlineText = match[2]; const lineStart = match.index; const lineEnd = lineStart + lineText.length; const bulletMatch = lineText.match(/^\s*-\s+(.*\S)\s*$/); if (bulletMatch) { parsed.push({ content: bulletMatch[1].trim(), start: lineStart, end: lineEnd }); } if (newlineText === "") break; } lastParsedCopylistText = textValue; parsedCopylistLines = parsed; if (parsed.length === 0) { root.copyListEntries = []; return; } } updateCopylistPositions(); } function updateCopylistPositions() { if (!textInput || parsedCopylistLines.length === 0) return; const rawSelectionStart = textInput.selectionStart; const rawSelectionEnd = textInput.selectionEnd; const selectionStart = rawSelectionStart === -1 ? textInput.cursorPosition : rawSelectionStart; const selectionEnd = rawSelectionEnd === -1 ? textInput.cursorPosition : rawSelectionEnd; const rangeStart = Math.min(selectionStart, selectionEnd); const rangeEnd = Math.max(selectionStart, selectionEnd); const entries = parsedCopylistLines.map(line => { // Don't show copy button if line is (partially) selected const caretIntersects = rangeEnd > line.start && rangeStart <= line.end; if (caretIntersects) return null; const startRect = textInput.positionToRectangle(line.start); let endRect = textInput.positionToRectangle(line.end); if (!isFinite(startRect.y)) return null; if (!isFinite(endRect.y)) endRect = startRect; const lineBottom = endRect.y + endRect.height; const rectHeight = Math.max(lineBottom - startRect.y, textInput.font.pixelSize + 8); return { content: line.content, y: startRect.y, height: rectHeight }; }).filter(entry => entry !== null); root.copyListEntries = entries; } implicitWidth: 300 implicitHeight: 200 ColumnLayout { id: contentItem anchors.fill: parent spacing: -16 ScrollView { id: editorScrollView Layout.fillWidth: true Layout.fillHeight: true clip: true ScrollBar.vertical.policy: ScrollBar.AsNeeded onWidthChanged: root.scheduleCopylistUpdate(true) StyledTextArea { // This has to be a direct child of ScrollView for proper scrolling id: textInput anchors { left: parent.left right: parent.right } wrapMode: TextEdit.Wrap placeholderText: Translation.tr("Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep") selectByMouse: true persistentSelection: true textFormat: TextEdit.PlainText background: null padding: 24 onTextChanged: { if (textInput.activeFocus) { saveDebounce.restart(); } root.scheduleCopylistUpdate(true); } onHeightChanged: root.scheduleCopylistUpdate(true) onContentHeightChanged: root.scheduleCopylistUpdate(true) onCursorPositionChanged: root.scheduleCopylistUpdate() onSelectionStartChanged: root.scheduleCopylistUpdate() onSelectionEndChanged: root.scheduleCopylistUpdate() } Item { anchors.fill: parent visible: root.copyListEntries.length > 0 clip: true Repeater { model: ScriptModel { values: root.copyListEntries } delegate: RippleButton { id: copyButton required property var modelData readonly property real lineHeight: Math.min(Math.max(modelData.height, Appearance.font.pixelSize.normal + 6), root.maxCopyButtonSize) readonly property real iconSizeLocal: Appearance.font.pixelSize.normal readonly property real hitPadding: 6 property bool justCopied: false implicitHeight: lineHeight implicitWidth: lineHeight buttonRadius: height / 2 y: modelData.y anchors.right: parent.right anchors.rightMargin: 10 z: 5 Timer { id: resetState interval: 700 onTriggered: { copyButton.justCopied = false; } } onClicked: { Quickshell.clipboardText = copyButton.modelData.content; justCopied = true; resetState.start(); } contentItem: Item { anchors.centerIn: parent MaterialSymbol { id: iconItem anchors.centerIn: parent text: copyButton.justCopied ? "check" : "content_copy" iconSize: copyButton.iconSizeLocal color: Appearance.colors.colOnLayer1 } } } } } } StyledText { id: statusLabel Layout.fillWidth: true Layout.margins: 16 horizontalAlignment: Text.AlignRight text: saveDebounce.running ? Translation.tr("Saving...") : Translation.tr("Saved ") color: Appearance.colors.colSubtext } } Timer { id: saveDebounce interval: 500 repeat: false onTriggered: saveContent() } Timer { id: copyListDebounce interval: 100 repeat: false onTriggered: updateCopylistPositions() } FileView { id: noteFile path: Qt.resolvedUrl(Directories.notesPath) onLoaded: { root.content = noteFile.text(); if (root.content !== root.content) { const previousCursor = textInput.cursorPosition; const previousAnchor = textInput.selectionStart; root.content = root.content; applySelection(previousCursor, previousAnchor); } if (pendingReload) { pendingReload = false; Qt.callLater(root.focusAtEnd); } Qt.callLater(root.updateCopyListEntries); } onLoadFailed: error => { if (error === FileViewError.FileNotFound) { root.content = ""; noteFile.setText(root.content); if (pendingReload) { pendingReload = false; Qt.callLater(root.focusAtEnd); } Qt.callLater(root.updateCopyListEntries); } else { console.log("[Overlay Notes] Error loading file: " + error); } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/recorder/Recorder.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.ii.overlay StyledOverlayWidget { id: root minimumWidth: 310 minimumHeight: 130 contentItem: OverlayBackground { id: contentItem radius: root.contentRadius property real padding: 8 ColumnLayout { id: contentColumn anchors.centerIn: parent spacing: 10 Row { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 10 BigRecorderButton { materialSymbol: "screenshot_region" name: "Screenshot region" onClicked: { GlobalStates.overlayOpen = false; Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "screenshot"]); } } BigRecorderButton { materialSymbol: "photo_camera" name: "Screenshot" onClicked: { GlobalStates.overlayOpen = false; Quickshell.execDetached(["bash", "-c", "grim - | wl-copy"]); } } BigRecorderButton { materialSymbol: "screen_record" name: "Record region" onClicked: { GlobalStates.overlayOpen = false; Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "recordWithSound"]); } } BigRecorderButton { materialSymbol: "capture" name: "Record screen" onClicked: { GlobalStates.overlayOpen = false; Quickshell.execDetached([Directories.recordScriptPath, "--fullscreen", "--sound"]); } } } RippleButton { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.fillWidth: false buttonRadius: height / 2 colBackground: Appearance.colors.colLayer3 colBackgroundHover: Appearance.colors.colLayer3Hover colRipple: Appearance.colors.colLayer3Active onClicked: { GlobalStates.overlayOpen = false; Qt.openUrlExternally(`file://${Config.options.screenRecord.savePath}`); } contentItem: Row { anchors.centerIn: parent spacing: 6 MaterialSymbol { anchors.verticalCenter: parent.verticalCenter text: "animated_images" iconSize: 20 } StyledText { anchors.verticalCenter: parent.verticalCenter text: Translation.tr("Open recordings folder") } } } } } component BigRecorderButton: RippleButton { id: bigButton required property string materialSymbol required property string name implicitHeight: 66 implicitWidth: 66 buttonRadius: height / 2 colBackground: Appearance.colors.colLayer3 colBackgroundHover: Appearance.colors.colLayer3Hover colRipple: Appearance.colors.colLayer3Active contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: bigButton.materialSymbol iconSize: 28 } StyledToolTip { text: bigButton.name } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/resources/Resources.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Qt5Compat.GraphicalEffects import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.ii.overlay StyledOverlayWidget { id: root minimumWidth: 300 minimumHeight: 200 property list resources: [ { "icon": "planner_review", "name": Translation.tr("CPU"), "history": ResourceUsage.cpuUsageHistory, "maxAvailableString": ResourceUsage.maxAvailableCpuString }, { "icon": "memory", "name": Translation.tr("RAM"), "history": ResourceUsage.memoryUsageHistory, "maxAvailableString": ResourceUsage.maxAvailableMemoryString }, { "icon": "swap_horiz", "name": Translation.tr("Swap"), "history": ResourceUsage.swapUsageHistory, "maxAvailableString": ResourceUsage.maxAvailableSwapString }, ] contentItem: OverlayBackground { id: contentItem radius: root.contentRadius property real padding: 4 ColumnLayout { id: contentColumn anchors { fill: parent margins: parent.padding } spacing: 8 SecondaryTabBar { id: tabBar currentIndex: Persistent.states.overlay.resources.tabIndex onCurrentIndexChanged: { Persistent.states.overlay.resources.tabIndex = tabBar.currentIndex; } Repeater { model: root.resources.length delegate: SecondaryTabButton { required property int index property var modelData: root.resources[index] buttonIcon: modelData.icon buttonText: modelData.name } } } ResourceSummary { Layout.margins: 8 history: root.resources[tabBar.currentIndex]?.history ?? [] maxAvailableString: root.resources[tabBar.currentIndex]?.maxAvailableString ?? "--" } } } component ResourceSummary: RowLayout { id: resourceSummary required property list history required property string maxAvailableString Layout.fillWidth: true Layout.fillHeight: true spacing: 12 ColumnLayout { spacing: 2 StyledText { text: (resourceSummary.history[resourceSummary.history.length - 1] * 100).toFixed(1) + "%" font { family: Appearance.font.family.numbers variableAxes: Appearance.font.variableAxes.numbers pixelSize: Appearance.font.pixelSize.huge } } StyledText { text: Translation.tr("of %1").arg(resourceSummary.maxAvailableString) font { // family: Appearance.font.family.numbers // variableAxes: Appearance.font.variableAxes.numbers pixelSize: Appearance.font.pixelSize.smallie } color: Appearance.colors.colSubtext } Item { Layout.fillHeight: true } } Rectangle { id: graphBg Layout.fillWidth: true Layout.fillHeight: true radius: Appearance.rounding.small color: Appearance.colors.colSecondaryContainer layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: graphBg.width height: graphBg.height radius: graphBg.radius } } Graph { anchors.fill: parent values: root.resources[tabBar.currentIndex]?.history ?? [] points: ResourceUsage.historyLength alignment: Graph.Alignment.Right } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overlay/volumeMixer/VolumeMixer.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.ii.overlay import qs.modules.ii.sidebarRight.volumeMixer StyledOverlayWidget { id: root minimumWidth: 300 minimumHeight: 380 contentItem: OverlayBackground { radius: root.contentRadius property real padding: 6 ColumnLayout { id: contentColumn anchors { fill: parent margins: parent.padding } spacing: 8 SecondaryTabBar { id: tabBar currentIndex: Persistent.states.overlay.volumeMixer.tabIndex onCurrentIndexChanged: { Persistent.states.overlay.volumeMixer.tabIndex = tabBar.currentIndex; } SecondaryTabButton { buttonIcon: "media_output" buttonText: Translation.tr("Output") } SecondaryTabButton { buttonIcon: "mic" buttonText: Translation.tr("Input") } } SwipeView { id: swipeView Layout.fillWidth: true Layout.fillHeight: true currentIndex: Persistent.states.overlay.volumeMixer.tabIndex onCurrentIndexChanged: { Persistent.states.overlay.volumeMixer.tabIndex = swipeView.currentIndex; } clip: true PaddedVolumeDialogContent { isSink: true } PaddedVolumeDialogContent { isSink: false } } } } component PaddedVolumeDialogContent: Item { id: paddedVolumeDialogContent property alias isSink: volDialogContent.isSink property real padding: 12 implicitWidth: volDialogContent.implicitWidth + padding * 2 implicitHeight: volDialogContent.implicitHeight + padding * 2 VolumeDialogContent { id: volDialogContent anchors { fill: parent margins: paddedVolumeDialogContent.padding } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/Overview.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import Qt.labs.synchronizer import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: overviewScope property bool dontAutoCancelSearch: false PanelWindow { id: panelWindow property string searchingText: "" readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) visible: GlobalStates.overviewOpen WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Top WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" mask: Region { item: GlobalStates.overviewOpen ? columnLayout : null } anchors { top: true bottom: true left: true right: true } Connections { target: GlobalStates function onOverviewOpenChanged() { if (!GlobalStates.overviewOpen) { searchWidget.disableExpandAnimation(); overviewScope.dontAutoCancelSearch = false; GlobalFocusGrab.dismiss(); } else { if (!overviewScope.dontAutoCancelSearch) { searchWidget.cancelSearch(); } GlobalFocusGrab.addDismissable(panelWindow); } } } Connections { target: GlobalFocusGrab function onDismissed() { GlobalStates.overviewOpen = false; } } implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight function setSearchingText(text) { searchWidget.setSearchingText(text); searchWidget.focusFirstItem(); } Column { id: columnLayout visible: GlobalStates.overviewOpen anchors { horizontalCenter: parent.horizontalCenter top: parent.top } spacing: -8 Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { GlobalStates.overviewOpen = false; } } SearchWidget { id: searchWidget anchors.horizontalCenter: parent.horizontalCenter Synchronizer on searchingText { property alias source: panelWindow.searchingText } } Loader { id: overviewLoader anchors.horizontalCenter: parent.horizontalCenter active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true) sourceComponent: OverviewWidget { screen: panelWindow.screen visible: (panelWindow.searchingText == "") } } } } function toggleClipboard() { if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { GlobalStates.overviewOpen = false; return; } overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText(Config.options.search.prefix.clipboard); GlobalStates.overviewOpen = true; } function toggleEmojis() { if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { GlobalStates.overviewOpen = false; return; } overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText(Config.options.search.prefix.emojis); GlobalStates.overviewOpen = true; } IpcHandler { target: "search" function toggle() { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } function workspacesToggle() { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } function close() { GlobalStates.overviewOpen = false; } function open() { GlobalStates.overviewOpen = true; } function toggleReleaseInterrupt() { GlobalStates.superReleaseMightTrigger = false; } function clipboardToggle() { overviewScope.toggleClipboard(); } } GlobalShortcut { name: "searchToggle" description: "Toggles search on press" onPressed: { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } GlobalShortcut { name: "overviewWorkspacesClose" description: "Closes overview on press" onPressed: { GlobalStates.overviewOpen = false; } } GlobalShortcut { name: "overviewWorkspacesToggle" description: "Toggles overview on press" onPressed: { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } GlobalShortcut { name: "searchToggleRelease" description: "Toggles search on release" onPressed: { GlobalStates.superReleaseMightTrigger = true; } onReleased: { if (!GlobalStates.superReleaseMightTrigger) { GlobalStates.superReleaseMightTrigger = true; return; } GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } GlobalShortcut { name: "searchToggleReleaseInterrupt" description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." onPressed: { GlobalStates.superReleaseMightTrigger = false; } } GlobalShortcut { name: "overviewClipboardToggle" description: "Toggle clipboard query on overview widget" onPressed: { overviewScope.toggleClipboard(); } } GlobalShortcut { name: "overviewEmojiToggle" description: "Toggle emoji query on overview widget" onPressed: { overviewScope.toggleEmojis(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/OverviewWidget.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Item { id: root required property var screen readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen) readonly property var toplevels: ToplevelManager.toplevels // Clamp to avoid lock-screen temp workspace (2147483647 - N) leaking into UI readonly property int effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1)) readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / workspacesShown) property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name) property var windows: HyprlandData.windowList property var windowByAddress: HyprlandData.windowByAddress property var windowAddresses: HyprlandData.addresses property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id) property real scale: Config.options.overview.scale property color activeBorderColor: Appearance.colors.colSecondary property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) property real largeWorkspaceRadius: Appearance.rounding.large property real smallWorkspaceRadius: Appearance.rounding.verysmall property real workspaceNumberMargin: 80 property real workspaceNumberSize: 250 * monitor.scale property int workspaceZ: 0 property int windowZ: 1 property int windowDraggingZ: 99999 property real workspaceSpacing: 5 property int draggingFromWorkspace: -1 property int draggingTargetWorkspace: -1 implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 property Component windowComponent: OverviewWindow {} property list windowWidgets: [] function getWsRow(ws) { // 1-indexed workspace, 0-indexed row var normalRow = Math.floor((ws - 1) / Config.options.overview.columns) % Config.options.overview.rows; return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - normalRow - 1 : normalRow); } function getWsColumn(ws) { // 1-indexed workspace, 0-indexed column var normalCol = (ws - 1) % Config.options.overview.columns; return (Config.options.overview.orderRightLeft ? Config.options.overview.columns - normalCol - 1 : normalCol); } function getWsInCell(ri, ci) { // 1-indexed workspace, 0-indexed row and column index return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - ri - 1 : ri) * Config.options.overview.columns + (Config.options.overview.orderRightLeft ? Config.options.overview.columns - ci - 1 : ci) + 1 } StyledRectangularShadow { target: overviewBackground } Rectangle { // Background id: overviewBackground property real padding: 10 anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 radius: root.largeWorkspaceRadius + padding color: Appearance.colors.colBackgroundSurfaceContainer Column { // Workspaces id: workspaceColumnLayout z: root.workspaceZ anchors.centerIn: parent spacing: workspaceSpacing Repeater { model: Config.options.overview.rows delegate: Row { id: row required property int index spacing: workspaceSpacing Repeater { // Workspace repeater model: Config.options.overview.columns Rectangle { // Workspace id: workspace required property int index property int colIndex: index property int workspaceValue: root.workspaceGroup * root.workspacesShown + getWsInCell(row.index, colIndex) property color defaultWorkspaceColor: Appearance.colors.colSurfaceContainerLow property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover property bool hoveredWhileDragging: false implicitWidth: root.workspaceImplicitWidth implicitHeight: root.workspaceImplicitHeight color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor property bool workspaceAtLeft: colIndex === 0 property bool workspaceAtRight: colIndex === Config.options.overview.columns - 1 property bool workspaceAtTop: row.index === 0 property bool workspaceAtBottom: row.index === Config.options.overview.rows - 1 topLeftRadius: (workspaceAtLeft && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius topRightRadius: (workspaceAtRight && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius bottomLeftRadius: (workspaceAtLeft && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius bottomRightRadius: (workspaceAtRight && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius border.width: 2 border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent" StyledText { anchors.centerIn: parent text: workspace.workspaceValue font { pixelSize: root.workspaceNumberSize * root.scale weight: Font.DemiBold family: Appearance.font.family.expressive } color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { id: workspaceArea anchors.fill: parent acceptedButtons: Qt.LeftButton onPressed: { if (root.draggingTargetWorkspace === -1) { GlobalStates.overviewOpen = false Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspace.workspaceValue} })`) } } } DropArea { anchors.fill: parent onEntered: { root.draggingTargetWorkspace = workspace.workspaceValue if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; hoveredWhileDragging = true } onExited: { hoveredWhileDragging = false if (root.draggingTargetWorkspace == workspace.workspaceValue) root.draggingTargetWorkspace = -1 } } } } } } } Item { // Windows & focused workspace indicator id: windowSpace anchors.centerIn: parent implicitWidth: workspaceColumnLayout.implicitWidth implicitHeight: workspaceColumnLayout.implicitHeight Repeater { // Window repeater model: ScriptModel { values: { // console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2)) return ToplevelManager.toplevels.values.filter((toplevel) => { const address = `0x${toplevel.HyprlandToplevel?.address}` var win = windowByAddress[address] const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) return inWorkspaceGroup; }) } } delegate: OverviewWindow { id: window required property var modelData property int monitorId: windowData?.monitor property var monitor: HyprlandData.monitors.find(m => m.id == monitorId) property var address: `0x${modelData.HyprlandToplevel.address}` toplevel: modelData monitorData: this.monitor scale: root.scale widgetMonitor: HyprlandData.monitors.find(m => m.id == root.monitor.id) windowData: windowByAddress[address] property bool atInitPosition: (initX == x && initY == y) // Offset on the canvas property int workspaceColIndex: getWsColumn(windowData?.workspace.id) property int workspaceRowIndex: getWsRow(windowData?.workspace.id) xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex property real xWithinWorkspaceWidget: Math.max((windowData?.at[0] - (monitor?.x ?? 0) - monitorData?.reserved[0]) * root.scale, 0) property real yWithinWorkspaceWidget: Math.max((windowData?.at[1] - (monitor?.y ?? 0) - monitorData?.reserved[1]) * root.scale, 0) // Radius property real minRadius: Appearance.rounding.small property bool workspaceAtLeft: workspaceColIndex === 0 property bool workspaceAtRight: workspaceColIndex === Config.options.overview.columns - 1 property bool workspaceAtTop: workspaceRowIndex === 0 property bool workspaceAtBottom: workspaceRowIndex === Config.options.overview.rows - 1 property bool workspaceAtTopLeft: (workspaceAtLeft && workspaceAtTop) property bool workspaceAtTopRight: (workspaceAtRight && workspaceAtTop) property bool workspaceAtBottomLeft: (workspaceAtLeft && workspaceAtBottom) property bool workspaceAtBottomRight: (workspaceAtRight && workspaceAtBottom) property real distanceFromLeftEdge: xWithinWorkspaceWidget property real distanceFromRightEdge: root.workspaceImplicitWidth - (xWithinWorkspaceWidget + targetWindowWidth) property real distanceFromTopEdge: yWithinWorkspaceWidget property real distanceFromBottomEdge: root.workspaceImplicitHeight - (yWithinWorkspaceWidget + targetWindowHeight) property real distanceFromTopLeftCorner: Math.max(distanceFromLeftEdge, distanceFromTopEdge) property real distanceFromTopRightCorner: Math.max(distanceFromRightEdge, distanceFromTopEdge) property real distanceFromBottomLeftCorner: Math.max(distanceFromLeftEdge, distanceFromBottomEdge) property real distanceFromBottomRightCorner: Math.max(distanceFromRightEdge, distanceFromBottomEdge) topLeftRadius: Math.max((workspaceAtTopLeft ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromTopLeftCorner, minRadius) topRightRadius: Math.max((workspaceAtTopRight ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromTopRightCorner, minRadius) bottomLeftRadius: Math.max((workspaceAtBottomLeft ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromBottomLeftCorner, minRadius) bottomRightRadius: Math.max((workspaceAtBottomRight ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromBottomRightCorner, minRadius) Timer { id: updateWindowPosition interval: Config.options.hacks.arbitraryRaceConditionDelay repeat: false running: false onTriggered: { window.x = Math.round(xWithinWorkspaceWidget + xOffset) window.y = Math.round(yWithinWorkspaceWidget + yOffset) } } z: Drag.active ? root.windowDraggingZ : (root.windowZ + windowData?.floating + windowData?.fullscreen * 2) Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: dragArea anchors.fill: parent hoverEnabled: true onEntered: hovered = true // For hover color change onExited: hovered = false // For hover color change acceptedButtons: Qt.LeftButton | Qt.MiddleButton drag.target: parent onPressed: (mouse) => { root.draggingFromWorkspace = windowData?.workspace.id window.pressed = true window.Drag.active = true window.Drag.source = window window.Drag.hotSpot.x = mouse.x window.Drag.hotSpot.y = mouse.y // console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`) } onReleased: { const targetWorkspace = root.draggingTargetWorkspace window.pressed = false window.Drag.active = false root.draggingFromWorkspace = -1 if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${targetWorkspace}, follow = false, window = "address:${window.windowData?.address}" })`) updateWindowPosition.restart() } else { if (!window.windowData.floating) { updateWindowPosition.restart() return } const percentageX = (window.x - xOffset) / root.workspaceImplicitWidth const percentageY = (window.y - yOffset) / root.workspaceImplicitHeight Hyprland.dispatch(`hl.dsp.window.move({ x = "${percentageX * root.screen.width}", y = "${percentageY * root.screen.height}", window = "address:${window.windowData?.address}" })`) } } onClicked: (event) => { if (!windowData) return; if (event.button === Qt.LeftButton) { GlobalStates.overviewOpen = false Hyprland.dispatch(`hl.dsp.focus({window = "address:${windowData.address}"})`) event.accepted = true } else if (event.button === Qt.MiddleButton) { Hyprland.dispatch(`hl.dsp.window.close({window = "address:${windowData.address}"})`) event.accepted = true } } StyledToolTip { extraVisibleCondition: false alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active text: `${windowData?.title}\n[${windowData?.class}] ${windowData?.xwayland ? "[XWayland] " : ""}` } } } } Rectangle { // Focused workspace indicator id: focusedWorkspaceIndicator property int rowIndex: getWsRow(root.effectiveActiveWorkspaceId) property int colIndex: getWsColumn(root.effectiveActiveWorkspaceId) x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex z: root.windowZ width: root.workspaceImplicitWidth height: root.workspaceImplicitHeight color: "transparent" property bool workspaceAtLeft: colIndex === 0 property bool workspaceAtRight: colIndex === Config.options.overview.columns - 1 property bool workspaceAtTop: rowIndex === 0 property bool workspaceAtBottom: rowIndex === Config.options.overview.rows - 1 topLeftRadius: (workspaceAtLeft && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius topRightRadius: (workspaceAtRight && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius bottomLeftRadius: (workspaceAtLeft && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius bottomRightRadius: (workspaceAtRight && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius border.width: 2 border.color: root.activeBorderColor Behavior on x { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on y { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on topLeftRadius { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on topRightRadius { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on bottomLeftRadius { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on bottomRightRadius { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/OverviewWindow.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland Item { // Window id: root property var toplevel property var windowData property var monitorData property var scale property bool restrictToWorkspace: true property real widthRatio: { const widgetWidth = widgetMonitor.transform & 1 ? widgetMonitor.height : widgetMonitor.width; const monitorWidth = monitorData.transform & 1 ? monitorData.height : monitorData.width; return (widgetWidth * monitorData.scale) / (monitorWidth * widgetMonitor.scale); } property real heightRatio: { const widgetHeight = widgetMonitor.transform & 1 ? widgetMonitor.width : widgetMonitor.height; const monitorHeight = monitorData.transform & 1 ? monitorData.width : monitorData.height; return (widgetHeight * monitorData.scale) / (monitorHeight * widgetMonitor.scale); } property real initX: { return Math.max((windowData?.at[0] - (monitorData?.x ?? 0) - monitorData?.reserved[0]) * widthRatio * root.scale, 0) + xOffset; } property real initY: { return Math.max((windowData?.at[1] - (monitorData?.y ?? 0) - monitorData?.reserved[1]) * heightRatio * root.scale, 0) + yOffset; } property real xOffset: 0 property real yOffset: 0 property var widgetMonitor property int widgetMonitorId: widgetMonitor.id property var targetWindowWidth: windowData?.size[0] * scale * widthRatio property var targetWindowHeight: windowData?.size[1] * scale * heightRatio property bool hovered: false property bool pressed: false property bool centerIcons: Config.options.overview.centerIcons property real iconGapRatio: 0.06 property real iconToWindowRatio: centerIcons ? 0.35 : 0.15 property real xwaylandIndicatorToIconRatio: 0.35 property real iconToWindowRatioCompact: 0.6 property string iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth property bool indicateXWayland: windowData?.xwayland ?? false x: initX y: initY width: targetWindowWidth height: targetWindowHeight opacity: windowData.monitor == widgetMonitorId ? 1 : 0.4 property real topLeftRadius property real topRightRadius property real bottomLeftRadius property real bottomRightRadius layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: root.width height: root.height topLeftRadius: root.topLeftRadius topRightRadius: root.topRightRadius bottomRightRadius: root.bottomRightRadius bottomLeftRadius: root.bottomLeftRadius } } Behavior on x { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on y { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on width { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on height { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } ScreencopyView { id: windowPreview anchors.fill: parent captureSource: GlobalStates.overviewOpen ? root.toplevel : null live: true // Color overlay for interactions Rectangle { anchors.fill: parent topLeftRadius: root.topLeftRadius topRightRadius: root.topRightRadius bottomRightRadius: root.bottomRightRadius bottomLeftRadius: root.bottomLeftRadius color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) : hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) : ColorUtils.transparentize(Appearance.colors.colLayer2) border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.88) border.width : 1 } StyledImage { id: windowIcon property real baseSize: Math.min(root.targetWindowWidth, root.targetWindowHeight) anchors { top: root.centerIcons ? undefined : parent.top left: root.centerIcons ? undefined : parent.left centerIn: root.centerIcons ? parent : undefined margins: baseSize * root.iconGapRatio } property var iconSize: { // console.log("-=-=-", root.toplevel.title, "-=-=-") // console.log("Target window size:", targetWindowWidth, targetWindowHeight) // console.log("Icon ratio:", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) // console.log("Scale:", root.monitorData.scale) // console.log("Final:", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale) return baseSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio); } mipmap: true Layout.alignment: Qt.AlignHCenter source: root.iconPath width: iconSize height: iconSize Behavior on width { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on height { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/SearchBar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions RowLayout { id: root spacing: 6 property bool animateWidth: false property alias searchInput: searchInput property string searchingText function forceFocus() { searchInput.forceActiveFocus(); } enum SearchPrefixType { Action, App, Clipboard, Emojis, Math, ShellCommand, WebSearch, DefaultSearch } property var searchPrefixType: { if (root.searchingText.startsWith(Config.options.search.prefix.action)) return SearchBar.SearchPrefixType.Action; if (root.searchingText.startsWith(Config.options.search.prefix.app)) return SearchBar.SearchPrefixType.App; if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) return SearchBar.SearchPrefixType.Clipboard; if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) return SearchBar.SearchPrefixType.Emojis; if (root.searchingText.startsWith(Config.options.search.prefix.math)) return SearchBar.SearchPrefixType.Math; if (root.searchingText.startsWith(Config.options.search.prefix.shellCommand)) return SearchBar.SearchPrefixType.ShellCommand; if (root.searchingText.startsWith(Config.options.search.prefix.webSearch)) return SearchBar.SearchPrefixType.WebSearch; return SearchBar.SearchPrefixType.DefaultSearch; } MaterialShapeWrappedMaterialSymbol { id: searchIcon Layout.alignment: Qt.AlignVCenter iconSize: Appearance.font.pixelSize.huge shape: switch(root.searchPrefixType) { case SearchBar.SearchPrefixType.Action: return MaterialShape.Shape.Pill; case SearchBar.SearchPrefixType.App: return MaterialShape.Shape.Clover4Leaf; case SearchBar.SearchPrefixType.Clipboard: return MaterialShape.Shape.Gem; case SearchBar.SearchPrefixType.Emojis: return MaterialShape.Shape.Sunny; case SearchBar.SearchPrefixType.Math: return MaterialShape.Shape.PuffyDiamond; case SearchBar.SearchPrefixType.ShellCommand: return MaterialShape.Shape.PixelCircle; case SearchBar.SearchPrefixType.WebSearch: return MaterialShape.Shape.SoftBurst; default: return MaterialShape.Shape.Cookie7Sided; } text: switch (root.searchPrefixType) { case SearchBar.SearchPrefixType.Action: return "settings_suggest"; case SearchBar.SearchPrefixType.App: return "apps"; case SearchBar.SearchPrefixType.Clipboard: return "content_paste_search"; case SearchBar.SearchPrefixType.Emojis: return "add_reaction"; case SearchBar.SearchPrefixType.Math: return "calculate"; case SearchBar.SearchPrefixType.ShellCommand: return "terminal"; case SearchBar.SearchPrefixType.WebSearch: return "travel_explore"; case SearchBar.SearchPrefixType.DefaultSearch: return "search"; default: return "search"; } } ToolbarTextField { // Search box id: searchInput Layout.topMargin: 4 Layout.bottomMargin: 4 implicitHeight: 40 focus: GlobalStates.overviewOpen font.pixelSize: Appearance.font.pixelSize.small placeholderText: Translation.tr("Search, calculate or run") implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth Behavior on implicitWidth { id: searchWidthBehavior enabled: root.animateWidth NumberAnimation { duration: 300 easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } onTextChanged: LauncherSearch.query = text onAccepted: { if (appResults.count > 0) { // Get the first visible delegate and trigger its click let firstItem = appResults.itemAtIndex(0); if (firstItem && firstItem.clicked) { firstItem.clicked(); } } } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { if (LauncherSearch.results.length === 0) return; const tabbedText = LauncherSearch.results[0].name; LauncherSearch.query = tabbedText; searchInput.text = tabbedText; event.accepted = true; } } } IconToolbarButton { Layout.topMargin: 4 Layout.bottomMargin: 4 onClicked: { GlobalStates.overviewOpen = false; Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "region", "search"]); } text: "image_search" StyledToolTip { text: Translation.tr("Google Lens") } } IconToolbarButton { id: songRecButton Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.rightMargin: 4 toggled: SongRec.running onClicked: SongRec.toggleRunning() text: "music_cast" StyledToolTip { text: Translation.tr("Recognize music") } colText: toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSurfaceVariant background: MaterialShape { RotationAnimation on rotation { running: songRecButton.toggled duration: 12000 easing.type: Easing.Linear loops: Animation.Infinite from: 0 to: 360 } shape: { if (songRecButton.down) { return songRecButton.toggled ? MaterialShape.Shape.Circle : MaterialShape.Shape.Square } else { return songRecButton.toggled ? MaterialShape.Shape.SoftBurst : MaterialShape.Shape.Circle } } color: { if (songRecButton.toggled) { return songRecButton.hovered ? Appearance.colors.colPrimaryHover : Appearance.colors.colPrimary } else { return songRecButton.hovered ? Appearance.colors.colSurfaceContainerHigh : ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh) } } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/SearchItem.qml ================================================ // pragma NativeMethodBehavior: AcceptThisObject import qs import qs.services import qs.modules.common import qs.modules.common.models import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets import Quickshell.Hyprland RippleButton { id: root property LauncherSearchResult entry property string query property bool entryShown: entry?.shown ?? true property string itemType: entry?.type ?? Translation.tr("App") property string itemName: entry?.name ?? "" property var iconType: entry?.iconType property string iconName: entry?.iconName ?? "" property var itemExecute: entry?.execute property var fontType: switch(entry?.fontType) { case LauncherSearchResult.FontType.Monospace: return "monospace" case LauncherSearchResult.FontType.Normal: return "main" default: return "main" } property string itemClickActionName: entry?.verb ?? "Open" property string bigText: entry?.iconType === LauncherSearchResult.IconType.Text ? entry?.iconName ?? "" : "" property string materialSymbol: entry.iconType === LauncherSearchResult.IconType.Material ? entry?.iconName ?? "" : "" property string cliphistRawString: entry?.rawValue ?? "" property bool blurImage: entry?.blurImage ?? false visible: root.entryShown property int horizontalMargin: 10 property int buttonHorizontalPadding: 10 property int buttonVerticalPadding: 6 property bool keyboardDown: false readonly property bool selected: (root.hovered || root.focus) implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2 implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2 buttonRadius: Appearance.rounding.normal colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colPrimaryContainerActive : (selected ? Appearance.colors.colPrimaryContainer : ColorUtils.transparentize(Appearance.colors.colPrimaryContainer, 1)) colBackgroundHover: Appearance.colors.colPrimaryContainer colRipple: Appearance.colors.colPrimaryContainerActive property color colForeground: selected ? Appearance.colors.colOnPrimaryContainer : Appearance.m3colors.m3onSurface readonly property string highlightPrefix: `` readonly property string highlightSuffix: `` // Note that this highlighting is independent from the search // It's close, but does not accurately represent how the fuzzy algorithm works function highlightContent(content, query) { if (!query || query.length === 0 || content == query || fontType === "monospace") return StringUtils.escapeHtml(content); let contentLower = content.toLowerCase(); let queryLower = query.toLowerCase(); let result = ""; let lastIndex = 0; let qIndex = 0; for (let i = 0; i < content.length && qIndex < query.length; i++) { if (contentLower[i] === queryLower[qIndex]) { // Add non-highlighted part (escaped) if (i > lastIndex) result += StringUtils.escapeHtml(content.slice(lastIndex, i)); // Add highlighted character (escaped) result += root.highlightPrefix + StringUtils.escapeHtml(content[i]) + root.highlightSuffix; lastIndex = i + 1; qIndex++; } } // Add the rest of the string (escaped) if (lastIndex < content.length) result += StringUtils.escapeHtml(content.slice(lastIndex)); return result; } property string displayContent: highlightContent(root.itemName, root.query) property list urls: { if (!root.itemName) return []; // Regular expression to match URLs const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi; const matches = root.itemName?.match(urlRegex) ?.filter(url => !url.includes("…")) // Elided = invalid return matches ? matches : []; } PointingHandInteraction {} background { anchors.fill: root anchors.leftMargin: root.horizontalMargin anchors.rightMargin: root.horizontalMargin } onClicked: { GlobalStates.overviewOpen = false root.itemExecute() } Keys.onPressed: (event) => { if (event.key === Qt.Key_Delete && event.modifiers === Qt.ShiftModifier) { const deleteAction = root.entry.actions.find(action => action.name == Translation.tr("Delete")); if (deleteAction) { deleteAction.execute() } } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { root.keyboardDown = true root.clicked() event.accepted = true; } } Keys.onReleased: (event) => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { root.keyboardDown = false event.accepted = true; } } RowLayout { id: rowLayout spacing: iconLoader.sourceComponent === null ? 0 : 10 anchors.fill: parent anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding // Icon Loader { id: iconLoader active: true sourceComponent: switch(root.iconType) { case LauncherSearchResult.IconType.Material: return materialSymbolComponent case LauncherSearchResult.IconType.Text: return bigTextComponent case LauncherSearchResult.IconType.System: return iconImageComponent case LauncherSearchResult.IconType.None: return null default: return null } } Component { id: iconImageComponent IconImage { source: Quickshell.iconPath(root.iconName, "image-missing") width: 35 height: 35 } } Component { id: materialSymbolComponent MaterialSymbol { text: root.materialSymbol iconSize: 30 color: root.colForeground } } Component { id: bigTextComponent StyledText { text: root.bigText font.pixelSize: Appearance.font.pixelSize.larger color: root.colForeground } } // Main text ColumnLayout { id: contentColumn Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter spacing: 0 StyledText { font.pixelSize: Appearance.font.pixelSize.smaller color: root.selected ? Appearance.colors.colOnPrimaryContainer : Appearance.colors.colSubtext visible: root.itemType && root.itemType != Translation.tr("App") text: root.itemType } RowLayout { Loader { // Checkmark for copied clipboard entry visible: itemName == Quickshell.clipboardText && root.cliphistRawString active: itemName == Quickshell.clipboardText && root.cliphistRawString sourceComponent: Rectangle { implicitWidth: activeText.implicitHeight implicitHeight: activeText.implicitHeight radius: Appearance.rounding.full color: Appearance.colors.colPrimary MaterialSymbol { id: activeText anchors.centerIn: parent text: "check" font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onPrimary } } } Repeater { // Favicons for links model: root.query == root.itemName ? [] : root.urls Favicon { required property var modelData size: parent.height url: modelData } } StyledText { // Item name/content Layout.fillWidth: true id: nameText textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work font.pixelSize: Appearance.font.pixelSize.small font.family: Appearance.font.family[root.fontType] color: root.colForeground horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: root.selected ? root.itemName : root.displayContent } } Loader { // Clipboard image preview active: root.cliphistRawString && Cliphist.entryIsImage(root.cliphistRawString) sourceComponent: CliphistImage { Layout.fillWidth: true entry: root.cliphistRawString maxWidth: contentColumn.width maxHeight: 140 blur: root.blurImage } } } // Action text StyledText { Layout.fillWidth: false visible: root.selected id: clickAction font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnPrimaryContainer horizontalAlignment: Text.AlignRight text: root.itemClickActionName } RowLayout { Layout.alignment: Qt.AlignTop Layout.topMargin: root.buttonVerticalPadding Layout.bottomMargin: -root.buttonVerticalPadding // Why is this necessary? Good question. spacing: 4 Repeater { model: (root.entry.actions ?? []).slice(0, 4) delegate: RippleButton { id: actionButton required property var modelData property var iconType: modelData.iconType property string iconName: modelData.iconName ?? "" implicitHeight: 34 implicitWidth: 34 colBackgroundHover: Appearance.colors.colSecondaryContainerHover colRipple: Appearance.colors.colSecondaryContainerActive contentItem: Item { id: actionContentItem anchors.centerIn: parent Loader { anchors.centerIn: parent active: actionButton.iconType === LauncherSearchResult.IconType.Material || actionButton.iconName === "" sourceComponent: MaterialSymbol { text: actionButton.iconName || "video_settings" font.pixelSize: Appearance.font.pixelSize.hugeass color: root.colForeground } } Loader { anchors.centerIn: parent active: actionButton.iconType === LauncherSearchResult.IconType.System && actionButton.iconName !== "" sourceComponent: IconImage { source: Quickshell.iconPath(actionButton.iconName) implicitSize: 20 } } } onClicked: modelData.execute() StyledToolTip { text: modelData.name } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/overview/SearchWidget.qml ================================================ pragma ComponentBehavior: Bound import Qt.labs.synchronizer import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions Item { // Wrapper id: root readonly property string xdgConfigHome: Directories.config readonly property int typingDebounceInterval: 200 readonly property int typingResultLimit: 15 // Should be enough to cover the whole view property string searchingText: LauncherSearch.query property bool showResults: searchingText != "" implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 implicitHeight: searchWidgetContent.implicitHeight + searchBar.verticalPadding * 2 + Appearance.sizes.elevationMargin * 2 function focusFirstItem() { appResults.currentIndex = 0; } function focusSearchInput() { searchBar.forceFocus(); } function disableExpandAnimation() { searchBar.animateWidth = false; } function cancelSearch() { searchBar.searchInput.selectAll(); LauncherSearch.query = ""; searchBar.animateWidth = true; } function setSearchingText(text) { searchBar.searchInput.text = text; LauncherSearch.query = text; } Keys.onPressed: event => { // Prevent Esc and Backspace from registering if (event.key === Qt.Key_Escape) return; // Handle Backspace: focus and delete character if not focused if (event.key === Qt.Key_Backspace) { if (!searchBar.searchInput.activeFocus) { root.focusSearchInput(); if (event.modifiers & Qt.ControlModifier) { // Delete word before cursor let text = searchBar.searchInput.text; let pos = searchBar.searchInput.cursorPosition; if (pos > 0) { // Find the start of the previous word let left = text.slice(0, pos); let match = left.match(/(\s*\S+)\s*$/); let deleteLen = match ? match[0].length : 1; searchBar.searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); searchBar.searchInput.cursorPosition = pos - deleteLen; } } else { // Delete character before cursor if any if (searchBar.searchInput.cursorPosition > 0) { searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition); searchBar.searchInput.cursorPosition -= 1; } } // Always move cursor to end after programmatic edit searchBar.searchInput.cursorPosition = searchBar.searchInput.text.length; event.accepted = true; } // If already focused, let TextField handle it return; } // Only handle visible printable characters (ignore control chars, arrows, etc.) if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc. { if (!searchBar.searchInput.activeFocus) { root.focusSearchInput(); // Insert the character at the cursor position searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition); searchBar.searchInput.cursorPosition += 1; event.accepted = true; root.focusFirstItem(); } } } StyledRectangularShadow { target: searchWidgetContent } Rectangle { // Background id: searchWidgetContent anchors { top: parent.top horizontalCenter: parent.horizontalCenter topMargin: Appearance.sizes.elevationMargin } clip: true implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight radius: searchBar.height / 2 + searchBar.verticalPadding color: Appearance.colors.colBackgroundSurfaceContainer Behavior on implicitHeight { id: searchHeightBehavior enabled: GlobalStates.overviewOpen && root.showResults animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } ColumnLayout { id: columnLayout anchors { top: parent.top horizontalCenter: parent.horizontalCenter } spacing: 0 // clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: searchWidgetContent.width height: searchWidgetContent.width radius: searchWidgetContent.radius } } SearchBar { id: searchBar property real verticalPadding: 4 Layout.fillWidth: true Layout.leftMargin: 10 Layout.rightMargin: 4 Layout.topMargin: verticalPadding Layout.bottomMargin: verticalPadding Synchronizer on searchingText { property alias source: root.searchingText } } Rectangle { // Separator visible: root.showResults Layout.fillWidth: true height: 1 color: Appearance.colors.colOutlineVariant } ListView { // App results id: appResults visible: root.showResults Layout.fillWidth: true implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) clip: true topMargin: 10 bottomMargin: 10 spacing: 2 KeyNavigation.up: searchBar highlightMoveDuration: 100 onFocusChanged: { if (focus) appResults.currentIndex = 1; } Connections { target: root function onSearchingTextChanged() { if (appResults.count > 0) appResults.currentIndex = 0; } } Timer { id: debounceTimer interval: root.typingDebounceInterval onTriggered: { resultModel.values = LauncherSearch.results ?? []; } } Connections { target: LauncherSearch function onResultsChanged() { resultModel.values = LauncherSearch.results.slice(0, root.typingResultLimit); root.focusFirstItem(); debounceTimer.restart(); } } model: ScriptModel { id: resultModel objectProp: "key" } delegate: SearchItem { id: searchItem // The selectable item for each search result required property var modelData anchors.left: parent?.left anchors.right: parent?.right entry: modelData query: StringUtils.cleanOnePrefix(root.searchingText, [Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch]) Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { if (LauncherSearch.results.length === 0) return; const tabbedText = searchItem.modelData.name; LauncherSearch.query = tabbedText; searchBar.searchInput.text = tabbedText; event.accepted = true; root.focusSearchInput(); } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Wayland FullscreenPolkitWindow { id: root contentComponent: Component { PolkitContent {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs.services import qs.modules.common import qs.modules.common.widgets Item { id: root readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { PolkitService.cancel(); } } function submit() { PolkitService.submit(inputField.text); } Connections { target: PolkitService function onInteractionAvailableChanged() { if (!PolkitService.interactionAvailable) return; inputField.text = ""; inputField.forceActiveFocus(); } } Rectangle { id: bg anchors.fill: parent color: Appearance.colors.colScrim opacity: 0 Component.onCompleted: { opacity = 1 } Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } WindowDialog { anchors.centerIn: parent backgroundWidth: 450 show: false Component.onCompleted: { show = true } MaterialSymbol { Layout.alignment: Qt.AlignHCenter iconSize: 26 text: "security" color: Appearance.colors.colSecondary } WindowDialogTitle { id: titleText Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter text: Translation.tr("Authentication") } WindowDialogParagraph { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: PolkitService.cleanMessage } MaterialTextField { id: inputField Layout.fillWidth: true focus: true enabled: PolkitService.interactionAvailable placeholderText: PolkitService.cleanPrompt echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal onAccepted: root.submit(); Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { PolkitService.cancel(); } } } WindowDialogButtonRow { Layout.bottomMargin: 10 // I honestly don't know why this is necessary Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Cancel") onClicked: PolkitService.cancel(); } DialogButton { enabled: PolkitService.interactionAvailable buttonText: Translation.tr("OK") onClicked: root.submit(); } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/CircleSelectionDetails.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Shapes import Quickshell Item { id: root required property color color required property color overlayColor required property list points property int strokeWidth: Config.options.regionSelector.circle.strokeWidth function updatePoints() { if (!root.dragging) return; root.points.push({ x: root.mouseX, y: root.mouseY }); } Rectangle { id: darkenOverlay z: 1 anchors.fill: parent color: root.overlayColor } Shape { id: shape z: 2 anchors.fill: parent layer.enabled: true layer.smooth: true preferredRendererType: Shape.CurveRenderer ShapePath { id: shapePath strokeWidth: root.strokeWidth pathHints: ShapePath.PathLinear fillColor: "transparent" strokeColor: root.color capStyle: ShapePath.RoundCap joinStyle: ShapePath.RoundJoin PathPolyline { path: root.points } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/CursorGuide.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root property var action property var selectionMode property string description: switch (root.action) { case RegionSelection.SnipAction.Copy: case RegionSelection.SnipAction.Edit: return Translation.tr("Copy region (LMB) or annotate (RMB)"); case RegionSelection.SnipAction.Search: return Translation.tr("Search with Google Lens"); case RegionSelection.SnipAction.CharRecognition: return Translation.tr("Recognize text"); case RegionSelection.SnipAction.Record: case RegionSelection.SnipAction.RecordWithSound: return Translation.tr("Record region"); } property string materialSymbol: switch (root.action) { case RegionSelection.SnipAction.Copy: case RegionSelection.SnipAction.Edit: return "content_cut"; case RegionSelection.SnipAction.Search: return "image_search"; case RegionSelection.SnipAction.CharRecognition: return "document_scanner"; case RegionSelection.SnipAction.Record: case RegionSelection.SnipAction.RecordWithSound: return "videocam"; default: return ""; } property bool showDescription: true function hideDescription() { root.showDescription = false } Timer { id: descTimeout interval: 1000 running: true onTriggered: { root.hideDescription() } } onActionChanged: { root.showDescription = true descTimeout.restart() } property int margins: 8 implicitWidth: content.implicitWidth + margins * 2 implicitHeight: content.implicitHeight + margins * 2 Rectangle { id: content anchors.centerIn: parent property real padding: 8 implicitHeight: 38 implicitWidth: root.showDescription ? contentRow.implicitWidth + padding * 2 : implicitHeight clip: true topLeftRadius: 6 bottomLeftRadius: implicitHeight - topLeftRadius bottomRightRadius: bottomLeftRadius topRightRadius: bottomLeftRadius color: Appearance.colors.colPrimary Behavior on topLeftRadius { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on implicitWidth { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Row { id: contentRow anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: content.padding } spacing: 12 MaterialSymbol { anchors.verticalCenter: parent.verticalCenter iconSize: 22 color: Appearance.colors.colOnPrimary animateChange: true text: root.materialSymbol } FadeLoader { id: descriptionLoader anchors.verticalCenter: parent.verticalCenter shown: root.showDescription sourceComponent: StyledText { color: Appearance.colors.colOnPrimary text: root.description anchors.right: parent.right anchors.rightMargin: 6 } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/OptionsToolbar.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland // Options toolbar Toolbar { id: root // Use a synchronizer on these property var action property var selectionMode // Signals signal dismiss() ToolbarTabBar { id: tabBar tabButtonList: [ {"icon": "activity_zone", "name": Translation.tr("Rect")}, {"icon": "gesture", "name": Translation.tr("Circle")} ] currentIndex: root.selectionMode === RegionSelection.SelectionMode.RectCorners ? 0 : 1 onCurrentIndexChanged: { root.selectionMode = currentIndex === 0 ? RegionSelection.SelectionMode.RectCorners : RegionSelection.SelectionMode.Circle; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick Item { id: root required property real regionX required property real regionY required property real regionWidth required property real regionHeight required property real mouseX required property real mouseY required property color color required property color overlayColor property bool showAimLines: Config.options.regionSelector.rect.showAimLines property bool breathingBorderOnly: false // Overlay to darken screen // Base dark overlay around region Rectangle { id: darkenOverlay z: 1 visible: !root.breathingBorderOnly anchors { left: parent.left top: parent.top leftMargin: root.regionX - darkenOverlay.border.width topMargin: root.regionY - darkenOverlay.border.width } width: root.regionWidth + darkenOverlay.border.width * 2 height: root.regionHeight + darkenOverlay.border.width * 2 color: "transparent" border.color: root.overlayColor border.width: Math.max(root.width, root.height) } DashedBorder { id: selectionBorder z: 9 anchors { left: parent.left top: parent.top leftMargin: Math.round(root.regionX) - borderWidth topMargin: Math.round(root.regionY) - borderWidth } width: Math.round(root.regionWidth) + borderWidth * 2 height: Math.round(root.regionHeight) + borderWidth * 2 color: root.color dashLength: 8 gapLength: 4 borderWidth: 1 // Breathing opacity: 0.9 SequentialAnimation on opacity { running: root.breathingBorderOnly loops: Animation.Infinite NumberAnimation { from: 0.9; to: 0.3; duration: 1200; easing.type: Easing.InOutQuad } NumberAnimation { from: 0.3; to: 0.9; duration: 1200; easing.type: Easing.InOutQuad } } } StyledText { z: 2 visible: !root.breathingBorderOnly anchors { top: selectionBorder.bottom right: selectionBorder.right margins: 8 } color: root.color text: `${Math.round(root.regionWidth)} x ${Math.round(root.regionHeight)}` } // Coord lines Rectangle { // Vertical visible: root.showAimLines && !root.breathingBorderOnly opacity: 0.2 z: 2 x: root.mouseX anchors { top: parent.top bottom: parent.bottom } width: 1 color: root.color } Rectangle { // Horizontal visible: root.showAimLines && !root.breathingBorderOnly opacity: 0.2 z: 2 y: root.mouseY anchors { left: parent.left right: parent.right } height: 1 color: root.color } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/RegionFunctions.qml ================================================ pragma Singleton import Quickshell Singleton { id: root function intersectionOverUnion(regionA, regionB) { // region: { at: [x, y], size: [w, h] } const ax1 = regionA.at[0], ay1 = regionA.at[1]; const ax2 = ax1 + regionA.size[0], ay2 = ay1 + regionA.size[1]; const bx1 = regionB.at[0], by1 = regionB.at[1]; const bx2 = bx1 + regionB.size[0], by2 = by1 + regionB.size[1]; const interX1 = Math.max(ax1, bx1); const interY1 = Math.max(ay1, by1); const interX2 = Math.min(ax2, bx2); const interY2 = Math.min(ay2, by2); const interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1); const areaA = (ax2 - ax1) * (ay2 - ay1); const areaB = (bx2 - bx1) * (by2 - by1); const unionArea = areaA + areaB - interArea; return unionArea > 0 ? interArea / unionArea : 0; } function filterOverlappingImageRegions(regions) { let keep = []; let removed = new Set(); for (let i = 0; i < regions.length; ++i) { if (removed.has(i)) continue; let regionA = regions[i]; for (let j = i + 1; j < regions.length; ++j) { if (removed.has(j)) continue; let regionB = regions[j]; if (intersectionOverUnion(regionA, regionB) > 0) { // Compare areas let areaA = regionA.size[0] * regionA.size[1]; let areaB = regionB.size[0] * regionB.size[1]; if (areaA <= areaB) { removed.add(j); } else { removed.add(i); } } } } for (let i = 0; i < regions.length; ++i) { if (!removed.has(i)) keep.push(regions[i]); } return keep; } function filterWindowRegionsByLayers(windowRegions, layerRegions) { return windowRegions.filter(windowRegion => { for (let i = 0; i < layerRegions.length; ++i) { if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0) return false; } return true; }); } function filterImageRegions(regions, windowRegions, threshold = 0.1) { // Remove image regions that overlap too much with any window region let filtered = regions.filter(region => { for (let i = 0; i < windowRegions.length; ++i) { if (intersectionOverUnion(region, windowRegions[i]) > threshold) return false; } return true; }); // Remove overlapping image regions, keep only the smaller one return filterOverlappingImageRegions(filtered); } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.utils import qs.modules.common.functions import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import Qt.labs.synchronizer import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland PanelWindow { id: root visible: false color: "transparent" WlrLayershell.namespace: "quickshell:regionSelector" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand exclusionMode: ExclusionMode.Ignore anchors { left: true right: true top: true bottom: true } // Modes // TODO: Ask: sidebar AI enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound } enum SelectionMode { RectCorners, Circle } enum Phase { Select, Post } property var action: RegionSelection.SnipAction.Copy property var selectionMode: RegionSelection.SelectionMode.RectCorners property var phase: RegionSelection.Phase.Select signal dismiss() // Styles property string screenshotDir: Directories.screenshotTemp property color overlayColor: ColorUtils.transparentize("#000000", 0.4) property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0 property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary property color brightTertiary: Appearance.m3colors.darkmode ? Appearance.colors.colTertiary : Qt.lighter(Appearance.colors.colPrimary) property color selectionBorderColor: ColorUtils.mix(brightText, brightSecondary, 0.5) property color selectionFillColor: "#33ffffff" property color windowBorderColor: brightSecondary property color windowFillColor: ColorUtils.transparentize(windowBorderColor, 0.85) property color imageBorderColor: brightTertiary property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85) property color onBorderColor: "#ff000000" property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity // Vars for indicators readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { // Sort floating=true windows before others if (a.floating === b.floating) return 0; return a.floating ? -1 : 1; }) readonly property var layers: HyprlandData.layers readonly property real falsePositivePreventionRatio: 0.5 // Screen & interaction vars readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) readonly property real monitorScale: hyprlandMonitor.scale readonly property real monitorOffsetX: hyprlandMonitor.x readonly property real monitorOffsetY: hyprlandMonitor.y property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0 property string screenshotPath: `${root.screenshotDir}/image-${screen.name}` property real dragStartX: 0 property real dragStartY: 0 property real draggingX: 0 property real draggingY: 0 property real dragDiffX: 0 property real dragDiffY: 0 property bool draggedAway: (dragDiffX !== 0 || dragDiffY !== 0) property bool dragging: false property list points: [] property var mouseButton: null property var imageRegions: [] readonly property list windowRegions: RegionFunctions.filterWindowRegionsByLayers( root.windows.filter(w => w.workspace.id === root.activeWorkspaceId), root.layerRegions ).map(window => { return { at: [window.at[0] - root.monitorOffsetX, window.at[1] - root.monitorOffsetY], size: [window.size[0], window.size[1]], class: window.class, title: window.title, } }) readonly property list layerRegions: { const layersOfThisMonitor = root.layers[root.hyprlandMonitor.name] const topLayers = layersOfThisMonitor?.levels["2"] if (!topLayers) return []; const nonBarTopLayers = topLayers .filter(layer => !(layer.namespace.includes(":bar") || layer.namespace.includes(":verticalBar") || layer.namespace.includes(":dock"))) .map(layer => { return { at: [layer.x, layer.y], size: [layer.w, layer.h], namespace: layer.namespace, } }) const offsetAdjustedLayers = nonBarTopLayers.map(layer => { return { at: [layer.at[0] - root.monitorOffsetX, layer.at[1] - root.monitorOffsetY], size: layer.size, namespace: layer.namespace, } }); return offsetAdjustedLayers; } // Config property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle) property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection property bool enableContentRegions: Config.options.regionSelector.targetRegions.content // Target property real targetedRegionX: -1 property real targetedRegionY: -1 property real targetedRegionWidth: 0 property real targetedRegionHeight: 0 function targetedRegionValid() { return (root.targetedRegionX >= 0 && root.targetedRegionY >= 0) } function setRegionToTargeted() { const padding = Config.options.regionSelector.targetRegions.selectionPadding; // Make borders not cut off n stuff root.regionX = root.targetedRegionX - padding; root.regionY = root.targetedRegionY - padding; root.regionWidth = root.targetedRegionWidth + padding * 2; root.regionHeight = root.targetedRegionHeight + padding * 2; } function updateTargetedRegion(x, y) { // Image regions const clickedRegion = root.imageRegions.find(region => { return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); if (clickedRegion) { root.targetedRegionX = clickedRegion.at[0]; root.targetedRegionY = clickedRegion.at[1]; root.targetedRegionWidth = clickedRegion.size[0]; root.targetedRegionHeight = clickedRegion.size[1]; return; } // Layer regions const clickedLayer = root.layerRegions.find(region => { return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); if (clickedLayer) { root.targetedRegionX = clickedLayer.at[0]; root.targetedRegionY = clickedLayer.at[1]; root.targetedRegionWidth = clickedLayer.size[0]; root.targetedRegionHeight = clickedLayer.size[1]; return; } // Window regions const clickedWindow = root.windowRegions.find(region => { return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1]; }); if (clickedWindow) { root.targetedRegionX = clickedWindow.at[0]; root.targetedRegionY = clickedWindow.at[1]; root.targetedRegionWidth = clickedWindow.size[0]; root.targetedRegionHeight = clickedWindow.size[1]; return; } root.targetedRegionX = -1; root.targetedRegionY = -1; root.targetedRegionWidth = 0; root.targetedRegionHeight = 0; } property real regionWidth: Math.abs(draggingX - dragStartX) property real regionHeight: Math.abs(draggingY - dragStartY) property real regionX: Math.min(dragStartX, draggingX) property real regionY: Math.min(dragStartY, draggingY) // Screenshot stuff TempScreenshotProcess { id: screenshotProc running: true screen: root.screen screenshotDir: root.screenshotDir screenshotPath: root.screenshotPath onExited: (exitCode, exitStatus) => { if (root.enableContentRegions) imageDetectionProcess.running = true; root.preparationDone = !checkRecordingProc.running; } } property bool isRecording: root.action === RegionSelection.SnipAction.Record || root.action === RegionSelection.SnipAction.RecordWithSound property bool recordingShouldStop: false Process { id: checkRecordingProc running: isRecording command: ["pidof", "wf-recorder"] onExited: (exitCode, exitStatus) => { root.preparationDone = !screenshotProc.running root.recordingShouldStop = (exitCode === 0); } } property bool preparationDone: false onPreparationDoneChanged: { if (!preparationDone) return; if (root.isRecording && root.recordingShouldStop) { Quickshell.execDetached([Directories.recordScriptPath]); root.dismiss(); return; } root.visible = true; } Process { id: imageDetectionProcess command: ["bash", "-c", `${Directories.scriptPath}/images/find-regions-venv.sh ` + `--hyprctl ` + `--image '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' ` + `--max-width ${Math.round(root.screen.width * root.falsePositivePreventionRatio)} ` + `--max-height ${Math.round(root.screen.height * root.falsePositivePreventionRatio)} `] stdout: StdioCollector { id: imageDimensionCollector onStreamFinished: { imageRegions = RegionFunctions.filterImageRegions( JSON.parse(imageDimensionCollector.text), root.windowRegions ); } } } function getScreenshotAction() { switch(root.action) { case RegionSelection.SnipAction.Copy: return ScreenshotAction.Action.Copy; case RegionSelection.SnipAction.Edit: return ScreenshotAction.Action.Edit; case RegionSelection.SnipAction.Search: return ScreenshotAction.Action.Search; case RegionSelection.SnipAction.CharRecognition: return ScreenshotAction.Action.CharRecognition; case RegionSelection.SnipAction.Record: return ScreenshotAction.Action.Record; case RegionSelection.SnipAction.RecordWithSound: return ScreenshotAction.Action.RecordWithSound; default: console.warn("[Region Selector] Unknown snip action, skipping snip."); root.dismiss(); return; } } // Execution after selection function snip() { // Validity check if (root.regionWidth <= 0 || root.regionHeight <= 0) { console.warn("[Region Selector] Invalid region size, skipping snip."); root.dismiss(); } // Clamp region to screen bounds root.regionX = Math.max(0, Math.min(root.regionX, root.screen.width - root.regionWidth)); root.regionY = Math.max(0, Math.min(root.regionY, root.screen.height - root.regionHeight)); root.regionWidth = Math.max(0, Math.min(root.regionWidth, root.screen.width - root.regionX)); root.regionHeight = Math.max(0, Math.min(root.regionHeight, root.screen.height - root.regionY)); // Adjust action if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) { root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy; } const screenshotDir = Config.options.screenSnip.savePath !== "" ? // Config.options.screenSnip.savePath : ""; var screenshotAction = root.getScreenshotAction(); const command = ScreenshotAction.getCommand( root.regionX * root.monitorScale, // root.regionY * root.monitorScale, // root.regionWidth * root.monitorScale,// root.regionHeight * root.monitorScale, // root.screenshotPath, // screenshotAction, // screenshotDir ) Quickshell.execDetached(command); if (root.action == RegionSelection.SnipAction.Record || root.action == RegionSelection.SnipAction.RecordWithSound) { root.phase = RegionSelection.Phase.Post root.selectionMode = RegionSelection.SelectionMode.RectCorners } else { root.dismiss(); } } // Only clickable in Selection phase mask: Region { item: switch(root.phase) { case RegionSelection.Phase.Select: return mouseArea; case RegionSelection.Phase.Post: return null; } } ScreencopyView { // For freezing anchors.fill: parent live: false captureSource: root.screen visible: root.phase === RegionSelection.Phase.Select focus: root.visible Keys.onPressed: (event) => { // Esc to close if (event.key === Qt.Key_Escape) { root.dismiss(); } } } MouseArea { id: mouseArea anchors.fill: parent cursorShape: Qt.CrossCursor acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true // Controls onPressed: (mouse) => { root.dragStartX = mouse.x; root.dragStartY = mouse.y; root.draggingX = mouse.x; root.draggingY = mouse.y; root.dragging = true; root.mouseButton = mouse.button; } onReleased: (mouse) => { // Detect if it was a click -> Try to select targeted region if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) { if (root.targetedRegionValid()) { root.setRegionToTargeted(); } } // Circle dragging? else if (root.selectionMode === RegionSelection.SelectionMode.Circle) { const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2; const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }]; const maxX = Math.max(...dragPoints.map(p => p.x)); const minX = Math.min(...dragPoints.map(p => p.x)); const maxY = Math.max(...dragPoints.map(p => p.y)); const minY = Math.min(...dragPoints.map(p => p.y)); root.regionX = minX - padding; root.regionY = minY - padding; root.regionWidth = maxX - minX + padding * 2; root.regionHeight = maxY - minY + padding * 2; } root.snip(); } onPositionChanged: (mouse) => { root.updateTargetedRegion(mouse.x, mouse.y); if (!root.dragging) return; root.draggingX = mouse.x; root.draggingY = mouse.y; root.dragDiffX = mouse.x - root.dragStartX; root.dragDiffY = mouse.y - root.dragStartY; root.points.push({ x: mouse.x, y: mouse.y }); } Loader { z: 2 anchors.fill: parent active: root.selectionMode === RegionSelection.SelectionMode.RectCorners sourceComponent: RectCornersSelectionDetails { regionX: root.regionX regionY: root.regionY regionWidth: root.regionWidth regionHeight: root.regionHeight mouseX: mouseArea.mouseX mouseY: mouseArea.mouseY color: root.selectionBorderColor overlayColor: root.overlayColor breathingBorderOnly: root.phase === RegionSelection.Phase.Post } } Loader { z: 2 anchors.fill: parent active: root.selectionMode === RegionSelection.SelectionMode.Circle sourceComponent: CircleSelectionDetails { color: root.selectionBorderColor overlayColor: root.overlayColor points: root.points } } // The thing to the bottom-right with an icon CursorGuide { z: 9999 visible: root.phase === RegionSelection.Phase.Select x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY action: root.action selectionMode: root.selectionMode } // Window regions Repeater { model: ScriptModel { values: { if (root.phase === RegionSelection.Phase.Select && root.enableWindowRegions) { return root.windowRegions } else { return [] } } } delegate: TargetRegion { z: 2 required property var modelData clientDimensions: modelData showIcon: true targeted: !root.draggedAway && // (root.targetedRegionX === modelData.at[0] // && root.targetedRegionY === modelData.at[1] // && root.targetedRegionWidth === modelData.size[0] // && root.targetedRegionHeight === modelData.size[1]) opacity: root.draggedAway ? 0 : root.targetRegionOpacity borderColor: root.windowBorderColor fillColor: targeted ? root.windowFillColor : "transparent" text: `${modelData.class}` radius: Appearance.rounding.windowRounding } } // Layer regions Repeater { model: ScriptModel { values: { if (root.phase === RegionSelection.Phase.Select && root.enableLayerRegions) { return root.layerRegions } else { return [] } } } delegate: TargetRegion { z: 3 required property var modelData clientDimensions: modelData targeted: !root.draggedAway && (root.targetedRegionX === modelData.at[0] && root.targetedRegionY === modelData.at[1] && root.targetedRegionWidth === modelData.size[0] && root.targetedRegionHeight === modelData.size[1]) opacity: root.draggedAway ? 0 : root.targetRegionOpacity borderColor: root.windowBorderColor fillColor: targeted ? root.windowFillColor : "transparent" text: `${modelData.namespace}` radius: Appearance.rounding.windowRounding } } // Content regions Repeater { model: ScriptModel { values: { if (root.phase === RegionSelection.Phase.Select && root.enableContentRegions) { return root.imageRegions } else { return [] } } } delegate: TargetRegion { z: 4 required property var modelData clientDimensions: modelData targeted: !root.draggedAway && (root.targetedRegionX === modelData.at[0] && root.targetedRegionY === modelData.at[1] && root.targetedRegionWidth === modelData.size[0] && root.targetedRegionHeight === modelData.size[1]) opacity: root.draggedAway ? 0 : root.contentRegionOpacity borderColor: root.imageBorderColor fillColor: targeted ? root.imageFillColor : "transparent" text: Translation.tr("Content region") } } // Controls Row { id: regionSelectionControls z: 10 visible: root.phase === RegionSelection.Phase.Select anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: -height } opacity: 0 Connections { target: root function onVisibleChanged() { if (!visible) return; regionSelectionControls.anchors.bottomMargin = 8; regionSelectionControls.opacity = 1; } } Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } spacing: 6 OptionsToolbar { Synchronizer on action { property alias source: root.action } Synchronizer on selectionMode { property alias source: root.selectionMode } onDismiss: root.dismiss(); } ToolbarPairedFab { anchors.verticalCenter: parent.verticalCenter iconText: "close" onClicked: root.dismiss(); StyledToolTip { text: Translation.tr("Close") } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.modules.common import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland Scope { id: root function dismiss() { GlobalStates.regionSelectorOpen = false } property var action: RegionSelection.SnipAction.Copy property var selectionMode: RegionSelection.SelectionMode.RectCorners Variants { model: Quickshell.screens delegate: Loader { id: regionSelectorLoader required property var modelData active: GlobalStates.regionSelectorOpen sourceComponent: RegionSelection { screen: regionSelectorLoader.modelData onDismiss: root.dismiss() action: root.action selectionMode: root.selectionMode } } } function screenshot() { root.action = RegionSelection.SnipAction.Copy root.selectionMode = RegionSelection.SelectionMode.RectCorners GlobalStates.regionSelectorOpen = true } function search() { root.action = RegionSelection.SnipAction.Search if (Config.options.search.imageSearch.useCircleSelection) { root.selectionMode = RegionSelection.SelectionMode.Circle } else { root.selectionMode = RegionSelection.SelectionMode.RectCorners } GlobalStates.regionSelectorOpen = true } function ocr() { root.action = RegionSelection.SnipAction.CharRecognition root.selectionMode = RegionSelection.SelectionMode.RectCorners GlobalStates.regionSelectorOpen = true } function record() { root.action = RegionSelection.SnipAction.Record root.selectionMode = RegionSelection.SelectionMode.RectCorners // If already open then re-trigger to stop recording if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false GlobalStates.regionSelectorOpen = true } function recordWithSound() { root.action = RegionSelection.SnipAction.RecordWithSound root.selectionMode = RegionSelection.SelectionMode.RectCorners // If already open then re-trigger to stop recording if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false GlobalStates.regionSelectorOpen = true } IpcHandler { target: "region" function screenshot() { root.screenshot() } function search() { root.search() } function ocr() { root.ocr() } function record() { root.record() } function recordWithSound() { root.recordWithSound() } } GlobalShortcut { name: "regionScreenshot" description: "Takes a screenshot of the selected region" onPressed: root.screenshot() } GlobalShortcut { name: "regionSearch" description: "Searches the selected region" onPressed: root.search() } GlobalShortcut { name: "regionOcr" description: "Recognizes text in the selected region" onPressed: root.ocr() } GlobalShortcut { name: "regionRecord" description: "Records the selected region" onPressed: root.record() } GlobalShortcut { name: "regionRecordWithSound" description: "Records the selected region with sound" onPressed: root.recordWithSound() } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/regionSelector/TargetRegion.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import Quickshell import Quickshell.Widgets Rectangle { id: root required property var clientDimensions property color colBackground: Qt.alpha("#88111111", 0.9) property color colForeground: "#ddffffff" property bool showLabel: Config.options.regionSelector.targetRegions.showLabel property bool showIcon: false property bool targeted: false property color borderColor property color fillColor: "transparent" property string text: "" property real textPadding: 10 z: 2 color: fillColor border.color: borderColor border.width: targeted ? 4 : 2 radius: 4 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } x: clientDimensions.at[0] y: clientDimensions.at[1] width: clientDimensions.size[0] height: clientDimensions.size[1] Loader { anchors { top: parent.top left: parent.left topMargin: root.textPadding leftMargin: root.textPadding } active: root.showLabel sourceComponent: Rectangle { property real verticalPadding: 5 property real horizontalPadding: 10 radius: 10 color: root.colBackground border.width: 1 border.color: Appearance.m3colors.m3outlineVariant implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2 implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2 Row { id: regionInfoRow anchors.centerIn: parent spacing: 4 Loader { id: regionIconLoader active: root.showIcon visible: active sourceComponent: IconImage { implicitSize: Appearance.font.pixelSize.larger source: Quickshell.iconPath(AppSearch.guessIcon(root.text), "image-missing") } } StyledText { id: regionText text: root.text color: root.colForeground } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/screenCorners/ScreenCorners.qml ================================================ import qs import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { id: screenCorners readonly property Toplevel activeWindow: ToplevelManager.activeToplevel property var actionForCorner: ({ [RoundCorner.CornerEnum.TopLeft]: () => GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen, [RoundCorner.CornerEnum.BottomLeft]: () => GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen, [RoundCorner.CornerEnum.TopRight]: () => GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen, [RoundCorner.CornerEnum.BottomRight]: () => GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen }) component CornerPanelWindow: PanelWindow { id: cornerPanelWindow property var screen: QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) property bool fullscreen visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !fullscreen)) property var corner exclusionMode: ExclusionMode.Ignore mask: Region { item: sidebarCornerOpenInteractionLoader.active ? sidebarCornerOpenInteractionLoader : null } WlrLayershell.namespace: "quickshell:screenCorners" WlrLayershell.layer: WlrLayer.Overlay color: "transparent" anchors { top: cornerWidget.isTopLeft || cornerWidget.isTopRight left: cornerWidget.isBottomLeft || cornerWidget.isTopLeft bottom: cornerWidget.isBottomLeft || cornerWidget.isBottomRight right: cornerWidget.isTopRight || cornerWidget.isBottomRight } margins { right: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.right) * -1 bottom: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.bottom) * -1 } implicitWidth: cornerWidget.implicitWidth implicitHeight: cornerWidget.implicitHeight RoundCorner { id: cornerWidget anchors.fill: parent corner: cornerPanelWindow.corner rightVisualMargin: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.right) * 1 bottomVisualMargin: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.bottom) * 1 implicitSize: Appearance.rounding.screenRounding implicitHeight: Math.max(implicitSize, sidebarCornerOpenInteractionLoader.implicitHeight) implicitWidth: Math.max(implicitSize, sidebarCornerOpenInteractionLoader.implicitWidth) Loader { id: sidebarCornerOpenInteractionLoader active: { if (!Config.options.sidebar.cornerOpen.enable) return false; if (cornerPanelWindow.fullscreen) return false; return (Config.options.sidebar.cornerOpen.bottom == cornerWidget.isBottom); } anchors { top: (cornerWidget.isTopLeft || cornerWidget.isTopRight) ? parent.top : undefined bottom: (cornerWidget.isBottomLeft || cornerWidget.isBottomRight) ? parent.bottom : undefined left: (cornerWidget.isLeft) ? parent.left : undefined right: (cornerWidget.isTopRight || cornerWidget.isBottomRight) ? parent.right : undefined } sourceComponent: FocusedScrollMouseArea { id: mouseArea implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight hoverEnabled: true onPositionChanged: { if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return; const verticalOffset = Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset; const correctX = (cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) || (cornerWidget.isLeft && mouseArea.mouseX <= 2); const correctY = (cornerWidget.isTop && mouseArea.mouseY > verticalOffset || cornerWidget.isBottom && mouseArea.mouseY < mouseArea.height - verticalOffset); if (correctX && correctY) screenCorners.actionForCorner[cornerPanelWindow.corner](); } onEntered: { if (Config.options.sidebar.cornerOpen.clickless) screenCorners.actionForCorner[cornerPanelWindow.corner](); } onPressed: { screenCorners.actionForCorner[cornerPanelWindow.corner](); } onScrollDown: { if (!Config.options.sidebar.cornerOpen.valueScroll) return; if (cornerWidget.isLeft) Brightness.decreaseBrightness() else { const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; Audio.sink.audio.volume -= step; } } onScrollUp: { if (!Config.options.sidebar.cornerOpen.valueScroll) return; if (cornerWidget.isLeft) Brightness.increaseBrightness() else { const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step); } } onMovedAway: { if (!Config.options.sidebar.cornerOpen.valueScroll) return; if (cornerWidget.isLeft) GlobalStates.osdBrightnessOpen = false; else GlobalStates.osdVolumeOpen = false; } Loader { active: Config.options.sidebar.cornerOpen.visualize anchors.fill: parent sourceComponent: Rectangle { color: Appearance.colors.colPrimary } } } } } } Variants { model: Quickshell.screens Scope { id: monitorScope required property var modelData property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) // Hide when fullscreen property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name) property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] property bool fullscreen: activeWorkspaceWithFullscreen != undefined CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.TopLeft fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.TopRight fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.BottomLeft fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.BottomRight fullscreen: monitorScope.fullscreen } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Effects import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.modules.common import qs.modules.common.functions import qs.modules.common.models.gCloud import qs.modules.common.utils import qs.modules.common.widgets import qs.services Item { id: root property double scaleFactor: 1 property color overlayColor: "#BB000000" property color textColor: "white" required property string screenshotPath readonly property string wikiLink: "https://ii.clsty.link/en/ii-qs/02usage/#setting-it-up" // TODO: write a page for this readonly property string textColorDetectionScriptPath: Quickshell.shellPath("scripts/images/text-color-venv.sh") property bool loading: true property var visionParagraphs: [] property list translationKeys: [] property var translation: ({}) function translate(s: string): string { return translation[s] ?? s; } property bool error: false property string errorMessage: "" function showError() { error = true; } Component.onCompleted: { if (GoogleCloud.tokenReady && GoogleCloud.tokenError) { root.showError(); } cloudVision.annotateImage(screenshotPath); } function reattemptAsNeeded() { if (root.visionParagraphs == [] && GoogleCloud.tokenReady && !GoogleCloud.tokenError) { root.error = false; cloudVision.annotateImage(root.screenshotPath); } } Connections { target: GoogleCloud function onTokenReadyChanged() { root.reattemptAsNeeded(); } } Rectangle { id: loadingOverlay anchors.fill: parent opacity: root.loading ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) } color: root.overlayColor Column { visible: !root.error anchors.centerIn: parent spacing: 10 * root.scaleFactor MaterialLoadingIndicator { anchors.horizontalCenter: parent.horizontalCenter implicitSize: 100 * root.scaleFactor scale: 1 + ((1 - loadingOverlay.opacity) * 0.5) * root.scaleFactor } StyledText { anchors.horizontalCenter: parent.horizontalCenter text: { if (cloudVision.state == GCloudApi.State.Preparing) return Translation.tr("Uploading image"); else if (cloudVision.state == GCloudApi.State.Processing) return Translation.tr("Reading image"); else if (cloudVision.state == GCloudApi.State.Error) return Translation.tr("Error"); else if (cloudTrans.state == GCloudApi.State.Preparing) return Translation.tr("Getting ready to translate"); else if (cloudTrans.state == GCloudApi.State.Processing) return Translation.tr("Translating"); else return " "; } font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor animateChange: true color: root.textColor } } Column { visible: root.error anchors.centerIn: parent spacing: 10 * root.scaleFactor MaterialShapeWrappedMaterialSymbol { anchors.horizontalCenter: parent.horizontalCenter text: "exclamation" iconSize: 80 * root.scaleFactor padding: 6 * root.scaleFactor color: Appearance.colors.colError colSymbol: Appearance.colors.colOnError shape: MaterialShape.Shape.Sunny } StyledText { anchors.horizontalCenter: parent.horizontalCenter width: Math.min(root.windowWidth / 2, 800) * root.scaleFactor horizontalAlignment: Text.AlignHCenter textFormat: Text.MarkdownText wrapMode: Text.Wrap text: `**${Translation.tr("Screen Translator")}**\n\n${root.errorMessage}\n\n__[${Translation.tr("See setup instructions on the wiki")}](${root.wikiLink})__` font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor color: root.textColor onLinkActivated: (link) => { Qt.openUrlExternally(link) GlobalStates.screenTranslatorOpen = false } PointingHandLinkHover {} } } } GCloudVisionResult { id: gcr } function handleError(msg) { if (msg?.length > 0) root.errorMessage = msg; else root.errorMessage = Translation.tr("Set your Google Cloud service account key"); root.showError(); } GCloudVision { id: cloudVision onError: (msg) => { root.handleError(msg); } onFinished: { gcr.initializeWithData(outputData); root.visionParagraphs = gcr.coherentParagraphs; // print(gcr.coherentParagraphs) root.translationKeys = gcr.coherentParagraphs.map(p => p.text); // print("TRANSLATION KEYS:", JSON.stringify(root.translationKeys)); cloudTrans.translateStrings(root.translationKeys); } } GCloudTranslate { id: cloudTrans onError: (msg) => { root.handleError(msg); } onFinished: { var values = outputData.translations.map(translation => translation.translatedText); const keys = root.translationKeys; root.translation = ({}); for (var i = 0; i < keys.length; i++) { Object.assign(root.translation, { [keys[i]]: values[i] }); } // print("TRANSLATION:", JSON.stringify(root.translation)); root.loading = false; } } property real windowWidth: QsWindow.window.screen.width property real windowHeight: QsWindow.window.screen.height StyledImage { id: screenshotImage z: 1 asynchronous: false width: root.windowWidth height: root.windowHeight source: Qt.resolvedUrl(root.screenshotPath) visible: false } Item { id: blurMaskItem z: 2 width: root.windowWidth height: root.windowHeight layer.enabled: true visible: false Repeater { model: root.loading ? [] : root.visionParagraphs delegate: VisionBoundingBoxRect { readonly property string text: modelData.text readonly property string translatedText: root.translate(text) visible: translatedText != text scaleFactor: 1 } } } // I no longer need these but they were a fucking pain in the ass to figure out so they're staying // GaussianBlur { // id: blurredImage // z: 3 // width: root.windowWidth // height: root.windowHeight // transformOrigin: Item.TopLeft // scale: root.scaleFactor // source: screenshotImage // radius: 10 // samples: radius * 2 + 1 // visible: false // } // MultiEffect { // id: blurredImage // z: 3 // source: screenshotImage // width: root.windowWidth // height: root.windowHeight // transformOrigin: Item.TopLeft // scale: root.scaleFactor // blurEnabled: true // blur: 1 // blurMax: 64 // visible: false // } MaskMultiEffect { z: 4 implicitWidth: parent.width implicitHeight: parent.height width: parent.width height: parent.height // Mask source: screenshotImage maskSource: blurMaskItem // Blur blurEnabled: true blur: 1 blurMax: 50 blurMultiplier: root.scaleFactor autoPaddingEnabled: false } Item { id: textItems z: 999 Repeater { model: root.loading ? [] : root.visionParagraphs // An entry looks like this: delegate: TextItem {} } } component VisionBoundingBoxRect: Rectangle { required property var modelData property real scaleFactor: root.scaleFactor property list boundingVertices: modelData.boundingBox.vertices property real unscaledX: boundingVertices[0].x property real unscaledY: boundingVertices[0].y property real unscaledWidth: boundingVertices[1].x - boundingVertices[0].x property real unscaledHeight: boundingVertices[3].y - boundingVertices[0].y // Calculate rotation based on first two vertices (top-left to top-right) property real dx: boundingVertices[1].x - boundingVertices[0].x property real dy: boundingVertices[1].y - boundingVertices[0].y transformOrigin: Item.TopLeft rotation: { // Note rotation in qml is degrees clockwise var angle = Math.atan2(dy, dx) * 180 / Math.PI; return angle; } x: unscaledX * scaleFactor y: unscaledY * scaleFactor width: unscaledWidth * scaleFactor height: unscaledHeight * scaleFactor radius: 4 } component TextItem: VisionBoundingBoxRect { id: ti // {"boundingPoly": {"vertices": [{"x": 536,"y": 236},{"x": 583,"y": 236},{"x": 583,"y": 262},{"x": 536,"y": 262}]},"description": "宮坂"} readonly property string text: modelData.text readonly property string translatedText: root.translate(text) visible: translatedText != text color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, 0.4) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } Loader { active: ti.visible sourceComponent: MultiTurnProcess { Component.onCompleted: { runSequence([ // [ // "bash", "-c", // `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} +repage -crop ${StringUtils.shellSingleQuoteEscape(ti.unscaledWidth)}x${StringUtils.shellSingleQuoteEscape(ti.unscaledHeight)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledX)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledY)} png:- | ${root.textColorDetectionScriptPath}` ], (out => { var colorData = JSON.parse(out); ti.color = ColorUtils.transparentize(colorData.background, 0.4); tiText.color = colorData.text; }) ]); } } } SqueezedAnnotationStyledText { id: tiText width: parent.width height: parent.height text: ti.translatedText scaleFactor: root.scaleFactor Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml ================================================ pragma ComponentBehavior: Bound import qs import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland Scope { id: root function dismiss() { GlobalStates.screenTranslatorOpen = false } readonly property var currentScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null Loader { id: translatorLoader property var lockedScreen active: false Connections { target: GlobalStates function onScreenTranslatorOpenChanged() { if (!GlobalStates.screenTranslatorOpen) { translatorLoader.active = false; } else { translatorLoader.lockedScreen = root.currentScreen translatorLoader.active = true } } } sourceComponent: ScreenTranslatorPanel { screen: translatorLoader.lockedScreen onDismiss: root.dismiss() } } function translate() { GlobalStates.screenTranslatorOpen = true } IpcHandler { target: "screenTranslator" function translate() { root.translate() } } GlobalShortcut { name: "screenTranslate" description: "Translates screen content" onPressed: root.translate() } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland import qs.modules.common import qs.modules.common.utils import qs.modules.common.widgets import qs.services PanelWindow { id: root // Interface signal dismiss // Window props visible: false // color: Appearance.colors.colLayer0 color: "black" WlrLayershell.namespace: "quickshell:regionSelector" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand exclusionMode: ExclusionMode.Ignore anchors { left: true right: true top: true bottom: true } // Config readonly property string screenshotDir: Directories.screenshotTemp readonly property string screenshotPath: `${root.screenshotDir}/image-${screen.name}` // Preparation property bool screenshotReady: false function performTranslation() { screenshotReady = true; } TempScreenshotProcess { id: screenshotProc running: true screen: root.screen screenshotDir: root.screenshotDir screenshotPath: root.screenshotPath onExited: (_, __) => { root.visible = true; root.performTranslation(); } } // Actual content property real scale: 1.0 property real contentX: 0 property real contentY: 0 MouseArea { anchors.fill: parent clip: true property real lastX: 0 property real lastY: 0 cursorShape: Qt.SizeAllCursor onPressed: mouse => { lastX = mouse.x; lastY = mouse.y; } onPositionChanged: mouse => { if (pressed) { root.contentX += (mouse.x - lastX); root.contentY += (mouse.y - lastY); lastX = mouse.x; lastY = mouse.y; } } onWheel: event => { const zoomFactor = event.angleDelta.y > 0 ? 1.1 : 0.9; const oldScale = root.scale; const newScale = Math.min(Math.max(0.1, oldScale * zoomFactor), 5); if (newScale !== oldScale) { // Determine mouse position relative to the content's unscaled origin const localX = (event.x - root.contentX) / oldScale; const localY = (event.y - root.contentY) / oldScale; // Apply zoom root.scale = newScale; // Shift offsets to keep the same local point under the cursor root.contentX = event.x - (localX * newScale); root.contentY = event.y - (localY * newScale); } } ScreencopyView { // Freeze screen id: screencopy width: parent.width height: parent.height x: root.contentX y: root.contentY scale: root.scale transformOrigin: Item.TopLeft live: false captureSource: root.screen } Loader { width: parent.width * root.scale height: parent.height * root.scale x: root.contentX y: root.contentY active: root.screenshotReady sourceComponent: ScreenTextOverlay { screenshotPath: root.screenshotPath scaleFactor: root.scale } } } Row { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: -height } Behavior on anchors.bottomMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Component.onCompleted: { anchors.bottomMargin = 8; } spacing: 6 Toolbar { id: toolbar focus: root.visible Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { root.dismiss(); } } spacing: 0 IconToolbarButton { id: sleepButton onClicked: { toggled = !toggled if (toggled) keyInput.forceActiveFocus() } text: "key" StyledToolTip { z: 9999 text: Translation.tr("Key input") } } Revealer { reveal: sleepButton.toggled Layout.fillHeight: true RowLayout { anchors.left: parent.left spacing: 6 Item {} // extra padding ToolbarTextField { id: keyInput implicitWidth: 400 placeholderText: Translation.tr("Paste service account key JSON here") inputMethodHints: Qt.ImhSensitiveData onAccepted: submit() function submit() { const success = GoogleCloud.setKeyJson(text); if (!success) { invalidJsonAnimation.restart(); } else { text = ""; sleepButton.toggled = false; } } ErrorShakeAnimation { id: invalidJsonAnimation target: keyInput } } IconToolbarButton { id: submitButton onClicked: keyInput.submit() text: "check" toggled: keyInput.text.length > 0 StyledToolTip { z: 9999 text: Translation.tr("Confirm") } } } } } ToolbarPairedFab { iconText: "close" onClicked: root.dismiss() StyledToolTip { text: Translation.tr("Close") } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionActionButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts RippleButton { id: button property string buttonIcon property string buttonText property bool keyboardDown: false property real size: 120 buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive : button.focus ? Appearance.colors.colPrimary : Appearance.colors.colSecondaryContainer colBackgroundHover: Appearance.colors.colPrimary colRipple: Appearance.colors.colPrimaryActive property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter background.implicitHeight: size background.implicitWidth: size Behavior on buttonRadius { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Keys.onPressed: (event) => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = true button.clicked() event.accepted = true; } } Keys.onReleased: (event) => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = false event.accepted = true; } } contentItem: MaterialSymbol { id: icon anchors.fill: parent color: button.colText horizontalAlignment: Text.AlignHCenter iconSize: 45 text: buttonIcon } StyledToolTip { text: buttonText } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) Loader { id: sessionLoader active: GlobalStates.sessionOpen onActiveChanged: { if (sessionLoader.active) SessionWarnings.refresh(); } Connections { target: GlobalStates function onScreenLockedChanged() { if (GlobalStates.screenLocked) { GlobalStates.sessionOpen = false; } } } sourceComponent: PanelWindow { // Session menu id: sessionRoot visible: sessionLoader.active property string subtitle function hide() { GlobalStates.sessionOpen = false; } exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:session" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: ColorUtils.transparentize(Appearance.m3colors.m3background, Appearance.m3colors.darkmode ? 0.05 : 0.12) anchors { top: true left: true right: true } implicitWidth: root.focusedScreen?.width ?? 0 implicitHeight: root.focusedScreen?.height ?? 0 MouseArea { id: sessionMouseArea anchors.fill: parent onClicked: { sessionRoot.hide(); } } ColumnLayout { // Content column id: contentColumn anchors.centerIn: parent spacing: 15 Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { sessionRoot.hide(); } } ColumnLayout { Layout.alignment: Qt.AlignHCenter spacing: 0 StyledText { // Title Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.title variableAxes: Appearance.font.variableAxes.title } text: Translation.tr("Session") } StyledText { // Small instruction Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.normal text: Translation.tr("Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel") } } GridLayout { columns: 4 columnSpacing: 15 rowSpacing: 15 SessionActionButton { id: sessionLock focus: sessionRoot.visible buttonIcon: "lock" buttonText: Translation.tr("Lock") onClicked: { Session.lock(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.right: sessionSleep KeyNavigation.down: sessionHibernate } SessionActionButton { id: sessionSleep buttonIcon: "dark_mode" buttonText: Translation.tr("Sleep") onClicked: { Session.suspend(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.left: sessionLock KeyNavigation.right: sessionLogout KeyNavigation.down: sessionShutdown } SessionActionButton { id: sessionLogout buttonIcon: "logout" buttonText: Translation.tr("Logout") onClicked: { Session.logout(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.left: sessionSleep KeyNavigation.right: sessionTaskManager KeyNavigation.down: sessionReboot } SessionActionButton { id: sessionTaskManager buttonIcon: "browse_activity" buttonText: Translation.tr("Task Manager") onClicked: { Session.launchTaskManager(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.left: sessionLogout KeyNavigation.down: sessionFirmwareReboot } SessionActionButton { id: sessionHibernate buttonIcon: "downloading" buttonText: Translation.tr("Hibernate") onClicked: { Session.hibernate(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.up: sessionLock KeyNavigation.right: sessionShutdown } SessionActionButton { id: sessionShutdown buttonIcon: "power_settings_new" buttonText: Translation.tr("Shutdown") onClicked: { Session.poweroff(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.left: sessionHibernate KeyNavigation.right: sessionReboot KeyNavigation.up: sessionSleep } SessionActionButton { id: sessionReboot buttonIcon: "restart_alt" buttonText: Translation.tr("Reboot") onClicked: { Session.reboot(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.left: sessionShutdown KeyNavigation.right: sessionFirmwareReboot KeyNavigation.up: sessionLogout } SessionActionButton { id: sessionFirmwareReboot buttonIcon: "settings_applications" buttonText: Translation.tr("Reboot to firmware settings") onClicked: { Session.rebootToFirmware(); sessionRoot.hide(); } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText; } KeyNavigation.up: sessionTaskManager KeyNavigation.left: sessionReboot } } DescriptionLabel { Layout.alignment: Qt.AlignHCenter text: sessionRoot.subtitle } } ColumnLayout { anchors { top: contentColumn.bottom topMargin: 10 horizontalCenter: contentColumn.horizontalCenter } spacing: 10 Loader { Layout.alignment: Qt.AlignHCenter active: SessionWarnings.downloadRunning visible: active sourceComponent: DescriptionLabel { text: Translation.tr("There might be a download in progress. Check your Downloads folder.") textColor: Appearance.m3colors.m3onErrorContainer color: Appearance.m3colors.m3errorContainer } } Loader { Layout.alignment: Qt.AlignHCenter active: SessionWarnings.packageManagerRunning visible: active sourceComponent: DescriptionLabel { text: Translation.tr("Your package manager is running") textColor: Appearance.m3colors.m3onErrorContainer color: Appearance.m3colors.m3errorContainer } } } } } component DescriptionLabel: Rectangle { id: descriptionLabel property string text property color textColor: Appearance.colors.colOnTooltip color: Appearance.colors.colTooltip clip: true radius: Appearance.rounding.normal implicitHeight: descriptionLabelText.implicitHeight + 10 * 2 implicitWidth: descriptionLabelText.implicitWidth + 15 * 2 Behavior on implicitWidth { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } StyledText { id: descriptionLabelText anchors.centerIn: parent color: descriptionLabel.textColor text: descriptionLabel.text } } IpcHandler { target: "session" function toggle(): void { GlobalStates.sessionOpen = !GlobalStates.sessionOpen; } function close(): void { GlobalStates.sessionOpen = false; } function open(): void { GlobalStates.sessionOpen = true; } } GlobalShortcut { name: "sessionToggle" description: "Toggles session screen on press" onPressed: { GlobalStates.sessionOpen = !GlobalStates.sessionOpen; } } GlobalShortcut { name: "sessionOpen" description: "Opens session screen on press" onPressed: { GlobalStates.sessionOpen = true; } } GlobalShortcut { name: "sessionClose" description: "Closes session screen on press" onPressed: { GlobalStates.sessionOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/AiChat.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.sidebarLeft.aiChat import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io Item { id: root property real padding: 4 property var inputField: messageInputField property string commandPrefix: "/" property var suggestionQuery: "" property var suggestionList: [] onFocusChanged: focus => { if (focus) { root.inputField.forceActiveFocus(); } } Keys.onPressed: event => { messageInputField.forceActiveFocus(); if (event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageUp) { messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2); event.accepted = true; } else if (event.key === Qt.Key_PageDown) { messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2); event.accepted = true; } } if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) { Ai.clearMessages(); } } property var allCommands: [ { name: "attach", description: Translation.tr("Attach a file. Only works with Gemini."), execute: args => { Ai.attachFile(args.join(" ").trim()); } }, { name: "model", description: Translation.tr("Choose model"), execute: args => { Ai.setModel(args[0]); } }, { name: "tool", description: Translation.tr("Set the tool to use for the model."), execute: args => { // console.log(args) if (args.length == 0 || args[0] == "get") { Ai.addMessage(Translation.tr("Usage: %1tool TOOL_NAME").arg(root.commandPrefix), Ai.interfaceRole); } else { const tool = args[0]; const switched = Ai.setTool(tool); if (switched) { Ai.addMessage(Translation.tr("Tool set to: %1").arg(tool), Ai.interfaceRole); } } } }, { name: "prompt", description: Translation.tr("Set the system prompt for the model."), execute: args => { if (args.length === 0 || args[0] === "get") { Ai.printPrompt(); return; } Ai.loadPrompt(args.join(" ").trim()); } }, { name: "key", description: Translation.tr("Set API key"), execute: args => { if (args[0] == "get") { Ai.printApiKey(); } else { Ai.setApiKey(args[0]); } } }, { name: "save", description: Translation.tr("Save chat"), execute: args => { const joinedArgs = args.join(" "); if (joinedArgs.trim().length == 0) { Ai.addMessage(Translation.tr("Usage: %1save CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole); return; } Ai.saveChat(joinedArgs); } }, { name: "load", description: Translation.tr("Load chat"), execute: args => { const joinedArgs = args.join(" "); if (joinedArgs.trim().length == 0) { Ai.addMessage(Translation.tr("Usage: %1load CHAT_NAME").arg(root.commandPrefix), Ai.interfaceRole); return; } Ai.loadChat(joinedArgs); } }, { name: "clear", description: Translation.tr("Clear chat history"), execute: () => { Ai.clearMessages(); } }, { name: "temp", description: Translation.tr("Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5."), execute: args => { // console.log(args) if (args.length == 0 || args[0] == "get") { Ai.printTemperature(); } else { const temp = parseFloat(args[0]); Ai.setTemperature(temp); } } }, { name: "test", description: Translation.tr("Markdown test"), execute: () => { Ai.addMessage(` A longer think block to test revealing animation OwO wem ipsum dowo sit amet, consekituwet awipiscing ewit, sed do eiuwsmod tempow inwididunt ut wabowe et dowo mawa. Ut enim ad minim weniam, quis nostwud exeucitation uwuwamcow bowowis nisi ut awiquip ex ea commowo consequat. Duuis aute iwuwe dowo in wepwependewit in wowuptate velit esse ciwwum dowo eu fugiat nuwa pawiatuw. Excepteuw sint occaecat cupidatat non pwowoident, sunt in cuwpa qui officia desewunt mowit anim id est wabowum. Meouw! >w< Mowe uwu wem ipsum! ## ✏️ Markdown test ### Formatting - *Italic*, \`Monospace\`, **Bold**, [Link](https://example.com) - Arch lincox icon ### Table Quickshell vs AGS/Astal | | Quickshell | AGS/Astal | |--------------------------|------------------|-------------------| | UI Toolkit | Qt | Gtk3/Gtk4 | | Language | QML | Js/Ts/Lua | | Reactivity | Implied | Needs declaration | | Widget placement | Mildly difficult | More intuitive | | Bluetooth & Wifi support | ❌ | ✅ | | No-delay keybinds | ✅ | ❌ | | Development | New APIs | New syntax | ### Code block Just a hello world... \`\`\`cpp #include // This is intentionally very long to test scrolling const std::string GREETING = \"UwU\"; int main(int argc, char* argv[]) { std::cout << GREETING; } \`\`\` ### LaTeX Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$ Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$ Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\] Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) `, Ai.interfaceRole); } }, ] function handleInput(inputText) { if (inputText.startsWith(root.commandPrefix)) { // Handle special commands const command = inputText.split(" ")[0].substring(1); const args = inputText.split(" ").slice(1); const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`); if (commandObj) { commandObj.execute(args); } else { Ai.addMessage(Translation.tr("Unknown command: ") + command, Ai.interfaceRole); } } else { Ai.sendUserMessage(inputText); } // Always scroll to bottom when user sends a message messageListView.positionViewAtEnd(); } Process { id: decodeImageAndAttachProc property string imageDecodePath: Directories.cliphistDecode property string imageDecodeFileName: "image" property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}` function handleEntry(entry: string) { imageDecodeFileName = parseInt(entry.match(/^(\d+)\t/)[1]); decodeImageAndAttachProc.exec(["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]); } onExited: (exitCode, exitStatus) => { if (exitCode === 0) { Ai.attachFile(imageDecodeFilePath); } else { console.error("[AiChat] Failed to decode image in clipboard content"); } } } component StatusItem: MouseArea { id: statusItem property string icon property string statusText property string description hoverEnabled: true implicitHeight: statusItemRowLayout.implicitHeight implicitWidth: statusItemRowLayout.implicitWidth RowLayout { id: statusItemRowLayout spacing: 0 MaterialSymbol { text: statusItem.icon iconSize: Appearance.font.pixelSize.huge color: Appearance.colors.colSubtext } StyledText { font.pixelSize: Appearance.font.pixelSize.small text: statusItem.statusText color: Appearance.colors.colSubtext animateChange: true } } StyledToolTip { text: statusItem.description extraVisibleCondition: false alternativeVisibleCondition: statusItem.containsMouse } } component StatusSeparator: Rectangle { implicitWidth: 4 implicitHeight: 4 radius: implicitWidth / 2 color: Appearance.colors.colOutlineVariant } ColumnLayout { id: columnLayout anchors { fill: parent margins: root.padding } spacing: root.padding Item { // Messages Layout.fillWidth: true Layout.fillHeight: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: swipeView.width height: swipeView.height radius: Appearance.rounding.small } } StyledRectangularShadow { z: 1 target: statusBg opacity: messageListView.atYBeginning ? 0 : 1 visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } Rectangle { id: statusBg z: 2 anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 4 } implicitWidth: statusRowLayout.implicitWidth + 10 * 2 implicitHeight: Math.max(statusRowLayout.implicitHeight, 38) radius: Appearance.rounding.normal - root.padding color: messageListView.atYBeginning ? Appearance.colors.colLayer2 : Appearance.colors.colLayer2Base Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } RowLayout { id: statusRowLayout anchors.centerIn: parent spacing: 10 StatusItem { icon: Ai.currentModelHasApiKey ? "key" : "key_off" statusText: "" description: Ai.currentModelHasApiKey ? Translation.tr("API key is set\nChange with /key YOUR_API_KEY") : Translation.tr("No API key\nSet it with /key YOUR_API_KEY") } StatusSeparator {} StatusItem { icon: "device_thermostat" statusText: Ai.temperature.toFixed(1) description: Translation.tr("Temperature\nChange with /temp VALUE") } StatusSeparator { visible: Ai.tokenCount.total > 0 } StatusItem { visible: Ai.tokenCount.total > 0 icon: "token" statusText: Ai.tokenCount.total description: Translation.tr("Total token count\nInput: %1\nOutput: %2").arg(Ai.tokenCount.input).arg(Ai.tokenCount.output) } } } ScrollEdgeFade { z: 1 target: messageListView vertical: true } StyledListView { // Message list id: messageListView z: 0 anchors.fill: parent spacing: 10 popin: false topMargin: statusBg.implicitHeight + statusBg.anchors.topMargin * 2 touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 property int lastResponseLength: 0 // onContentHeightChanged: { // if (atYEnd) // Qt.callLater(positionViewAtEnd); // } // onCountChanged: { // // Auto-scroll when new messages are added // if (atYEnd) // Qt.callLater(positionViewAtEnd); // } add: null // Prevent function calls from being janky model: ScriptModel { values: Ai.messageIDs.filter(id => { const message = Ai.messageByID[id]; return message?.visibleToUser ?? true; }) } delegate: AiMessage { required property var modelData required property int index messageIndex: index messageData: { Ai.messageByID[modelData]; } messageInputField: root.inputField } } PagePlaceholder { z: 2 shown: Ai.messageIDs.length === 0 icon: "neurology" title: Translation.tr("Large language models") description: Translation.tr("Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar") shape: MaterialShape.Shape.PixelCircle } ScrollToBottomButton { z: 3 target: messageListView } } DescriptionBox { text: root.suggestionList[suggestions.selectedIndex]?.description ?? "" showArrows: root.suggestionList.length > 1 } FlowButtonGroup { // Suggestions id: suggestions visible: root.suggestionList.length > 0 && messageInputField.text.length > 0 property int selectedIndex: 0 Layout.fillWidth: true spacing: 5 Repeater { id: suggestionRepeater model: { suggestions.selectedIndex = 0; return root.suggestionList.slice(0, 10); } delegate: ApiCommandButton { id: commandButton colBackground: suggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer bounce: false contentItem: StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.m3colors.m3onSurface horizontalAlignment: Text.AlignHCenter text: modelData.displayName ?? modelData.name } onHoveredChanged: { if (commandButton.hovered) { suggestions.selectedIndex = index; } } onClicked: { suggestions.acceptSuggestion(modelData.name); } } } function acceptSuggestion(word) { const words = messageInputField.text.trim().split(/\s+/); if (words.length > 0) { words[words.length - 1] = word; } else { words.push(word); } const updatedText = words.join(" ") + " "; messageInputField.text = updatedText; messageInputField.cursorPosition = messageInputField.text.length; messageInputField.forceActiveFocus(); } function acceptSelectedWord() { if (suggestions.selectedIndex >= 0 && suggestions.selectedIndex < suggestionRepeater.count) { const word = root.suggestionList[suggestions.selectedIndex].name; suggestions.acceptSuggestion(word); } } } Rectangle { // Input area id: inputWrapper property real spacing: 5 Layout.fillWidth: true radius: Appearance.rounding.normal - root.padding color: Appearance.colors.colLayer2 implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45) + (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin) clip: true Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } AttachedFileIndicator { id: attachedFileIndicator anchors { top: parent.top left: parent.left right: parent.right margins: visible ? 5 : 0 } filePath: Ai.pendingFilePath onRemove: Ai.attachFile("") } RowLayout { // Input field and send button id: inputFieldRowLayout anchors { bottom: commandButtonsRow.top left: parent.left right: parent.right bottomMargin: 5 } spacing: 0 ScrollView { id: inputScrollView Layout.fillWidth: true Layout.preferredHeight: Math.min(root.height * 3/5, messageInputField.height) clip: true ScrollBar.vertical.policy: ScrollBar.AsNeeded StyledTextArea { // The actual TextArea (inside ScrollView to enable scrolling) id: messageInputField anchors.fill: parent wrapMode: TextArea.Wrap padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant placeholderText: Translation.tr('Message the model... "%1" for commands').arg(root.commandPrefix) background: null onTextChanged: { // Handle suggestions if (messageInputField.text.length === 0) { root.suggestionQuery = ""; root.suggestionList = []; return; } else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) { root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""; const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => { return { name: Fuzzy.prepare(model), obj: model }; }), { all: true, key: "name" }); root.suggestionList = modelResults.map(model => { return { name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "model ") : ""}${model.target}`, displayName: `${Ai.models[model.target].name}`, description: `${Ai.models[model.target].description}` }; }); } else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) { root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""; const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => { return { name: Fuzzy.prepare(file), obj: file }; }), { all: true, key: "name" }); root.suggestionList = promptFileResults.map(file => { return { name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "prompt ") : ""}${file.target}`, displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`, description: Translation.tr("Load prompt from %1").arg(file.target) }; }); } else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) { root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""; const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => { return { name: Fuzzy.prepare(file), obj: file }; }), { all: true, key: "name" }); root.suggestionList = promptFileResults.map(file => { const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim(); return { name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "save ") : ""}${chatName}`, displayName: `${chatName}`, description: Translation.tr("Save chat to %1").arg(chatName) }; }); } else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) { root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""; const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => { return { name: Fuzzy.prepare(file), obj: file }; }), { all: true, key: "name" }); root.suggestionList = promptFileResults.map(file => { const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim(); return { name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "load ") : ""}${chatName}`, displayName: `${chatName}`, description: Translation.tr(`Load chat from %1`).arg(file.target) }; }); } else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) { root.suggestionQuery = messageInputField.text.split(" ")[1] ?? ""; const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => { return { name: Fuzzy.prepare(tool), obj: tool }; }), { all: true, key: "name" }); root.suggestionList = toolResults.map(tool => { const toolName = tool.target; return { name: `${messageInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "tool ") : ""}${tool.target}`, displayName: toolName, description: Ai.toolDescriptions[toolName] }; }); } else if (messageInputField.text.startsWith(root.commandPrefix)) { root.suggestionQuery = messageInputField.text; root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => { return { name: `${root.commandPrefix}${cmd.name}`, description: `${cmd.description}` }; }); } } function accept() { root.handleInput(text); text = ""; } Keys.onPressed: event => { if (event.key === Qt.Key_Tab) { suggestions.acceptSelectedWord(); event.accepted = true; } else if (event.key === Qt.Key_Up && suggestions.visible) { suggestions.selectedIndex = Math.max(0, suggestions.selectedIndex - 1); event.accepted = true; } else if (event.key === Qt.Key_Down && suggestions.visible) { suggestions.selectedIndex = Math.min(root.suggestionList.length - 1, suggestions.selectedIndex + 1); event.accepted = true; } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { if (event.modifiers & Qt.ShiftModifier) { // Insert newline messageInputField.insert(messageInputField.cursorPosition, "\n"); event.accepted = true; } else { // Accept text const inputText = messageInputField.text; messageInputField.clear(); root.handleInput(inputText); event.accepted = true; } } else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle image/file pasting if (event.modifiers & Qt.ShiftModifier) { // Let Shift+Ctrl+V = plain paste messageInputField.text += Quickshell.clipboardText; event.accepted = true; return; } // Try image paste first const currentClipboardEntry = Cliphist.entries[0]; const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry); if (/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(currentClipboardEntry)) { // First entry = currently copied entry = image? decodeImageAndAttachProc.handleEntry(currentClipboardEntry); event.accepted = true; return; } else if (cleanCliphistEntry.startsWith("file://")) { // First entry = currently copied entry = image? const fileName = decodeURIComponent(cleanCliphistEntry); Ai.attachFile(fileName); event.accepted = true; return; } event.accepted = false; // No image, let text pasting proceed } else if (event.key === Qt.Key_Escape) { // Esc to detach file if (Ai.pendingFilePath.length > 0) { Ai.attachFile(""); event.accepted = true; } else { event.accepted = false; } } } } } RippleButton { // Send button id: sendButton Layout.alignment: Qt.AlignBottom Layout.rightMargin: 5 implicitWidth: 40 implicitHeight: 40 buttonRadius: Appearance.rounding.small enabled: messageInputField.text.length > 0 toggled: enabled MouseArea { anchors.fill: parent cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { const inputText = messageInputField.text; root.handleInput(inputText); messageInputField.clear(); } } contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: 22 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled text: "arrow_upward" } } } RowLayout { // Controls id: commandButtonsRow anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 5 anchors.leftMargin: 10 anchors.rightMargin: 5 spacing: 4 property var commandsShown: [ { name: "", sendDirectly: false, dontAddSpace: true }, { name: "clear", sendDirectly: true }, ] ApiInputBoxIndicator { // Model indicator icon: "api" text: Ai.getModel().name tooltipText: Translation.tr("Current model: %1\nSet it with %2model MODEL").arg(Ai.getModel().name).arg(root.commandPrefix) } ApiInputBoxIndicator { // Tool indicator icon: "service_toolbox" text: Ai.currentTool.charAt(0).toUpperCase() + Ai.currentTool.slice(1) tooltipText: Translation.tr("Current tool: %1\nSet it with %2tool TOOL").arg(Ai.currentTool).arg(root.commandPrefix) } Item { Layout.fillWidth: true } ButtonGroup { // Command buttons padding: 0 Repeater { // Command buttons model: commandButtonsRow.commandsShown delegate: ApiCommandButton { property string commandRepresentation: `${root.commandPrefix}${modelData.name}` buttonText: commandRepresentation downAction: () => { if (modelData.sendDirectly) { root.handleInput(commandRepresentation); } else { messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? "" : " "); messageInputField.cursorPosition = messageInputField.text.length; messageInputField.forceActiveFocus(); } if (modelData.name === "clear") { messageInputField.text = ""; } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/Anime.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.sidebarLeft.anime import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell Item { id: root property real padding: 4 property var inputField: tagInputField readonly property var responses: Booru.responses property string previewDownloadPath: Directories.booruPreviews property string downloadPath: Directories.booruDownloads property string nsfwPath: Directories.booruDownloadsNsfw property string commandPrefix: "/" property real scrollOnNewResponse: 100 property int tagSuggestionDelay: 210 property var suggestionQuery: "" property var suggestionList: [] property bool pullLoading: false property int pullLoadingGap: 80 property real normalizedPullDistance: Math.max(0, (1 - Math.exp(-booruResponseListView.verticalOvershoot / 50)) * booruResponseListView.dragging) Connections { target: Booru function onTagSuggestion(query, suggestions) { root.suggestionQuery = query; root.suggestionList = suggestions; } function onRunningRequestsChanged() { if (Booru.runningRequests === 0) { root.pullLoading = false; } } } property var allCommands: [ { name: "mode", description: Translation.tr("Set the current API provider"), execute: (args) => { Booru.setProvider(args[0]); } }, { name: "clear", description: Translation.tr("Clear the current list of images"), execute: () => { Booru.clearResponses(); } }, { name: "next", description: Translation.tr("Get the next page of results"), execute: () => { if (root.responses.length > 0) { const lastResponse = root.responses[root.responses.length - 1]; root.handleInput(`${lastResponse.tags.join(" ")} ${parseInt(lastResponse.page) + 1}`); } else { root.handleInput(""); } } }, { name: "safe", description: Translation.tr("Disable NSFW content"), execute: () => { Persistent.states.booru.allowNsfw = false; } }, { name: "lewd", description: Translation.tr("Allow NSFW content"), execute: () => { Persistent.states.booru.allowNsfw = true; } }, ] function handleInput(inputText) { if (inputText.startsWith(root.commandPrefix)) { // Handle special commands const command = inputText.split(" ")[0].substring(1); const args = inputText.split(" ").slice(1); const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`); if (commandObj) { commandObj.execute(args); } else { Booru.addSystemMessage(Translation.tr("Unknown command: ") + command); } } else if (inputText.trim() == "+") { root.handleInput(`${root.commandPrefix}next`); } else { // Create tag list const tagList = inputText.split(/\s+/).filter(tag => tag.length > 0); let pageIndex = 1; for (let i = 0; i < tagList.length; ++i) { // Detect page number if (/^\d+$/.test(tagList[i])) { pageIndex = parseInt(tagList[i], 10); tagList.splice(i, 1); break; } } Booru.makeRequest(tagList, Persistent.states.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex); } } onFocusChanged: (focus) => { if (focus) { tagInputField.forceActiveFocus() } } property real pageKeyScrollAmount: booruResponseListView.height / 2 Keys.onPressed: (event) => { tagInputField.forceActiveFocus() if (event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageUp) { if (booruResponseListView.atYBeginning) return; booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount) event.accepted = true } else if (event.key === Qt.Key_PageDown) { if (booruResponseListView.atYEnd) return; booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount) event.accepted = true } } if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) { Booru.clearResponses() } } ColumnLayout { id: columnLayout anchors { fill: parent margins: root.padding } spacing: root.padding Item { Layout.fillWidth: true Layout.fillHeight: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: swipeView.width height: swipeView.height radius: Appearance.rounding.small } } ScrollEdgeFade { z: 1 target: booruResponseListView vertical: true } StyledListView { // Booru responses id: booruResponseListView z: 0 anchors.fill: parent spacing: 10 touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 property int lastResponseLength: 0 Connections { target: root function onResponsesChanged() { if (root.responses.length > booruResponseListView.lastResponseLength) { if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != "system") booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse booruResponseListView.lastResponseLength = root.responses.length } } } model: ScriptModel { values: root.responses } delegate: BooruResponse { responseData: modelData tagInputField: root.inputField previewDownloadPath: root.previewDownloadPath downloadPath: root.downloadPath nsfwPath: root.nsfwPath } onDragEnded: { // Pull to load more const gap = booruResponseListView.verticalOvershoot if (gap > root.pullLoadingGap) { root.pullLoading = true root.handleInput(`${root.commandPrefix}next`) } } } PagePlaceholder { id: placeholderItem z: 2 shown: root.responses.length === 0 icon: "bookmark_heart" title: Translation.tr("Anime boorus") description: "" shape: MaterialShape.Shape.Bun } ScrollToBottomButton { z: 3 target: booruResponseListView } MaterialLoadingIndicator { id: loadingIndicator z: 4 anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 50)) Behavior on bottomMargin { NumberAnimation { duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial } } } loading: root.pullLoading || Booru.runningRequests > 0 pullProgress: Math.min(1, booruResponseListView.verticalOvershoot / root.pullLoadingGap * booruResponseListView.dragging) scale: root.pullLoading ? 1 : Math.min(1, root.normalizedPullDistance * 2) } } DescriptionBox { // Tag suggestion description text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? "" showArrows: root.suggestionList.length > 1 } FlowButtonGroup { // Tag suggestions id: tagSuggestions visible: root.suggestionList.length > 0 && tagInputField.text.length > 0 property int selectedIndex: 0 Layout.fillWidth: true spacing: 5 Repeater { id: tagSuggestionRepeater model: { tagSuggestions.selectedIndex = 0 return root.suggestionList.slice(0, 10) } delegate: ApiCommandButton { id: tagButton colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer bounce: false contentItem: RowLayout { anchors.centerIn: parent spacing: 5 StyledText { Layout.fillWidth: false font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnSecondaryContainer horizontalAlignment: Text.AlignRight text: modelData.displayName ?? modelData.name } StyledText { Layout.fillWidth: false visible: modelData.count !== undefined font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnSecondaryContainer horizontalAlignment: Text.AlignLeft text: modelData.count ?? "" } } onHoveredChanged: { if (tagButton.hovered) { tagSuggestions.selectedIndex = index; } } onClicked: { tagSuggestions.acceptTag(modelData.name) } } } function acceptTag(tag) { const words = tagInputField.text.trim().split(/\s+/); if (words.length > 0) { words[words.length - 1] = tag; } else { words.push(tag); } const updatedText = words.join(" ") + " "; tagInputField.text = updatedText; tagInputField.cursorPosition = tagInputField.text.length; tagInputField.forceActiveFocus(); } function acceptSelectedTag() { if (tagSuggestions.selectedIndex >= 0 && tagSuggestions.selectedIndex < tagSuggestionRepeater.count) { const tag = root.suggestionList[tagSuggestions.selectedIndex].name; tagSuggestions.acceptTag(tag); } } } Rectangle { // Tag input area id: tagInputContainer property real columnSpacing: 5 Layout.fillWidth: true radius: Appearance.rounding.normal - root.padding color: Appearance.colors.colLayer2 implicitWidth: tagInputField.implicitWidth implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45) clip: true Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } RowLayout { // Input field and send button id: inputFieldRowLayout anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 5 spacing: 0 StyledTextArea { // The actual TextArea id: tagInputField wrapMode: TextArea.Wrap Layout.fillWidth: true padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering placeholderText: Translation.tr('Enter tags, or "%1" for commands').arg(root.commandPrefix) background: null property Timer searchTimer: Timer { // Timer for tag suggestions interval: root.tagSuggestionDelay repeat: false onTriggered: { const inputText = tagInputField.text const words = inputText.trim().split(/\s+/); if (words.length > 0) { Booru.triggerTagSearch(words[words.length - 1]); } } } onTextChanged: { // Handle tag suggestions if(tagInputField.text.length === 0) { root.suggestionQuery = "" root.suggestionList = [] searchTimer.stop(); return } if(tagInputField.text.startsWith(`${root.commandPrefix}mode`)) { root.suggestionQuery = tagInputField.text.split(" ")[1] ?? "" const providerResults = Fuzzy.go(root.suggestionQuery, Booru.providerList.map(provider => { return { name: Fuzzy.prepare(provider), obj: provider, } }), { all: true, key: "name" }) root.suggestionList = providerResults.map(provider => { return { name: `${tagInputField.text.trim().split(" ").length == 1 ? (root.commandPrefix + "mode ") : ""}${provider.target}`, displayName: `${Booru.providers[provider.target].name}`, description: `${Booru.providers[provider.target].description}`, } }) searchTimer.stop(); return } if(tagInputField.text.startsWith(root.commandPrefix)) { root.suggestionQuery = tagInputField.text root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => { return { name: `${root.commandPrefix}${cmd.name}`, description: `${cmd.description}`, } }) searchTimer.stop(); return } searchTimer.restart(); } function accept() { root.handleInput(text) text = "" } Keys.onPressed: (event) => { if (event.key === Qt.Key_Tab) { tagSuggestions.acceptSelectedTag(); event.accepted = true; } else if (event.key === Qt.Key_Up) { tagSuggestions.selectedIndex = Math.max(0, tagSuggestions.selectedIndex - 1); event.accepted = true; } else if (event.key === Qt.Key_Down) { tagSuggestions.selectedIndex = Math.min(root.suggestionList.length - 1, tagSuggestions.selectedIndex + 1); event.accepted = true; } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { if (event.modifiers & Qt.ShiftModifier) { // Insert newline tagInputField.insert(tagInputField.cursorPosition, "\n") event.accepted = true } else { // Accept text const inputText = tagInputField.text root.handleInput(inputText) tagInputField.clear() event.accepted = true } } } } RippleButton { // Send button id: sendButton Layout.alignment: Qt.AlignTop Layout.rightMargin: 5 implicitWidth: 40 implicitHeight: 40 buttonRadius: Appearance.rounding.small enabled: tagInputField.text.length > 0 toggled: enabled MouseArea { anchors.fill: parent cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor onClicked: { const inputText = tagInputField.text root.handleInput(inputText) tagInputField.clear() } } contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: 22 color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled text: "arrow_upward" } } } RowLayout { // Controls id: commandButtonsRow anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom anchors.bottomMargin: 5 anchors.leftMargin: 5 anchors.rightMargin: 5 spacing: 5 property var commandsShown: [ { name: "mode", sendDirectly: false, }, { name: "clear", sendDirectly: true, }, ] ApiInputBoxIndicator { // Tool indicator icon: "api" text: Booru.providers[Booru.currentProvider].name tooltipText: Translation.tr("Current API endpoint: %1\nSet it with %2mode PROVIDER") .arg(Booru.providers[Booru.currentProvider].url) .arg(root.commandPrefix) } StyledText { font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 text: "•" } MouseArea { // NSFW toggle visible: width > 0 implicitWidth: switchesRow.implicitWidth Layout.fillHeight: true hoverEnabled: true PointingHandInteraction {} onPressed: { nsfwSwitch.checked = !nsfwSwitch.checked } RowLayout { id: switchesRow spacing: 5 anchors.centerIn: parent StyledText { Layout.fillHeight: true Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter font.pixelSize: Appearance.font.pixelSize.smaller color: nsfwSwitch.enabled ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outline text: Translation.tr("Allow NSFW") } StyledSwitch { id: nsfwSwitch enabled: Booru.currentProvider !== "zerochan" scale: 0.6 Layout.alignment: Qt.AlignVCenter checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { if (!nsfwSwitch.enabled) return; Persistent.states.booru.allowNsfw = checked; } } } } Item { Layout.fillWidth: true } ButtonGroup { padding: 0 Repeater { // Command buttons id: commandRepeater model: commandButtonsRow.commandsShown delegate: ApiCommandButton { property string commandRepresentation: `${root.commandPrefix}${modelData.name}` buttonText: commandRepresentation colBackground: Appearance.colors.colLayer2 downAction: () => { if (modelData.sendDirectly) { root.handleInput(commandRepresentation) } else { tagInputField.text = commandRepresentation + " " tagInputField.cursorPosition = tagInputField.text.length tagInputField.forceActiveFocus() } if (modelData.name === "clear") { tagInputField.text = "" } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/ApiCommandButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick GroupButton { id: button property string buttonText horizontalPadding: 8 verticalPadding: 6 baseWidth: contentItem.implicitWidth + horizontalPadding * 2 clickedWidth: baseWidth + 14 baseHeight: contentItem.implicitHeight + verticalPadding * 2 buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colBackgroundActive: Appearance.colors.colLayer2Active contentItem: StyledText { horizontalAlignment: Text.AlignHCenter text: buttonText color: Appearance.m3colors.m3onSurface } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/ApiInputBoxIndicator.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts Item { // Model indicator id: root property string icon: "api" property string text: "" property string tooltipText: "" implicitHeight: rowLayout.implicitHeight + 4 * 2 implicitWidth: rowLayout.implicitWidth + 4 * 2 RowLayout { id: rowLayout anchors.centerIn: parent MaterialSymbol { text: root.icon iconSize: Appearance.font.pixelSize.normal } StyledText { id: providerName font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.m3colors.m3onSurface elide: Text.ElideRight text: root.text animateChange: true } } Loader { active: root.tooltipText?.length > 0 anchors.fill: parent sourceComponent: MouseArea { id: mouseArea hoverEnabled: true StyledToolTip { id: toolTip extraVisibleCondition: false alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered text: root.tooltipText } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/DescriptionBox.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts Item { // Tag suggestion description id: root property alias text: tagDescriptionText.text property bool showArrows: true property bool showTab: true visible: tagDescriptionText.text.length > 0 Layout.fillWidth: true implicitHeight: tagDescriptionBackground.implicitHeight Rectangle { id: tagDescriptionBackground color: Appearance.colors.colLayer2 anchors.fill: parent radius: Appearance.rounding.verysmall implicitHeight: descriptionRow.implicitHeight + 5 * 2 RowLayout { id: descriptionRow spacing: 4 anchors { fill: parent leftMargin: 10 rightMargin: 10 } StyledText { id: tagDescriptionText Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnLayer2 wrapMode: Text.Wrap } KeyboardKey { visible: root.showArrows key: "↑" } KeyboardKey { visible: root.showArrows key: "↓" } StyledText { visible: root.showArrows && root.showTab text: Translation.tr("or") font.pixelSize: Appearance.font.pixelSize.smaller } KeyboardKey { id: tagDescriptionKey visible: root.showTab key: "Tab" Layout.alignment: Qt.AlignVCenter } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/ScrollToBottomButton.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts RippleButton { id: root required property ListView target anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: 10 } opacity: !target.atYEnd ? 1 : 0 scale: !target.atYEnd ? 1 : 0.7 visible: opacity > 0 Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on scale { animation: Appearance.animation.elementResize.numberAnimation.createObject(this) } implicitWidth: contentItem.implicitWidth + 8 * 2 implicitHeight: contentItem.implicitHeight + 4 * 2 colBackground: Appearance.colors.colSecondary colBackgroundHover: Appearance.colors.colSecondaryHover colRipple: Appearance.colors.colSecondaryActive buttonRadius: Appearance.rounding.verysmall downAction: () => { target.positionViewAtEnd() } contentItem: Row { id: contentItem spacing: 4 MaterialSymbol { anchors.verticalCenter: parent.verticalCenter text: "arrow_downward" font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSecondary verticalAlignment: Text.AlignVCenter } StyledText { anchors.verticalCenter: parent.verticalCenter text: Translation.tr("Scroll to Bottom") font.pixelSize: Appearance.font.pixelSize.smallie color: Appearance.colors.colOnSecondary verticalAlignment: Text.AlignVCenter } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import Quickshell.Io import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { // Scope id: root property bool detach: false property bool pin: false property Component contentComponent: SidebarLeftContent {} property Item sidebarContent function toggleDetach() { root.detach = !root.detach; } Process { // Dodge cursor away, pin, move cursor back id: pinWithFunnyHyprlandWorkaroundProc property var hook: null property int cursorX; property int cursorY; function doIt() { command = ["hyprctl", "cursorpos"] hook = (output) => { cursorX = parseInt(output.split(",")[0]); cursorY = parseInt(output.split(",")[1]); doIt2(); } running = true; } function doIt2(output) { command = ["bash", "-c", "hyprctl dispatch 'hl.dsp.cursor.move({x=9999,y=9999})'"]; hook = () => { doIt3(); } running = true; } function doIt3(output) { root.pin = !root.pin; command = ["bash", "-c", `sleep 0.01; hyprctl dispatch 'hl.dsp.cursor.move({x=${cursorX},y=${cursorY}})'`]; hook = null running = true; } stdout: StdioCollector { onStreamFinished: { pinWithFunnyHyprlandWorkaroundProc.hook(text); } } } function togglePin() { if (!root.pin) pinWithFunnyHyprlandWorkaroundProc.doIt() else root.pin = !root.pin; } Component.onCompleted: { root.sidebarContent = contentComponent.createObject(null, { "scopeRoot": root, }); sidebarLoader.item.contentParent.children = [root.sidebarContent]; } onDetachChanged: { if (root.detach) { GlobalFocusGrab.removeDismissable(sidebarLoader.item) // Remove sidebar from the focus grab system sidebarContent.parent = null; // Detach content from sidebar sidebarLoader.active = false; // Unload sidebar detachedSidebarLoader.active = true; // Load detached window detachedSidebarLoader.item.contentParent.children = [sidebarContent]; } else { sidebarContent.parent = null; // Detach content from window detachedSidebarLoader.active = false; // Unload detached window sidebarLoader.active = true; // Load sidebar sidebarLoader.item.contentParent.children = [sidebarContent]; } } Loader { id: sidebarLoader active: true sourceComponent: PanelWindow { // Window id: panelWindow visible: GlobalStates.sidebarLeftOpen property bool extend: false property real sidebarWidth: panelWindow.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth property var contentParent: sidebarLeftBackground function hide() { GlobalStates.sidebarLeftOpen = false } exclusionMode: ExclusionMode.Normal exclusiveZone: root.pin ? sidebarWidth : 0 implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin WlrLayershell.namespace: "quickshell:sidebarLeft" // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { top: true left: true bottom: true } mask: Region { item: sidebarLeftBackground } onVisibleChanged: { if (visible) { GlobalFocusGrab.addDismissable(panelWindow); } else { GlobalFocusGrab.removeDismissable(panelWindow); } } Connections { target: GlobalFocusGrab function onDismissed() { panelWindow.hide(); } } // Content StyledRectangularShadow { target: sidebarLeftBackground radius: sidebarLeftBackground.radius } Rectangle { id: sidebarLeftBackground anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.leftMargin: Appearance.sizes.hyprlandGapsOut width: panelWindow.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 border.width: 1 border.color: Appearance.colors.colLayer0Border radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 Behavior on width { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { panelWindow.hide(); } if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_O) { panelWindow.extend = !panelWindow.extend; } else if (event.key === Qt.Key_D) { root.toggleDetach(); } else if (event.key === Qt.Key_P) { root.togglePin(); } event.accepted = true; } } } } } Loader { id: detachedSidebarLoader active: false sourceComponent: FloatingWindow { id: detachedSidebarRoot property var contentParent: detachedSidebarBackground color: "transparent" visible: GlobalStates.sidebarLeftOpen onVisibleChanged: { if (!visible) GlobalStates.sidebarLeftOpen = false; } Rectangle { id: detachedSidebarBackground anchors.fill: parent color: Appearance.colors.colLayer0 Keys.onPressed: (event) => { if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_D) { root.toggleDetach(); } event.accepted = true; } } } } } IpcHandler { target: "sidebarLeft" function toggle(): void { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen } function close(): void { GlobalStates.sidebarLeftOpen = false } function open(): void { GlobalStates.sidebarLeftOpen = true } } GlobalShortcut { name: "sidebarLeftToggle" description: "Toggles left sidebar on press" onPressed: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } } GlobalShortcut { name: "sidebarLeftOpen" description: "Opens left sidebar on press" onPressed: { GlobalStates.sidebarLeftOpen = true; } } GlobalShortcut { name: "sidebarLeftClose" description: "Closes left sidebar on press" onPressed: { GlobalStates.sidebarLeftOpen = false; } } GlobalShortcut { name: "sidebarLeftToggleDetach" description: "Detach left sidebar into a window/Attach it back" onPressed: { root.detach = !root.detach; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeftContent.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Qt.labs.synchronizer Item { id: root required property var scopeRoot property int sidebarPadding: 10 anchors.fill: parent property bool aiChatEnabled: Config.options.policies.ai !== 0 property bool translatorEnabled: Config.options.sidebar.translator.enable property bool animeEnabled: Config.options.policies.weeb !== 0 property bool animeCloset: Config.options.policies.weeb === 2 property var tabButtonList: [ ...(root.aiChatEnabled ? [{"icon": "neurology", "name": Translation.tr("Intelligence")}] : []), ...(root.translatorEnabled ? [{"icon": "translate", "name": Translation.tr("Translator")}] : []), ...((root.animeEnabled && !root.animeCloset) ? [{"icon": "bookmark_heart", "name": Translation.tr("Anime")}] : []) ] property int tabCount: swipeView.count function focusActiveItem() { swipeView.currentItem.forceActiveFocus() } Keys.onPressed: (event) => { if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { swipeView.incrementCurrentIndex() event.accepted = true; } else if (event.key === Qt.Key_PageUp) { swipeView.decrementCurrentIndex() event.accepted = true; } } } ColumnLayout { anchors { fill: parent margins: sidebarPadding } spacing: sidebarPadding Toolbar { visible: tabButtonList.length > 0 Layout.alignment: Qt.AlignHCenter enableShadow: false ToolbarTabBar { id: tabBar Layout.alignment: Qt.AlignHCenter tabButtonList: root.tabButtonList currentIndex: swipeView.currentIndex } } Rectangle { Layout.fillWidth: true Layout.fillHeight: true implicitWidth: swipeView.implicitWidth implicitHeight: swipeView.implicitHeight radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 SwipeView { // Content pages id: swipeView anchors.fill: parent spacing: 10 currentIndex: tabBar.currentIndex clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: swipeView.width height: swipeView.height radius: Appearance.rounding.small } } contentChildren: [ ...(root.aiChatEnabled ? [aiChat.createObject()] : []), ...(root.translatorEnabled ? [translator.createObject()] : []), ...((root.tabButtonList.length === 0 || (!root.aiChatEnabled && !root.translatorEnabled && root.animeCloset)) ? [placeholder.createObject()] : []), ...(root.animeEnabled ? [anime.createObject()] : []), ] } } Component { id: aiChat AiChat {} } Component { id: translator Translator {} } Component { id: anime Anime {} } Component { id: placeholder Item { StyledText { anchors.centerIn: parent text: root.animeCloset ? Translation.tr("Nothing") : Translation.tr("Enjoy your empty sidebar...") color: Appearance.colors.colSubtext } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/Translator.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.sidebarLeft.translator import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io /** * Translator widget with the `trans` commandline tool. */ Item { id: root // Sizes property real padding: 4 // Widgets property var inputField: inputCanvas.inputTextArea // Widget variables property bool translationFor: false // Indicates if the translation is for an autocorrected text property string translatedText: "" property list languages: [] // Options property string targetLanguage: Config.options.language.translator.targetLanguage property string sourceLanguage: Config.options.language.translator.sourceLanguage property string hostLanguage: targetLanguage // States property bool showLanguageSelector: false property bool languageSelectorTarget: false // true for target language, false for source language function showLanguageSelectorDialog(isTargetLang: bool) { root.languageSelectorTarget = isTargetLang; root.showLanguageSelector = true } onFocusChanged: (focus) => { if (focus) { root.inputField.forceActiveFocus() } } Timer { id: translateTimer interval: Config.options.sidebar.translator.delay repeat: false onTriggered: () => { if (root.inputField.text.trim().length > 0) { // console.log("Translating with command:", translateProc.command); translateProc.running = false; translateProc.buffer = ""; // Clear the buffer translateProc.running = true; // Restart the process } else { root.translatedText = ""; } } } Process { id: translateProc command: ["bash", "-c", `trans -brief -no-bidi` + ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'` + ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'` + ` '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`] property string buffer: "" stdout: SplitParser { onRead: data => { translateProc.buffer += data + "\n"; } } onExited: (exitCode, exitStatus) => { // With -brief mode, we get output with no metadata root.translatedText = translateProc.buffer.trim(); } } Process { id: getLanguagesProc command: ["trans", "-list-languages", "-no-bidi"] property list bufferList: ["auto"] running: true stdout: SplitParser { onRead: data => { getLanguagesProc.bufferList.push(data.trim()); } } onExited: (exitCode, exitStatus) => { // Ensure "auto" is always the first language let langs = getLanguagesProc.bufferList .filter(lang => lang.trim().length > 0 && lang !== "auto") .sort((a, b) => a.localeCompare(b)); langs.unshift("auto"); root.languages = langs; getLanguagesProc.bufferList = []; // Clear the buffer } } ColumnLayout { anchors { fill: parent margins: root.padding } StyledFlickable { Layout.fillWidth: true Layout.fillHeight: true contentHeight: contentColumn.implicitHeight ColumnLayout { id: contentColumn anchors.fill: parent LanguageSelectorButton { // Target language button id: targetLanguageButton displayText: root.targetLanguage onClicked: { root.showLanguageSelectorDialog(true); } } TextCanvas { // Content translation id: outputCanvas isInput: false placeholderText: Translation.tr("Translation goes here...") property bool hasTranslation: (root.translatedText.trim().length > 0) text: hasTranslation ? root.translatedText : "" GroupButton { id: copyButton baseWidth: height buttonRadius: Appearance.rounding.small enabled: outputCanvas.displayedText.trim().length > 0 contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger text: "content_copy" color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } onClicked: { Quickshell.clipboardText = outputCanvas.displayedText } } GroupButton { id: searchButton baseWidth: height buttonRadius: Appearance.rounding.small enabled: outputCanvas.displayedText.trim().length > 0 contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger text: "travel_explore" color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } onClicked: { let url = Config.options.search.engineBaseUrl + outputCanvas.displayedText; for (let site of Config.options.search.excludedSites) { url += ` -site:${site}`; } Qt.openUrlExternally(url); } } } } } LanguageSelectorButton { // Source language button id: sourceLanguageButton displayText: root.sourceLanguage onClicked: { root.showLanguageSelectorDialog(false); } } TextCanvas { // Content input id: inputCanvas isInput: true placeholderText: Translation.tr("Enter text to translate...") onInputTextChanged: { translateTimer.restart(); } GroupButton { id: pasteButton baseWidth: height buttonRadius: Appearance.rounding.small contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger text: "content_paste" color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } onClicked: { root.inputField.text = Quickshell.clipboardText } } GroupButton { id: deleteButton baseWidth: height buttonRadius: Appearance.rounding.small enabled: inputCanvas.inputTextArea.text.length > 0 contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger text: "close" color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext } onClicked: { root.inputField.text = "" } } } } Loader { anchors.fill: parent active: root.showLanguageSelector visible: root.showLanguageSelector z: 9999 sourceComponent: SelectionDialog { id: languageSelectorDialog titleText: Translation.tr("Select Language") items: root.languages defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage onCanceled: () => { root.showLanguageSelector = false; } onSelected: (result) => { root.showLanguageSelector = false; if (!result || result.length === 0) return; // No selection made if (root.languageSelectorTarget) { root.targetLanguage = result; Config.options.language.translator.targetLanguage = result; // Save to config } else { root.sourceLanguage = result; Config.options.language.translator.sourceLanguage = result; // Save to config } translateTimer.restart(); // Restart translation after language change } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AiMessage.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Rectangle { id: root property int messageIndex property var messageData property var messageInputField property real messagePadding: 7 property real contentSpacing: 3 property bool enableMouseSelection: false property bool renderMarkdown: true property bool editing: false property list messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content) anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 function saveMessage() { if (!root.editing) return; // Get all Loader children (each represents a segment) const segments = messageContentColumnLayout.children .map(child => child.segment) .filter(segment => (segment)); // Reconstruct markdown const newContent = segments.map(segment => { if (segment.type === "code") { const lang = segment.lang ? segment.lang : ""; // Remove trailing newlines const code = segment.content.replace(/\n+$/, ""); return "```" + lang + "\n" + code + "\n```"; } else { return segment.content; } }).join(""); root.editing = false root.messageData.content = newContent; } Keys.onPressed: (event) => { if ( // Prevent de-select event.key === Qt.Key_Control || event.key == Qt.Key_Shift || event.key == Qt.Key_Alt || event.key == Qt.Key_Meta ) { event.accepted = true } // Ctrl + S to save if ((event.key === Qt.Key_S) && event.modifiers == Qt.ControlModifier) { root.saveMessage(); event.accepted = true; } } ColumnLayout { // Main layout of the whole thing id: columnLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: messagePadding spacing: root.contentSpacing Rectangle { Layout.fillWidth: true implicitWidth: headerRowLayout.implicitWidth + 4 * 2 implicitHeight: headerRowLayout.implicitHeight + 4 * 2 color: Appearance.colors.colSecondaryContainer radius: Appearance.rounding.small RowLayout { // Header id: headerRowLayout anchors { fill: parent margins: 4 } spacing: 18 Item { // Name id: nameWrapper implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30) Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter RowLayout { id: nameRowLayout anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 10 anchors.rightMargin: 10 spacing: 12 Item { Layout.alignment: Qt.AlignVCenter Layout.fillHeight: true implicitWidth: messageData?.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth implicitHeight: messageData?.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight CustomIcon { id: modelIcon anchors.centerIn: parent visible: messageData?.role == 'assistant' && Ai.models[messageData?.model].icon width: Appearance.font.pixelSize.large height: Appearance.font.pixelSize.large source: messageData?.role == 'assistant' ? Ai.models[messageData?.model].icon : messageData?.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic' colorize: true color: Appearance.m3colors.m3onSecondaryContainer } MaterialSymbol { id: roleIcon anchors.centerIn: parent visible: !modelIcon.visible iconSize: Appearance.font.pixelSize.larger color: Appearance.m3colors.m3onSecondaryContainer text: messageData?.role == 'user' ? 'person' : messageData?.role == 'interface' ? 'settings' : messageData?.role == 'assistant' ? 'neurology' : 'computer' } } StyledText { id: providerName Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name : (messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username : Translation.tr("Interface") } } } Button { // Not visible to model id: modelVisibilityIndicator visible: messageData?.role == 'interface' implicitWidth: 16 implicitHeight: 30 Layout.alignment: Qt.AlignVCenter background: Item MaterialSymbol { id: notVisibleToModelText anchors.centerIn: parent iconSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext text: "visibility_off" } StyledToolTip { text: Translation.tr("Not visible to model") } } ButtonGroup { spacing: 5 AiMessageControlButton { id: regenButton buttonIcon: "refresh" visible: messageData?.role === 'assistant' onClicked: { Ai.regenerate(root.messageIndex) } StyledToolTip { text: Translation.tr("Regenerate") } } AiMessageControlButton { id: copyButton buttonIcon: activated ? "inventory" : "content_copy" onClicked: { Quickshell.clipboardText = root.messageData?.content copyButton.activated = true copyIconTimer.restart() } Timer { id: copyIconTimer interval: 1500 repeat: false onTriggered: { copyButton.activated = false } } StyledToolTip { text: Translation.tr("Copy") } } AiMessageControlButton { id: editButton activated: root.editing enabled: root.messageData?.done ?? false buttonIcon: "edit" onClicked: { root.editing = !root.editing if (!root.editing) { // Save changes root.saveMessage() } } StyledToolTip { text: root.editing ? Translation.tr("Save") : Translation.tr("Edit") } } AiMessageControlButton { id: toggleMarkdownButton activated: !root.renderMarkdown buttonIcon: "code" onClicked: { root.renderMarkdown = !root.renderMarkdown } StyledToolTip { text: Translation.tr("View Markdown source") } } AiMessageControlButton { id: deleteButton buttonIcon: "close" onClicked: { Ai.removeMessage(root.messageIndex) } StyledToolTip { text: Translation.tr("Delete") } } } } } Loader { Layout.fillWidth: true active: root.messageData?.localFilePath && root.messageData?.localFilePath.length > 0 sourceComponent: AttachedFileIndicator { filePath: root.messageData?.localFilePath canRemove: false } } ColumnLayout { // Message content id: messageContentColumnLayout spacing: 0 Item { Layout.fillWidth: true implicitHeight: loadingIndicatorLoader.shown ? loadingIndicatorLoader.implicitHeight : 0 implicitWidth: loadingIndicatorLoader.implicitWidth visible: implicitHeight > 0 Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } FadeLoader { id: loadingIndicatorLoader anchors.centerIn: parent shown: (root.messageBlocks.length < 1) && (!root.messageData.done) sourceComponent: MaterialLoadingIndicator { loading: true } } } Repeater { model: ScriptModel { values: root.messageBlocks } delegate: DelegateChooser { id: messageDelegate role: "type" DelegateChoice { roleValue: "code"; MessageCodeBlock { editing: root.editing renderMarkdown: root.renderMarkdown enableMouseSelection: root.enableMouseSelection segmentContent: modelData.content segmentLang: modelData.lang messageData: root.messageData } } DelegateChoice { roleValue: "think"; MessageThinkBlock { editing: root.editing renderMarkdown: root.renderMarkdown enableMouseSelection: root.enableMouseSelection segmentContent: modelData.content messageData: root.messageData done: root.messageData?.done ?? false completed: modelData.completed ?? false } } DelegateChoice { roleValue: "text"; MessageTextBlock { editing: root.editing renderMarkdown: root.renderMarkdown enableMouseSelection: root.enableMouseSelection segmentContent: modelData.content messageData: root.messageData done: root.messageData?.done ?? false forceDisableChunkSplitting: root.messageData?.content.includes("```") ?? true } } } } } Flow { // Annotations visible: root.messageData?.annotationSources?.length > 0 spacing: 5 Layout.fillWidth: true Layout.alignment: Qt.AlignLeft Repeater { model: ScriptModel { values: root.messageData?.annotationSources || [] } delegate: AnnotationSourceButton { required property var modelData displayText: modelData.text url: modelData.url } } } Flow { // Search queries visible: root.messageData?.searchQueries?.length > 0 spacing: 5 Layout.fillWidth: true Layout.alignment: Qt.AlignLeft Repeater { model: ScriptModel { values: root.messageData?.searchQueries || [] } delegate: SearchQueryButton { required property var modelData query: modelData } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AiMessageControlButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick GroupButton { id: button property string buttonIcon property bool activated: false toggled: activated baseWidth: height colBackgroundHover: Appearance.colors.colSecondaryContainerHover colBackgroundActive: Appearance.colors.colSecondaryContainerActive contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger text: buttonIcon color: button.activated ? Appearance.m3colors.m3onPrimary : button.enabled ? Appearance.m3colors.m3onSurface : Appearance.colors.colOnLayer1Inactive Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs import qs.modules.common import qs.modules.common.widgets RippleButton { id: root property string displayText property string url property real faviconSize: 20 implicitHeight: 30 leftPadding: (implicitHeight - faviconSize) / 2 rightPadding: 10 buttonRadius: Appearance.rounding.full colBackground: Appearance.colors.colSurfaceContainerHighest colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive PointingHandInteraction {} onClicked: { if (url) { Qt.openUrlExternally(url) GlobalStates.sidebarLeftOpen = false } } contentItem: Item { anchors.centerIn: parent implicitWidth: rowLayout.implicitWidth implicitHeight: rowLayout.implicitHeight RowLayout { id: rowLayout anchors.fill: parent spacing: 5 Favicon { url: root.url size: root.faviconSize displayText: root.displayText } StyledText { id: text horizontalAlignment: Text.AlignHCenter text: displayText color: Appearance.m3colors.m3onSurface } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AttachedFileIndicator.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Io import qs.modules.common import qs.modules.common.widgets import qs.services Rectangle { id: root signal remove() property bool canRemove: true property string filePath: "" property string mimeType: "" property real maxHeight: 200 property real imageWidth: -1 property real imageHeight: -1 property real scale: Math.min(root.maxHeight / imageHeight, root.width / imageWidth) onFilePathChanged: refresh() visible: filePath !== "" function refresh() { root.mimeType = ""; root.imageWidth = -1; root.imageHeight = -1; fileTypeProc.exec(["file", "-b", "--mime-type", filePath]); } Process { id: fileTypeProc command: ["file", "-b", "--mime-type", filePath] stdout: StdioCollector { onStreamFinished: { root.mimeType = this.text; if (root.mimeType.startsWith("image/")) imageSizeProc.exec(["identify", "-format", "%wx%h", filePath]); } } } Process { id: imageSizeProc command: ["identify", "-format", "%wx%h", filePath] stdout: StdioCollector { onStreamFinished: { const dimensions = this.text.split("x"); root.imageWidth = parseInt(dimensions[0]); root.imageHeight = parseInt(dimensions[1]); } } } // Styles/widgets property real horizontalPadding: 10 property real verticalPadding: 10 radius: Appearance.rounding.small - anchors.margins color: Appearance.colors.colLayer2 implicitHeight: visible ? (contentItem.implicitHeight + verticalPadding * 2) : 0 ColumnLayout { id: contentItem anchors { fill: parent leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding topMargin: root.verticalPadding bottomMargin: root.verticalPadding } RowLayout { MaterialSymbol { Layout.alignment: Qt.AlignTop text: { if (root.mimeType.startsWith("image/")) return "image"; if (root.mimeType.startsWith("audio/")) return "music_note"; if (root.mimeType.startsWith("video/")) return "movie"; if (root.mimeType === "application/pdf") return "picture_as_pdf"; if (root.mimeType.startsWith("text/")) return "description"; return "file_present"; } iconSize: Appearance.font.pixelSize.hugeass } StyledText { Layout.fillWidth: true Layout.topMargin: 4 text: root.filePath font.pixelSize: Appearance.font.pixelSize.smaller font.family: Appearance.font.family.monospace wrapMode: Text.Wrap } RippleButton { visible: root.canRemove Layout.alignment: Qt.AlignTop buttonRadius: Appearance.rounding.full colBackground: Appearance.colors.colLayer2 implicitHeight: 28 implicitWidth: 28 contentItem: MaterialSymbol { anchors.centerIn: parent text: "close" horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSurfaceVariant } onClicked: root.remove() } } Loader { id: imagePreviewLoader visible: (root.imageWidth != -1) && (root.imageHeight != -1) Layout.alignment: Qt.AlignHCenter sourceComponent: Item { implicitHeight: root.imageHeight * root.scale implicitWidth: imagePreview.implicitWidth StyledImage { id: imagePreview anchors.fill: parent source: Qt.resolvedUrl(root.filePath) fillMode: Image.PreserveAspectFit antialiasing: true width: root.imageWidth * root.scale height: root.imageHeight * root.scale sourceSize.width: root.imageWidth * root.scale sourceSize.height: root.imageHeight * root.scale layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: imagePreview.width height: imagePreview.height radius: Appearance.rounding.normal } } Rectangle { anchors.fill: parent color: "transparent" border.width: 1 border.color: Appearance.colors.colOutlineVariant radius: Appearance.rounding.normal } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageCodeBlock.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import org.kde.syntaxhighlighting ColumnLayout { id: root // These are needed on the parent loader property bool editing: false property bool renderMarkdown: true property bool enableMouseSelection: false property var segmentContent: ({}) property var segmentLang: "txt" property var messageData: {} property bool isCommandRequest: segmentLang === "command" property var displayLang: (isCommandRequest ? "bash" : segmentLang) property real codeBlockBackgroundRounding: Appearance.rounding.small property real codeBlockHeaderPadding: 3 property real codeBlockComponentSpacing: 2 spacing: codeBlockComponentSpacing Rectangle { // Code background Layout.fillWidth: true topLeftRadius: codeBlockBackgroundRounding topRightRadius: codeBlockBackgroundRounding bottomLeftRadius: Appearance.rounding.unsharpen bottomRightRadius: Appearance.rounding.unsharpen color: Appearance.colors.colSurfaceContainerHighest implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2 RowLayout { // Language and buttons id: codeBlockTitleBarRowLayout anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: codeBlockHeaderPadding anchors.rightMargin: codeBlockHeaderPadding spacing: 5 StyledText { id: codeBlockLanguage Layout.alignment: Qt.AlignLeft Layout.fillWidth: false Layout.topMargin: 7 Layout.bottomMargin: 7 Layout.leftMargin: 10 font.pixelSize: Appearance.font.pixelSize.small font.weight: Font.DemiBold color: Appearance.colors.colOnLayer2 text: root.displayLang ? Repository.definitionForName(root.displayLang).name : "plain" } Item { Layout.fillWidth: true } ButtonGroup { AiMessageControlButton { id: copyCodeButton buttonIcon: activated ? "inventory" : "content_copy" onClicked: { Quickshell.clipboardText = segmentContent copyCodeButton.activated = true copyIconTimer.restart() } Timer { id: copyIconTimer interval: 1500 repeat: false onTriggered: { copyCodeButton.activated = false } } StyledToolTip { text: Translation.tr("Copy code") } } AiMessageControlButton { id: saveCodeButton buttonIcon: activated ? "check" : "save" onClicked: { const downloadPath = FileUtils.trimFileProtocol(Directories.downloads) Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'` ]) Quickshell.execDetached(["notify-send", Translation.tr("Code saved to file"), Translation.tr("Saved to %1").arg(`${downloadPath}/code.${segmentLang || "txt"}`), "-a", "Shell" ]) saveCodeButton.activated = true saveIconTimer.restart() } Timer { id: saveIconTimer interval: 1500 repeat: false onTriggered: { saveCodeButton.activated = false } } StyledToolTip { text: Translation.tr("Save to Downloads") } } } } } RowLayout { // Line numbers and code spacing: codeBlockComponentSpacing Rectangle { // Line numbers implicitWidth: 40 implicitHeight: lineNumberColumnLayout.implicitHeight Layout.fillHeight: true Layout.fillWidth: false topLeftRadius: Appearance.rounding.unsharpen bottomLeftRadius: codeBlockBackgroundRounding topRightRadius: Appearance.rounding.unsharpen bottomRightRadius: Appearance.rounding.unsharpen color: Appearance.colors.colLayer2 ColumnLayout { id: lineNumberColumnLayout anchors { left: parent.left right: parent.right rightMargin: 5 top: parent.top topMargin: 6 } spacing: 0 Repeater { model: codeTextArea.text.split("\n").length Text { required property int index Layout.fillWidth: true Layout.alignment: Qt.AlignRight font.family: Appearance.font.family.monospace font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext horizontalAlignment: Text.AlignRight text: index + 1 } } } } Rectangle { // Code background Layout.fillWidth: true topLeftRadius: Appearance.rounding.unsharpen bottomLeftRadius: Appearance.rounding.unsharpen topRightRadius: Appearance.rounding.unsharpen bottomRightRadius: codeBlockBackgroundRounding color: Appearance.colors.colLayer2 implicitHeight: codeColumnLayout.implicitHeight ColumnLayout { id: codeColumnLayout anchors.fill: parent spacing: 0 ScrollView { id: codeScrollView Layout.fillWidth: true // Layout.fillHeight: true implicitWidth: parent.width implicitHeight: codeTextArea.implicitHeight + 1 contentWidth: codeTextArea.width - 1 // contentHeight: codeTextArea.contentHeight clip: true ScrollBar.vertical.policy: ScrollBar.AlwaysOff ScrollBar.horizontal: ScrollBar { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right padding: 5 policy: ScrollBar.AsNeeded opacity: visualSize == 1 ? 0 : 1 visible: opacity > 0 Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } contentItem: Rectangle { implicitHeight: 6 radius: Appearance.rounding.small color: Appearance.colors.colLayer2Active } } TextArea { // Code id: codeTextArea Layout.fillWidth: true readOnly: !editing selectByMouse: enableMouseSelection || editing renderType: Text.NativeRendering font.family: Appearance.font.family.monospace font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer // wrapMode: TextEdit.Wrap color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 text: segmentContent onTextChanged: { segmentContent = text } Keys.onPressed: (event) => { if (event.key === Qt.Key_Tab) { // Insert 4 spaces at cursor const cursor = codeTextArea.cursorPosition; codeTextArea.insert(cursor, " "); codeTextArea.cursorPosition = cursor + 4; event.accepted = true; } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) { codeTextArea.copy(); event.accepted = true; } } SyntaxHighlighter { id: highlighter textEdit: codeTextArea repository: Repository definition: Repository.definitionForName(root.displayLang || "plaintext") theme: Appearance.syntaxHighlightingTheme } } } Loader { active: root.isCommandRequest && root.messageData.functionPending visible: active Layout.fillWidth: true Layout.margins: 6 Layout.topMargin: 0 sourceComponent: RowLayout { Item { Layout.fillWidth: true } ButtonGroup { GroupButton { contentItem: StyledText { text: Translation.tr("Reject") font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer2 } onClicked: Ai.rejectCommand(root.messageData) } GroupButton { toggled: true contentItem: StyledText { text: Translation.tr("Approve") font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnPrimary } onClicked: Ai.approveCommand(root.messageData) } } } } } // MouseArea to block scrolling // MouseArea { // id: codeBlockMouseArea // anchors.fill: parent // acceptedButtons: editing ? Qt.NoButton : Qt.LeftButton // cursorShape: (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor // onWheel: (event) => { // event.accepted = false // } // } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageTextBlock.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland ColumnLayout { id: root // These are needed on the parent loader property bool editing: false property bool renderMarkdown: true property bool enableMouseSelection: false property var segmentContent: ({}) property var messageData: {} property bool done: true property bool forceDisableChunkSplitting: false property list renderedLatexHashes: [] property string renderedSegmentContent: "" property string shownText: "" property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\n\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn Layout.fillWidth: true Timer { id: renderTimer interval: 1000 repeat: false onTriggered: { renderLatex() for (const hash of renderedLatexHashes) { handleRenderedLatex(hash, true); } } } function renderLatex() { // Regex for $...$, $$...$$, \[...\] // Note: This is a simple approach and may need refinement for edge cases let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g; let match; while ((match = regex.exec(segmentContent)) !== null) { let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8]; if (expression) { Qt.callLater(() => { const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim()); if (!renderedLatexHashes.includes(renderHash)) { renderedLatexHashes.push(renderHash); } }); } } } function handleRenderedLatex(hash, force = false) { if (renderedLatexHashes.includes(hash) || force) { const imagePath = LatexRenderer.renderedImagePaths[hash]; const markdownImage = `![latex](${imagePath})`; const expression = LatexRenderer.processedExpressions[hash]; renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage); } } onDoneChanged: { renderTimer.restart(); } onEditingChanged: { if (!editing) { renderLatex() } else { // console.log("Editing mode enabled", segmentContent) root.shownText = segmentContent } } onSegmentContentChanged: { // console.log("Segment content changed: " + segmentContent); renderedSegmentContent = segmentContent; if (!root.editing && segmentContent) { root.renderLatex(); } } onRenderedSegmentContentChanged: { // console.log("Rendered segment content changed: " + renderedSegmentContent); if (renderedSegmentContent) { root.shownText = renderedSegmentContent; } } // When something finishes rendering // 1. Check if the hash is in the list // 2. If it is, replace the expression with the image path Connections { target: LatexRenderer function onRenderFinished(hash, imagePath) { const expression = LatexRenderer.processedExpressions[hash]; // console.log("Render finished: " + hash + " " + expression); handleRenderedLatex(hash); } } spacing: 0 Repeater { id: textLinesRepeater property list textLineOpacities: [] model: ScriptModel { // Split by either double newlines or single newlines in a list values: root.fadeChunkSplitting ? root.shownText.split(/\n\n(?= {0,2})|\n(?= {0,2}[-\*])/g).filter(line => line.trim() !== "") : [root.shownText] onValuesChanged: { while (textLinesRepeater.textLineOpacities.length < values.length) { textLinesRepeater.textLineOpacities.push(root.messageData.done ? 1 : 0); } } } delegate: TextArea { id: textArea required property int index required property string modelData // Fade in animation visible: opacity > 0 opacity: fadeChunkSplitting ? (textLinesRepeater.textLineOpacities[index] ?? (root.messageData.done ? 1 : 0)) : 1 Connections { target: root.messageData function onDoneChanged() { if (root.messageData.done) { textLinesRepeater.textLineOpacities[textArea.index] = 1 } } } Connections { target: textLinesRepeater.model function onValuesChanged() { if (textLinesRepeater.model.values.length > textArea.index + 1) { textLinesRepeater.textLineOpacities[textArea.index] = 1 } } } Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Layout.fillWidth: true readOnly: !editing selectByMouse: enableMouseSelection || editing renderType: Text.NativeRendering font.family: Appearance.font.family.reading font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text font.pixelSize: Appearance.font.pixelSize.small selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer wrapMode: TextEdit.Wrap color: root.messageData?.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1 textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText text: modelData onTextChanged: { if (!root.editing) return segmentContent = text } onLinkActivated: (link) => { Qt.openUrlExternally(link) GlobalStates.sidebarLeftOpen = false } MouseArea { // Pointing hand for links anchors.fill: parent acceptedButtons: Qt.NoButton // Only for hover hoverEnabled: true cursorShape: parent.hoveredLink !== "" ? Qt.PointingHandCursor : (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor } // Rectangle { // anchors.fill: parent // color: "#22786378" // border.width: 1 // border.color: "#7E7E7E" // } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageThinkBlock.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects Item { id: root // These are needed on the parent loader property bool editing: false property bool renderMarkdown: true property bool enableMouseSelection: false property var segmentContent: ({}) property var messageData: {} property bool done: true property bool completed: false property real thinkBlockBackgroundRounding: Appearance.rounding.small property real thinkBlockHeaderPaddingVertical: 3 property real thinkBlockHeaderPaddingHorizontal: 10 property real thinkBlockComponentSpacing: 2 property var collapseAnimation: messageTextBlock.implicitHeight > 40 ? Appearance.animation.elementMoveEnter : Appearance.animation.elementMoveFast property bool collapsed: true /* should be root.completed but its kinda buggy rn so nope */ Layout.fillWidth: true implicitHeight: collapsed ? header.implicitHeight : columnLayout.implicitHeight layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: root.width height: root.height radius: thinkBlockBackgroundRounding } } Behavior on implicitHeight { enabled: root.completed ?? false NumberAnimation { duration: collapseAnimation.duration easing.type: collapseAnimation.type easing.bezierCurve: collapseAnimation.bezierCurve } } ColumnLayout { id: columnLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top spacing: 0 Rectangle { // Header background id: header color: Appearance.colors.colSurfaceContainerHighest Layout.fillWidth: true implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2 MouseArea { // Click to reveal id: headerMouseArea enabled: root.completed anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onClicked: { root.collapsed = !root.collapsed } } RowLayout { // Header content id: thinkBlockTitleBarRowLayout anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: thinkBlockHeaderPaddingHorizontal anchors.rightMargin: thinkBlockHeaderPaddingHorizontal spacing: 10 MaterialSymbol { Layout.fillWidth: false Layout.topMargin: 7 Layout.bottomMargin: 7 Layout.leftMargin: 3 text: "linked_services" } StyledText { id: thinkBlockLanguage Layout.fillWidth: false Layout.alignment: Qt.AlignLeft text: root.completed ? Translation.tr("Thought") : (Translation.tr("Thinking") + ".".repeat(Math.random() * 4)) } Item { Layout.fillWidth: true } RippleButton { // Expand button id: expandButton visible: root.completed implicitWidth: 22 implicitHeight: 22 colBackground: headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover : ColorUtils.transparentize(Appearance.colors.colLayer2, 1) colBackgroundHover: Appearance.colors.colLayer2Hover colRipple: Appearance.colors.colLayer2Active onClicked: { root.collapsed = !root.collapsed } contentItem: MaterialSymbol { anchors.centerIn: parent text: "keyboard_arrow_down" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 rotation: root.collapsed ? 0 : 180 Behavior on rotation { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } } } } } Item { id: content Layout.fillWidth: true implicitHeight: collapsed ? 0 : contentBackground.implicitHeight + thinkBlockComponentSpacing clip: true Behavior on implicitHeight { enabled: root.completed ?? false NumberAnimation { duration: collapseAnimation.duration easing.type: collapseAnimation.type easing.bezierCurve: collapseAnimation.bezierCurve } } Rectangle { id: contentBackground anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom implicitHeight: messageTextBlock.implicitHeight color: Appearance.colors.colLayer2 // Load data for the message at the correct scope property bool editing: root.editing property bool renderMarkdown: root.renderMarkdown property bool enableMouseSelection: root.enableMouseSelection property var messageData: root.messageData property bool done: root.done MessageTextBlock { id: messageTextBlock anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom segmentContent: root.segmentContent } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/SearchQueryButton.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell.Hyprland RippleButton { id: root property string query implicitHeight: 30 leftPadding: 6 rightPadding: 10 buttonRadius: Appearance.rounding.verysmall colBackground: Appearance.colors.colSurfaceContainerHighest colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive PointingHandInteraction {} onClicked: { let url = Config.options.search.engineBaseUrl + root.query; for (let site of (Config?.options?.search.excludedSites ?? [])) { url += ` -site:${site}`; } Qt.openUrlExternally(url); GlobalStates.sidebarLeftOpen = false; } contentItem: Item { anchors.centerIn: parent implicitWidth: rowLayout.implicitWidth implicitHeight: rowLayout.implicitHeight RowLayout { id: rowLayout anchors.centerIn: parent spacing: 5 MaterialSymbol { text: "search" iconSize: 20 color: Appearance.m3colors.m3onSurface } StyledText { id: text horizontalAlignment: Text.AlignHCenter text: root.query color: Appearance.m3colors.m3onSurface } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/anime/BooruImage.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.utils import qs.modules.common.widgets import QtQml import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Hyprland Button { id: root property var imageData property var rowHeight property bool manualDownload: false property string previewDownloadPath property string downloadPath property string nsfwPath property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1)) property string filePath: `${root.previewDownloadPath}/${root.fileName}` property int maxTagStringLineLength: 50 property real imageRadius: Appearance.rounding.small property bool showActions: false ImageDownloaderProcess { id: imageDownloader running: root.manualDownload filePath: root.filePath sourceUrl: root.imageData.preview_url ?? root.imageData.sample_url onDone: (path, width, height) => { imageObject.source = "" imageObject.source = path if (!modelData.width || !modelData.height) { modelData.width = width modelData.height = height modelData.aspect_ratio = width / height } } } StyledToolTip { text: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}` } padding: 0 implicitWidth: root.rowHeight * modelData.aspect_ratio implicitHeight: root.rowHeight background: Rectangle { implicitWidth: root.rowHeight * modelData.aspect_ratio implicitHeight: root.rowHeight radius: imageRadius color: Appearance.colors.colLayer2 } contentItem: Item { anchors.fill: parent StyledImage { id: imageObject anchors.fill: parent width: root.rowHeight * modelData.aspect_ratio height: root.rowHeight fillMode: Image.PreserveAspectFit source: modelData.preview_url layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: root.rowHeight * modelData.aspect_ratio height: root.rowHeight radius: imageRadius } } } RippleButton { id: menuButton anchors.top: parent.top anchors.right: parent.right property real buttonSize: 30 anchors.margins: Math.max(root.imageRadius - buttonSize / 2, 8) implicitHeight: buttonSize implicitWidth: buttonSize buttonRadius: Appearance.rounding.full colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surface, 0.3) colBackgroundHover: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) colRipple: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) contentItem: MaterialSymbol { horizontalAlignment: Text.AlignHCenter iconSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSurface text: "more_vert" } onClicked: { root.showActions = !root.showActions } } Loader { id: contextMenuLoader active: root.showActions anchors.top: menuButton.bottom anchors.right: parent.right anchors.margins: 8 sourceComponent: Item { width: contextMenu.width height: contextMenu.height StyledRectangularShadow { target: contextMenu } Rectangle { id: contextMenu anchors.centerIn: parent opacity: root.showActions ? 1 : 0 visible: opacity > 0 radius: Appearance.rounding.small color: Appearance.m3colors.m3surfaceContainer implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitWidth: contextMenuColumnLayout.implicitWidth Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } ColumnLayout { id: contextMenuColumnLayout anchors.centerIn: parent spacing: 0 MenuButton { id: openFileLinkButton Layout.fillWidth: true buttonText: Translation.tr("Open file link") onClicked: { root.showActions = false Hyprland.dispatch("hl.config({cursor = {no_warps = true}})") Qt.openUrlExternally(root.imageData.file_url) Hyprland.dispatch("hl.config({cursor = {no_warps = false}})") } } MenuButton { id: sourceButton visible: root.imageData.source && root.imageData.source.length > 0 Layout.fillWidth: true buttonText: Translation.tr("Go to source (%1)").arg(StringUtils.getDomain(root.imageData.source)) enabled: root.imageData.source && root.imageData.source.length > 0 onClicked: { root.showActions = false Hyprland.dispatch("hl.config({cursor = {no_warps = true}})") Qt.openUrlExternally(root.imageData.source) Hyprland.dispatch("hl.config({cursor = {no_warps = false}})") } } MenuButton { id: downloadButton Layout.fillWidth: true buttonText: Translation.tr("Download") onClicked: { root.showActions = false; const targetPath = root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath; Quickshell.execDetached(["bash", "-c", `mkdir -p '${targetPath}' && curl '${root.imageData.file_url}' -o '${targetPath}/${root.fileName}' && notify-send '${Translation.tr("Download complete")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'` ]) } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/anime/BooruResponse.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.sidebarLeft import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Qt5Compat.GraphicalEffects Rectangle { id: root property var responseData property var tagInputField property string previewDownloadPath property string downloadPath property string nsfwPath property real availableWidth: parent.width property real rowTooShortThreshold: 190 property real imageSpacing: 5 property real responsePadding: 5 anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.responsePadding * 2 Component.onCompleted: { // Break property bind to prevent aggressive updates availableWidth = parent.width } Connections { target: parent function onWidthChanged() { updateWidthTimer.restart() } } Timer { id: updateWidthTimer interval: 100 onTriggered: { availableWidth = parent.width } } radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 ColumnLayout { id: columnLayout anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: responsePadding spacing: root.imageSpacing RowLayout { // Header Rectangle { // Provider name id: providerNameWrapper color: Appearance.colors.colSecondaryContainer radius: Appearance.rounding.small implicitWidth: providerName.implicitWidth + 10 * 2 implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30) Layout.alignment: Qt.AlignVCenter StyledText { id: providerName anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSecondaryContainer text: Booru.providers[root.responseData.provider].name } } Item { Layout.fillWidth: true } Item { // Page number visible: root.responseData.page != "" && root.responseData.page > 0 implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30) implicitHeight: pageNumber.implicitHeight + 5 * 2 Layout.alignment: Qt.AlignVCenter StyledText { id: pageNumber anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnLayer2 // text: `Page ${root.responseData.page}` text: Translation.tr("Page %1").arg(root.responseData.page) } } } StyledFlickable { // Tag strip id: tagsFlickable visible: root.responseData.tags.length > 0 Layout.alignment: Qt.AlignLeft Layout.fillWidth: true implicitHeight: tagRowLayout.implicitHeight contentWidth: tagRowLayout.implicitWidth clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: tagsFlickable.width height: tagsFlickable.height radius: Appearance.rounding.small } } Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } RowLayout { id: tagRowLayout Layout.alignment: Qt.AlignBottom Repeater { id: tagRepeater model: root.responseData.tags ApiCommandButton { Layout.fillWidth: false buttonText: modelData onClicked: { if(root.tagInputField.text.length !== 0) root.tagInputField.text += " " root.tagInputField.text += modelData } } } } } StyledText { // Message id: messageText Layout.fillWidth: true visible: root.responseData.message.length > 0 font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 text: root.responseData.message wrapMode: Text.WordWrap Layout.margins: responsePadding textFormat: Text.MarkdownText onLinkActivated: (link) => { Qt.openUrlExternally(link) GlobalStates.sidebarLeftOpen = false } PointingHandLinkHover {} } Repeater { model: ScriptModel { values: { // Greedily add images to a row as long as rowHeight >= rowTooShortThreshold let i = 0; let rows = []; const responseList = root.responseData.images; const minRowHeight = rowTooShortThreshold; const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2); while (i < responseList.length) { let row = { height: 0, images: [], }; let j = i; let combinedAspect = 0; let rowHeight = 0; // Try to add as many images as possible without going below minRowHeight while (j < responseList.length) { combinedAspect += responseList[j].aspect_ratio; // Subtract imageSpacing for each gap between images in the row let imagesInRow = j - i + 1; let totalSpacing = root.imageSpacing * (imagesInRow - 1); let rowAvailableWidth = availableImageWidth - totalSpacing; rowHeight = rowAvailableWidth / combinedAspect; if (rowHeight < minRowHeight) { combinedAspect -= responseList[j].aspect_ratio; imagesInRow -= 1; totalSpacing = root.imageSpacing * (imagesInRow - 1); rowAvailableWidth = availableImageWidth - totalSpacing; rowHeight = rowAvailableWidth / combinedAspect; break; } j++; } // If we couldn't add any image (shouldn't happen), add at least one if (j === i) { row.images.push(responseList[i]); row.height = availableImageWidth / responseList[i].aspect_ratio; rows.push(row); i++; } else { for (let k = i; k < j; k++) { row.images.push(responseList[k]); } // Recalculate spacing for the final row let imagesInRow = j - i; let totalSpacing = root.imageSpacing * (imagesInRow - 1); let rowAvailableWidth = availableImageWidth - totalSpacing; row.height = rowAvailableWidth / combinedAspect; rows.push(row); i = j; } } return rows; } } delegate: RowLayout { id: imageRow required property var modelData property var rowHeight: modelData.height spacing: root.imageSpacing Repeater { model: modelData.images delegate: BooruImage { required property var modelData imageData: modelData rowHeight: imageRow.rowHeight imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal // Download manually to reduce redundant requests or make sure downloading works manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider) previewDownloadPath: root.previewDownloadPath downloadPath: root.downloadPath nsfwPath: root.nsfwPath } } } } RippleButton { // Next page button id: button property string buttonText visible: root.responseData.page != "" && root.responseData.page > 0 Layout.alignment: Qt.AlignRight implicitHeight: 30 leftPadding: 10 rightPadding: 5 onClicked: { tagInputField.text = `${responseData.tags.join(" ")} ${parseInt(root.responseData.page) + 1}` tagInputField.accept() } buttonRadius: Appearance.rounding.small colBackground: Appearance.colors.colSurfaceContainerHighest colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover colRipple: Appearance.colors.colSurfaceContainerHighestActive contentItem: Item { anchors.fill: parent implicitHeight: nextPageRow.implicitHeight implicitWidth: nextPageRow.implicitWidth RowLayout { id: nextPageRow anchors.centerIn: parent spacing: 0 StyledText { Layout.alignment: Qt.AlignVCenter verticalAlignment: Text.AlignVCenter text: "Next page" color: Appearance.m3colors.m3onSurface } MaterialSymbol { Layout.alignment: Qt.AlignVCenter iconSize: Appearance.font.pixelSize.larger color: Appearance.m3colors.m3onSurface text: "chevron_right" } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/translator/LanguageSelectorButton.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts RippleButton { id: root property string displayText: "" colBackground: Appearance.colors.colLayer2 implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 implicitHeight: contentItem.implicitHeight + verticalPadding * 2 contentItem: Item { anchors.centerIn: parent implicitWidth: languageRow.implicitWidth implicitHeight: languageText.implicitHeight RowLayout { id: languageRow anchors.centerIn: parent spacing: 0 StyledText { id: languageText Layout.alignment: Qt.AlignVCenter Layout.leftMargin: 5 text: root.displayText color: Appearance.colors.colOnLayer2 font.pixelSize: Appearance.font.pixelSize.small } MaterialSymbol { Layout.alignment: Qt.AlignVCenter iconSize: Appearance.font.pixelSize.hugeass text: "arrow_drop_down" color: Appearance.colors.colOnLayer2 } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarLeft/translator/TextCanvas.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts Rectangle { id: root property bool isInput: true // true for input, false for output property string placeholderText property string text: "" property var inputTextArea: isInput ? inputLoader.item : undefined readonly property string displayedText: isInput ? inputLoader.item.text : root.text.length > 0 ? outputLoader.item.text : "" default property alias actionButtons: actions.data Layout.fillWidth: true implicitHeight: Math.max(150, inputColumn.implicitHeight) color: Appearance.colors.colLayer2 radius: Appearance.rounding.normal signal inputTextChanged(); // Signal emitted when text changes ColumnLayout { id: inputColumn anchors.fill: parent spacing: 0 Loader { id: inputLoader active: root.isInput visible: root.isInput Layout.fillWidth: true sourceComponent: StyledTextArea { // Input area id: inputTextArea placeholderText: root.placeholderText wrapMode: TextEdit.Wrap textFormat: TextEdit.PlainText font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 padding: 15 background: null onTextChanged: root.inputTextChanged() } } Loader { id: outputLoader active: !root.isInput visible: !root.isInput Layout.fillWidth: true sourceComponent: StyledText { // Output area id: outputTextArea padding: 15 wrapMode: Text.Wrap font.pixelSize: Appearance.font.pixelSize.small color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext text: root.text.length > 0 ? root.text : root.placeholderText } } Item { Layout.fillHeight: true } RowLayout { // Status row Layout.fillWidth: true Layout.margins: 10 spacing: 10 Loader { active: root.isInput visible: root.isInput Layout.leftMargin: 10 sourceComponent: Text { text: Translation.tr("%1 characters").arg(inputLoader.item.text.length) color: Appearance.colors.colOnLayer1 font.pixelSize: Appearance.font.pixelSize.smaller } } Item { Layout.fillWidth: true } ButtonGroup { id: actions } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml ================================================ pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.widgets import qs.services import qs.modules.ii.sidebarRight.calendar import qs.modules.ii.sidebarRight.todo import qs.modules.ii.sidebarRight.pomodoro import QtQuick import QtQuick.Layouts Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 clip: true implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : 350 property int selectedTab: Persistent.states.sidebar.bottomGroup.tab property int previousIndex: -1 property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property var tabs: [ { "type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": "calendar/CalendarWidget.qml" }, { "type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": "todo/TodoWidget.qml" }, { "type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": "pomodoro/PomodoroWidget.qml" }, ] Behavior on implicitHeight { NumberAnimation { duration: Appearance.animation.elementMove.duration easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } function setCollapsed(state) { Persistent.states.sidebar.bottomGroup.collapsed = state; if (collapsed) { bottomWidgetGroupRow.opacity = 0; } else { collapsedBottomWidgetGroupRow.opacity = 0; } collapseCleanFadeTimer.start(); } Timer { id: collapseCleanFadeTimer interval: Appearance.animation.elementMove.duration / 2 repeat: false onTriggered: { if (collapsed) collapsedBottomWidgetGroupRow.opacity = 1; else bottomWidgetGroupRow.opacity = 1; } } Keys.onPressed: event => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { root.selectedTab = Math.min(root.selectedTab + 1, root.tabs.length - 1); } else if (event.key === Qt.Key_PageUp) { root.selectedTab = Math.max(root.selectedTab - 1, 0); } event.accepted = true; } } // The thing when collapsed RowLayout { id: collapsedBottomWidgetGroupRow opacity: collapsed ? 1 : 0 visible: opacity > 0 Behavior on opacity { NumberAnimation { id: collapsedBottomWidgetGroupRowFade duration: Appearance.animation.elementMove.duration / 2 easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } spacing: 15 CalendarHeaderButton { Layout.margins: 10 Layout.rightMargin: 0 forceCircle: true downAction: () => { root.setCollapsed(false); } contentItem: MaterialSymbol { text: "keyboard_arrow_up" iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } } StyledText { property int remainingTasks: Todo.list.filter(task => !task.done).length Layout.margins: 10 Layout.leftMargin: 0 // text: `${DateTime.collapsedCalendarFormat} • ${remainingTasks} task${remainingTasks > 1 ? "s" : ""}` text: Translation.tr("%1 • %2 tasks").arg(DateTime.collapsedCalendarFormat).arg(remainingTasks) font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 } } // The thing when expanded RowLayout { id: bottomWidgetGroupRow opacity: collapsed ? 0 : 1 visible: opacity > 0 Behavior on opacity { NumberAnimation { id: bottomWidgetGroupRowFade duration: Appearance.animation.elementMove.duration / 2 easing.type: Appearance.animation.elementMove.type easing.bezierCurve: Appearance.animation.elementMove.bezierCurve } } anchors.fill: parent // implicitHeight: tabStack.implicitHeight spacing: 20 // Navigation rail Item { Layout.fillHeight: true Layout.fillWidth: false Layout.leftMargin: 10 Layout.topMargin: 10 implicitWidth: tabBar.implicitWidth // Navigation rail buttons NavigationRailTabArray { id: tabBar anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 5 currentIndex: root.selectedTab expanded: false Repeater { model: root.tabs NavigationRailButton { required property int index required property var modelData showToggledHighlight: false toggled: root.selectedTab == index buttonText: modelData.name buttonIcon: modelData.icon onPressed: { root.selectedTab = index; Persistent.states.sidebar.bottomGroup.tab = index; } } } } // Collapse button CalendarHeaderButton { anchors.left: parent.left anchors.top: parent.top forceCircle: true downAction: () => { root.setCollapsed(true); } contentItem: MaterialSymbol { text: "keyboard_arrow_down" iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } } } // Content area Item { Layout.fillWidth: true Layout.fillHeight: true // implicitHeight: tabStack.implicitHeight Loader { id: tabStack anchors.fill: parent anchors.bottomMargin: -anchors.topMargin Component.onCompleted: { tabStack.source = root.tabs[root.selectedTab].widget; } Connections { target: root function onSelectedTabChanged() { if (root.currentTab > root.previousIndex) tabSwitchBehavior.animation.down = true; else if (root.currentTab < root.previousIndex) tabSwitchBehavior.animation.down = false; tabStack.source = root.tabs[root.selectedTab].widget; } } Behavior on source { id: tabSwitchBehavior animation: TabSwitchAnim { id: upAnim down: true } } } } } component TabSwitchAnim: SequentialAnimation { id: switchAnim property bool down: false ParallelAnimation { PropertyAnimation { target: tabStack properties: "opacity" to: 0 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } PropertyAnimation { target: tabStack.anchors properties: "topMargin" to: 10 * (switchAnim.down ? -1 : 1) duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } PropertyAction { target: tabStack property: "source" value: root.tabs[root.selectedTab].widget } // The source change happens here ParallelAnimation { PropertyAnimation { target: tabStack.anchors properties: "topMargin" from: 10 * -(switchAnim.down ? -1 : 1) to: 0 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } PropertyAnimation { target: tabStack properties: "opacity" to: 1 duration: Appearance.animation.elementMoveFast.duration easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve } } ScriptAction { script: { root.previousIndex = root.selectedTab; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/CenterWidgetGroup.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import qs.modules.ii.sidebarRight.notifications import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 NotificationList { anchors.fill: parent anchors.margins: 5 } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/QuickSliders.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import Quickshell.Services.UPower Rectangle { id: root property var screen: root.QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) implicitWidth: contentItem.implicitWidth + root.horizontalPadding * 2 implicitHeight: contentItem.implicitHeight + root.verticalPadding * 2 radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 property real verticalPadding: 4 property real horizontalPadding: 12 Column { id: contentItem anchors { fill: parent leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding topMargin: root.verticalPadding bottomMargin: root.verticalPadding } Loader { anchors { left: parent.left right: parent.right } visible: active active: Config.options.sidebar.quickSliders.showBrightness sourceComponent: QuickSlider { materialSymbol: "light_mode" secondaryMaterialSymbol: "wb_twilight" stopIndicatorValues: Hyprsunset.gamma !== 100 && root.brightnessMonitor?.brightness !== 0 ? [0.3 + root.brightnessMonitor?.brightness * 0.7] : [] value: Hyprsunset.gamma === 100? 0.3 + root.brightnessMonitor?.brightness * 0.7 : (Hyprsunset.gamma - Hyprsunset.gammaLowerLimit) / (100 - Hyprsunset.gammaLowerLimit) * 0.3 tooltipContent: Hyprsunset.gamma === 100 ? `${Math.round(root.brightnessMonitor?.brightness * 100)}%` : `${Translation.tr("Gamma")} ${Hyprsunset.gamma}%` onMoved: { if (value >= 0.3) { // 0.3 - 1.0 brightness root.brightnessMonitor.setBrightness((value - 0.3) / 0.7); if (Hyprsunset.gamma !== 100) { Hyprsunset.setGamma(100); } } else { // 0 - 0.3 gamma if (root.brightnessMonitor.brightness !== 0) { root.brightnessMonitor.setBrightness(0); } Hyprsunset.setGamma((value / 0.3 * (100 - Hyprsunset.gammaLowerLimit) + Hyprsunset.gammaLowerLimit)); } } } } Loader { anchors { left: parent.left right: parent.right } visible: active active: Config.options.sidebar.quickSliders.showVolume sourceComponent: QuickSlider { materialSymbol: "volume_up" value: Audio.sink.audio.volume onMoved: { Audio.sink.audio.volume = value } } } Loader { anchors { left: parent.left right: parent.right } visible: active active: Config.options.sidebar.quickSliders.showMic sourceComponent: QuickSlider { materialSymbol: "mic" value: Audio.source.audio.volume onMoved: { Audio.source.audio.volume = value } } } } component QuickSlider: StyledSlider { id: quickSlider required property string materialSymbol property string secondaryMaterialSymbol configuration: StyledSlider.Configuration.M stopIndicatorValues: [] dividerValues: secondaryMaterialSymbol.length > 0 ? [secondaryIcon.iconLocation] : [] MaterialSymbol { id: icon property bool nearFull: quickSlider.value >= 0.9 anchors { verticalCenter: quickSlider.verticalCenter right: nearFull ? quickSlider.handle.right : quickSlider.right rightMargin: nearFull ? 14 : 8 } iconSize: 20 color: nearFull ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer text: quickSlider.materialSymbol Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } Behavior on anchors.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } MaterialSymbol { id: secondaryIcon visible: secondaryMaterialSymbol.length > 0 property real iconLocation: 0.3 property bool nearIcon: iconLocation - quickSlider.value <= 0.1 && iconLocation - quickSlider.value > (quickSlider.handleWidth + 8 - 14) / quickSlider.effectiveDraggingWidth anchors { verticalCenter: quickSlider.verticalCenter right: nearIcon ? quickSlider.handle.right : quickSlider.right rightMargin: nearIcon ? 14 : (1 - iconLocation) * quickSlider.effectiveDraggingWidth + quickSlider.rightPadding + 8 } iconSize: 20 color: quickSlider.value >= iconLocation - 0.1 ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer text: secondaryMaterialSymbol Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml ================================================ import qs import qs.services import qs.modules.common import QtQuick import Quickshell.Io import Quickshell import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property int sidebarWidth: Appearance.sizes.sidebarWidth PanelWindow { id: panelWindow visible: GlobalStates.sidebarRightOpen function hide() { GlobalStates.sidebarRightOpen = false; } exclusiveZone: 0 implicitWidth: sidebarWidth WlrLayershell.namespace: "quickshell:sidebarRight" WlrLayershell.keyboardFocus: GlobalStates.sidebarRightOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None color: "transparent" anchors { top: true right: true bottom: true } onVisibleChanged: { if (visible) { GlobalFocusGrab.addDismissable(panelWindow); } else { GlobalFocusGrab.removeDismissable(panelWindow); } } Connections { target: GlobalFocusGrab function onDismissed() { panelWindow.hide(); } } Loader { id: sidebarContentLoader active: GlobalStates.sidebarRightOpen || Config?.options.sidebar.keepRightSidebarLoaded anchors { fill: parent margins: Appearance.sizes.hyprlandGapsOut leftMargin: Appearance.sizes.elevationMargin } width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 focus: GlobalStates.sidebarRightOpen Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { panelWindow.hide(); } } sourceComponent: SidebarRightContent {} } } IpcHandler { target: "sidebarRight" function toggle(): void { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } function close(): void { GlobalStates.sidebarRightOpen = false; } function open(): void { GlobalStates.sidebarRightOpen = true; } } GlobalShortcut { name: "sidebarRightToggle" description: "Toggles right sidebar on press" onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } } GlobalShortcut { name: "sidebarRightOpen" description: "Opens right sidebar on press" onPressed: { GlobalStates.sidebarRightOpen = true; } } GlobalShortcut { name: "sidebarRightClose" description: "Closes right sidebar on press" onPressed: { GlobalStates.sidebarRightOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRightContent.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import Quickshell.Hyprland import qs.modules.ii.sidebarRight.quickToggles import qs.modules.ii.sidebarRight.quickToggles.classicStyle import qs.modules.ii.sidebarRight.bluetoothDevices import qs.modules.ii.sidebarRight.nightLight import qs.modules.ii.sidebarRight.volumeMixer import qs.modules.ii.sidebarRight.wifiNetworks Item { id: root property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 10 property string settingsQmlPath: Quickshell.shellPath("settings.qml") property bool showAudioOutputDialog: false property bool showAudioInputDialog: false property bool showBluetoothDialog: false property bool showNightLightDialog: false property bool showWifiDialog: false property bool editMode: false Connections { target: GlobalStates function onSidebarRightOpenChanged() { if (!GlobalStates.sidebarRightOpen) { root.showWifiDialog = false; root.showBluetoothDialog = false; root.showAudioOutputDialog = false; root.showAudioInputDialog = false; } } } implicitHeight: sidebarRightBackground.implicitHeight implicitWidth: sidebarRightBackground.implicitWidth StyledRectangularShadow { target: sidebarRightBackground } Rectangle { id: sidebarRightBackground anchors.fill: parent implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2 implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 color: Appearance.colors.colLayer0 border.width: 1 border.color: Appearance.colors.colLayer0Border radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 ColumnLayout { anchors.fill: parent anchors.margins: sidebarPadding spacing: sidebarPadding SystemButtonRow { Layout.fillHeight: false Layout.fillWidth: true // Layout.margins: 10 Layout.topMargin: 5 Layout.bottomMargin: 0 } Loader { id: slidersLoader Layout.fillWidth: true visible: active active: { const configQuickSliders = Config.options.sidebar.quickSliders if (!configQuickSliders.enable) return false if (!configQuickSliders.showMic && !configQuickSliders.showVolume && !configQuickSliders.showBrightness) return false; return true; } sourceComponent: QuickSliders {} } LoaderedQuickPanelImplementation { styleName: "classic" sourceComponent: ClassicQuickPanel {} } LoaderedQuickPanelImplementation { styleName: "android" sourceComponent: AndroidQuickPanel { editMode: root.editMode } } CenterWidgetGroup { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true Layout.fillWidth: true } BottomWidgetGroup { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: false Layout.fillWidth: true Layout.preferredHeight: implicitHeight } } } ToggleDialog { shownPropertyString: "showAudioOutputDialog" dialog: VolumeDialog { isSink: true } } ToggleDialog { shownPropertyString: "showAudioInputDialog" dialog: VolumeDialog { isSink: false } } ToggleDialog { shownPropertyString: "showBluetoothDialog" dialog: BluetoothDialog {} onShownChanged: { if (!shown) { Bluetooth.defaultAdapter.discovering = false; } else { Bluetooth.defaultAdapter.enabled = true; Bluetooth.defaultAdapter.discovering = true; } } } ToggleDialog { shownPropertyString: "showNightLightDialog" dialog: NightLightDialog {} } ToggleDialog { shownPropertyString: "showWifiDialog" dialog: WifiDialog {} onShownChanged: { if (!shown) return; Network.enableWifi(); Network.rescanWifi(); } } component ToggleDialog: Loader { id: toggleDialogLoader required property string shownPropertyString property alias dialog: toggleDialogLoader.sourceComponent readonly property bool shown: root[shownPropertyString] anchors.fill: parent onShownChanged: if (shown) toggleDialogLoader.active = true; active: shown onActiveChanged: { if (active) { item.show = true; item.forceActiveFocus(); } } Connections { target: toggleDialogLoader.item function onDismiss() { toggleDialogLoader.item.show = false root[toggleDialogLoader.shownPropertyString] = false; } function onVisibleChanged() { if (!toggleDialogLoader.item.visible && !root[toggleDialogLoader.shownPropertyString]) toggleDialogLoader.active = false; } } } component LoaderedQuickPanelImplementation: Loader { id: quickPanelImplLoader required property string styleName Layout.alignment: item?.Layout.alignment ?? Qt.AlignHCenter Layout.fillWidth: item?.Layout.fillWidth ?? false visible: active active: Config.options.sidebar.quickToggles.style === styleName Connections { target: quickPanelImplLoader.item function onOpenAudioOutputDialog() { root.showAudioOutputDialog = true; } function onOpenAudioInputDialog() { root.showAudioInputDialog = true; } function onOpenBluetoothDialog() { root.showBluetoothDialog = true; } function onOpenNightLightDialog() { root.showNightLightDialog = true; } function onOpenWifiDialog() { root.showWifiDialog = true; } } } component SystemButtonRow: Item { implicitHeight: Math.max(uptimeContainer.implicitHeight, systemButtonsRow.implicitHeight) Rectangle { id: uptimeContainer anchors { top: parent.top bottom: parent.bottom left: parent.left } color: Appearance.colors.colLayer1 radius: height / 2 implicitWidth: uptimeRow.implicitWidth + 24 implicitHeight: uptimeRow.implicitHeight + 8 Row { id: uptimeRow anchors.centerIn: parent spacing: 8 CustomIcon { id: distroIcon anchors.verticalCenter: parent.verticalCenter width: 25 height: 25 source: SystemInfo.distroIcon colorize: true color: Appearance.colors.colOnLayer0 } StyledText { anchors.verticalCenter: parent.verticalCenter font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer0 text: Translation.tr("Up %1").arg(DateTime.uptime) textFormat: Text.MarkdownText } } } ButtonGroup { id: systemButtonsRow anchors { top: parent.top bottom: parent.bottom right: parent.right } color: Appearance.colors.colLayer1 padding: 4 QuickToggleButton { toggled: root.editMode visible: Config.options.sidebar.quickToggles.style === "android" buttonIcon: "edit" onClicked: root.editMode = !root.editMode StyledToolTip { text: Translation.tr("Edit quick toggles") + (root.editMode ? Translation.tr("\nLMB to enable/disable\nRMB to toggle size\nScroll to swap position") : "") } } QuickToggleButton { toggled: false buttonIcon: "restart_alt" onClicked: { Quickshell.execDetached(["hyprctl", "reload"]) Quickshell.reload(true); } StyledToolTip { text: Translation.tr("Reload Hyprland & Quickshell") } } QuickToggleButton { toggled: false buttonIcon: "settings" onClicked: { GlobalStates.sidebarRightOpen = false; Quickshell.execDetached(["qs", "-p", root.settingsQmlPath]); } StyledToolTip { text: Translation.tr("Settings") } } QuickToggleButton { toggled: false buttonIcon: "power_settings_new" onClicked: { GlobalStates.sessionOpen = true; } StyledToolTip { text: Translation.tr("Session") } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDeviceItem.qml ================================================ import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts DialogListItem { id: root required property var device property bool expanded: false pointingHandCursor: !expanded onClicked: expanded = !expanded altAction: () => expanded = !expanded component ActionButton: DialogButton { colBackground: Appearance.colors.colPrimary colBackgroundHover: Appearance.colors.colPrimaryHover colRipple: Appearance.colors.colPrimaryActive colText: Appearance.colors.colOnPrimary } contentItem: ColumnLayout { anchors { fill: parent topMargin: root.verticalPadding leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding } spacing: 0 RowLayout { // Name spacing: 10 MaterialSymbol { iconSize: Appearance.font.pixelSize.larger text: Icons.getBluetoothDeviceMaterialSymbol(root.device?.icon || "") color: Appearance.colors.colOnSurfaceVariant } ColumnLayout { spacing: 2 Layout.fillWidth: true StyledText { Layout.fillWidth: true color: Appearance.colors.colOnSurfaceVariant elide: Text.ElideRight text: root.device?.name || Translation.tr("Unknown device") textFormat: Text.PlainText } StyledText { visible: (root.device?.connected || root.device?.paired) ?? false Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext elide: Text.ElideRight text: { if (!root.device?.paired) return ""; let statusText = root.device?.connected ? Translation.tr("Connected") : Translation.tr("Paired"); if (!root.device?.batteryAvailable) return statusText; statusText += ` • ${Math.round(root.device?.battery * 100)}%`; return statusText; } } } MaterialSymbol { text: "keyboard_arrow_down" iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer3 rotation: root.expanded ? 180 : 0 Behavior on rotation { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } } } RowLayout { visible: root.expanded Layout.topMargin: 8 Item { Layout.fillWidth: true } ActionButton { readonly property bool p: root.device?.paired ?? false colBackground: p ? Appearance.colors.colError : ColorUtils.transparentize(Appearance.colors.colLayer3, 1) colBackgroundHover: p ? Appearance.colors.colErrorHover : ColorUtils.transparentize(Appearance.colors.colLayer3, 1) colRipple: p ? Appearance.colors.colErrorActive : Appearance.colors.colLayer3Hover colText: p ? Appearance.colors.colOnError : Appearance.colors.colPrimary buttonText: p ? Translation.tr("Forget") : Translation.tr("Always connect") onClicked: { if (root.device?.paired) { root.device?.forget(); } else { root.device?.pair(); } } } ActionButton { buttonText: root.device?.connected ? Translation.tr("Disconnect") : Translation.tr("Connect") onClicked: { if (root.device?.connected) { root.device.disconnect(); } else { root.device.connect(); } } } } Item { Layout.fillHeight: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell.Bluetooth import Quickshell import Quickshell.Wayland import Quickshell.Hyprland WindowDialog { id: root backgroundHeight: 600 WindowDialogTitle { text: Translation.tr("Bluetooth devices") } WindowDialogSeparator { visible: !(Bluetooth.defaultAdapter?.discovering ?? false) } StyledIndeterminateProgressBar { visible: Bluetooth.defaultAdapter?.discovering ?? false Layout.fillWidth: true Layout.topMargin: -8 Layout.bottomMargin: -8 Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large } StyledListView { Layout.fillHeight: true Layout.fillWidth: true Layout.topMargin: -15 Layout.bottomMargin: -16 Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large clip: true spacing: 0 animateAppearance: false model: ScriptModel { values: BluetoothStatus.friendlyDeviceList } delegate: BluetoothDeviceItem { required property BluetoothDevice modelData device: modelData anchors { left: parent?.left right: parent?.right } } } WindowDialogSeparator {} WindowDialogButtonRow { DialogButton { buttonText: Translation.tr("Details") onClicked: { Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`]); GlobalStates.sidebarRightOpen = false; } } Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Done") onClicked: root.dismiss() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarDayButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts RippleButton { id: button property string day property int isToday property bool bold Layout.fillWidth: false Layout.fillHeight: false implicitWidth: 38; implicitHeight: 38; toggled: (isToday == 1) buttonRadius: Appearance.rounding.small contentItem: StyledText { anchors.fill: parent text: day horizontalAlignment: Text.AlignHCenter font.weight: bold ? Font.DemiBold : Font.Normal color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : (isToday == 0) ? Appearance.colors.colOnLayer1 : Appearance.colors.colOutlineVariant Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarHeaderButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick RippleButton { id: button property string buttonText: "" property string tooltipText: "" property bool forceCircle: false implicitHeight: 30 implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2) Behavior on implicitWidth { SmoothedAnimation { velocity: Appearance.animation.elementMove.velocity } } background.anchors.fill: button buttonRadius: Appearance.rounding.full colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colRipple: Appearance.colors.colLayer2Active contentItem: StyledText { text: buttonText horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } StyledToolTip { text: tooltipText extraVisibleCondition: tooltipText.length > 0 } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarWidget.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import "calendar_layout.js" as CalendarLayout import QtQuick import QtQuick.Layouts Item { // Layout.topMargin: 10 anchors.topMargin: 10 property int monthShift: 0 property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift) property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0) width: calendarColumn.width implicitHeight: calendarColumn.height + 10 * 2 Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageDown) { monthShift++; } else if (event.key === Qt.Key_PageUp) { monthShift--; } event.accepted = true; } } MouseArea { anchors.fill: parent onWheel: (event) => { if (event.angleDelta.y > 0) { monthShift--; } else if (event.angleDelta.y < 0) { monthShift++; } } } ColumnLayout { id: calendarColumn anchors.centerIn: parent spacing: 5 // Calendar header RowLayout { Layout.fillWidth: true spacing: 5 CalendarHeaderButton { clip: true buttonText: `${monthShift != 0 ? "• " : ""}${viewingDate.toLocaleDateString(Qt.locale(), "MMMM yyyy")}` tooltipText: (monthShift === 0) ? "" : Translation.tr("Jump to current month") downAction: () => { monthShift = 0; } } Item { Layout.fillWidth: true Layout.fillHeight: false } CalendarHeaderButton { forceCircle: true downAction: () => { monthShift--; } contentItem: MaterialSymbol { text: "chevron_left" iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } } CalendarHeaderButton { forceCircle: true downAction: () => { monthShift++; } contentItem: MaterialSymbol { text: "chevron_right" iconSize: Appearance.font.pixelSize.larger horizontalAlignment: Text.AlignHCenter color: Appearance.colors.colOnLayer1 } } } // Week days row RowLayout { id: weekDaysRow Layout.alignment: Qt.AlignHCenter Layout.fillHeight: false spacing: 5 Repeater { model: CalendarLayout.weekDays delegate: CalendarDayButton { day: Translation.tr(modelData.day) isToday: modelData.today bold: true enabled: false } } } // Real week rows Repeater { id: calendarRows // model: calendarLayout model: 6 delegate: RowLayout { Layout.alignment: Qt.AlignHCenter Layout.fillHeight: false spacing: 5 Repeater { model: Array(7).fill(modelData) delegate: CalendarDayButton { day: calendarLayout[modelData][index].day isToday: calendarLayout[modelData][index].today } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/calendar_layout.js ================================================ const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW: { day: 'Mo', today: 0 }, { day: 'Tu', today: 0 }, { day: 'We', today: 0 }, { day: 'Th', today: 0 }, { day: 'Fr', today: 0 }, { day: 'Sa', today: 0 }, { day: 'Su', today: 0 }, ] function checkLeapYear(year) { return ( year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)); } function getMonthDays(month, year) { const leapYear = checkLeapYear(year); if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31; if (month == 2 && leapYear) return 29; if (month == 2 && !leapYear) return 28; return 30; } function getNextMonthDays(month, year) { const leapYear = checkLeapYear(year); if (month == 1 && leapYear) return 29; if (month == 1 && !leapYear) return 28; if (month == 12) return 31; if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30; return 31; } function getPrevMonthDays(month, year) { const leapYear = checkLeapYear(year); if (month == 3 && leapYear) return 29; if (month == 3 && !leapYear) return 28; if (month == 1) return 31; if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30; return 31; } function getDateInXMonthsTime(x) { var currentDate = new Date(); // Get the current date if (x == 0) return currentDate; // If x is 0, return the current date var targetMonth = currentDate.getMonth() + x; // Calculate the target month var targetYear = currentDate.getFullYear(); // Get the current year // Adjust the year and month if necessary targetYear += Math.floor(targetMonth / 12); targetMonth = (targetMonth % 12 + 12) % 12; // Create a new date object with the target year and month var targetDate = new Date(targetYear, targetMonth, 1); // Set the day to the last day of the month to get the desired date // targetDate.setDate(0); return targetDate; } function getCalendarLayout(dateObject, highlight) { if (!dateObject) dateObject = new Date(); const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK const day = dateObject.getDate(); const month = dateObject.getMonth() + 1; const year = dateObject.getFullYear(); const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7; const daysInMonth = getMonthDays(month, year); const daysInNextMonth = getNextMonthDays(month, year); const daysInPrevMonth = getPrevMonthDays(month, year); // Fill var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1); var toFill, dim; if (weekdayOfMonthFirst == 0) { toFill = 1; dim = daysInMonth; } else { toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1)); dim = daysInPrevMonth; } var calendar = [...Array(6)].map(() => Array(7)); var i = 0, j = 0; while (i < 6 && j < 7) { calendar[i][j] = { "day": toFill, "today": ((toFill == day && monthDiff == 0 && highlight) ? 1 : ( monthDiff == 0 ? 0 : -1 )) }; // Increment toFill++; if (toFill > dim) { // Next month? monthDiff++; if (monthDiff == 0) dim = daysInMonth; else if (monthDiff == 1) dim = daysInNextMonth; toFill = 1; } // Next tile j++; if (j == 7) { j = 0; i++; } } return calendar; } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io import Quickshell import Quickshell.Wayland import Quickshell.Hyprland WindowDialog { id: root property var screen: root.QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) backgroundHeight: 700 WindowDialogTitle { text: Translation.tr("Eye protection") } WindowDialogSectionHeader { text: Translation.tr("Night Light") } WindowDialogSeparator { Layout.topMargin: -22 Layout.leftMargin: 0 Layout.rightMargin: 0 } Column { id: nightLightColumn Layout.topMargin: -16 Layout.fillWidth: true ConfigSwitch { anchors { left: parent.left right: parent.right } iconSize: Appearance.font.pixelSize.larger buttonIcon: "check" text: Translation.tr("Enable now") checked: Hyprsunset.temperatureActive onCheckedChanged: { Hyprsunset.toggleTemperature(checked) } } ConfigSwitch { anchors { left: parent.left right: parent.right } iconSize: Appearance.font.pixelSize.larger buttonIcon: "night_sight_auto" text: Translation.tr("Automatic") checked: Config.options.light.night.automatic onCheckedChanged: { Config.options.light.night.automatic = checked; } } WindowDialogSlider { anchors { left: parent.left right: parent.right leftMargin: 4 rightMargin: 4 } text: Translation.tr("Intensity") from: 6500 to: 1200 stopIndicatorValues: [5000, to] value: Config.options.light.night.colorTemperature onMoved: Config.options.light.night.colorTemperature = value tooltipContent: `${Math.round(value)}K` } } WindowDialogSectionHeader { text: Translation.tr("Anti-flashbang (experimental)") } WindowDialogSeparator { Layout.topMargin: -22 Layout.leftMargin: 0 Layout.rightMargin: 0 } Column { id: antiFlashbangColumn Layout.topMargin: -16 Layout.fillWidth: true ConfigSwitch { anchors { left: parent.left right: parent.right } iconSize: Appearance.font.pixelSize.larger buttonIcon: "filter" text: Translation.tr("Content adjustment") checked: HyprlandAntiFlashbangShader.enabled onCheckedChanged: { if (checked) HyprlandAntiFlashbangShader.enable() else HyprlandAntiFlashbangShader.disable() } StyledToolTip { text: Translation.tr("Dims screen content as needed.

Pros: Immediately responsive
Cons: Expensive and can hurt color accuracy

Uses a Hyprland screen shader") } } ConfigSwitch { anchors { left: parent.left right: parent.right } iconSize: Appearance.font.pixelSize.larger buttonIcon: "light_mode" text: Translation.tr("Brightness adjustment") checked: Config.options.light.antiFlashbang.enable onCheckedChanged: { Config.options.light.antiFlashbang.enable = checked; } StyledToolTip { text: Translation.tr("Adapts the display (physical screen) brightness

Pros: Less expensive, retains colors
Cons: Not immediately responsive

Adjusts display brightness after each Hyprland IPC event") } } } WindowDialogSectionHeader { text: Translation.tr("Brightness") } WindowDialogSeparator { Layout.topMargin: -22 Layout.leftMargin: 0 Layout.rightMargin: 0 } Column { id: brightnessColumn Layout.topMargin: -16 Layout.fillWidth: true WindowDialogSlider { anchors { left: parent.left right: parent.right leftMargin: 4 rightMargin: 4 } value: root.brightnessMonitor.brightness onMoved: root.brightnessMonitor.setBrightness(value) } } WindowDialogSectionHeader { text: Translation.tr("Gamma") } WindowDialogSeparator { Layout.topMargin: -22 Layout.leftMargin: 0 Layout.rightMargin: 0 } Column { id: gammaColumn Layout.topMargin: -16 Layout.fillWidth: true Layout.fillHeight: true WindowDialogSlider { anchors { left: parent.left right: parent.right leftMargin: 4 rightMargin: 4 } from: Hyprsunset.gammaLowerLimit / 100 value: Hyprsunset.gamma / 100 onMoved: Hyprsunset.setGamma(value * 100) tooltipContent: `${Math.round(value * 100)}%` } } WindowDialogButtonRow { Layout.fillWidth: true Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Done") onClicked: root.dismiss() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/notifications/NotificationList.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { id: root NotificationListView { // Scrollable window id: listview anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.bottom: statusRow.top anchors.bottomMargin: 5 clip: true layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: listview.width height: listview.height radius: Appearance.rounding.normal } } popup: false } // Placeholder when list is empty PagePlaceholder { shown: Notifications.list.length === 0 icon: "notifications_active" description: Translation.tr("Nothing") shape: MaterialShape.Shape.Ghostish descriptionHorizontalAlignment: Text.AlignHCenter } ButtonGroup { id: statusRow anchors { left: parent.left right: parent.right bottom: parent.bottom } NotificationStatusButton { Layout.fillWidth: false buttonIcon: "notifications_paused" toggled: Notifications.silent onClicked: () => { Notifications.silent = !Notifications.silent; } } NotificationStatusButton { enabled: false Layout.fillWidth: true buttonText: Translation.tr("%1 notifications").arg(Notifications.list.length) } NotificationStatusButton { Layout.fillWidth: false buttonIcon: "delete_sweep" onClicked: () => { Notifications.discardAllNotifications() } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/notifications/NotificationStatusButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts GroupButton { id: button property string buttonIcon: "" property string buttonText: "" baseHeight: 36 baseWidth: content.implicitWidth + 46 clickedWidth: baseWidth + 6 buttonRadius: baseHeight / 2 buttonRadiusPressed: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colBackgroundActive: Appearance.colors.colLayer2Active property color colText: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 contentItem: Item { id: content anchors.fill: parent implicitWidth: contentRowLayout.implicitWidth implicitHeight: contentRowLayout.implicitHeight RowLayout { id: contentRowLayout anchors.centerIn: parent spacing: 5 MaterialSymbol { visible: buttonIcon !== "" text: buttonIcon iconSize: Appearance.font.pixelSize.huge color: button.colText } StyledText { visible: buttonText !== "" text: buttonText font.pixelSize: Appearance.font.pixelSize.small color: button.colText } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/PomodoroTimer.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Item { id: root implicitHeight: contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth ColumnLayout { id: contentColumn anchors.fill: parent spacing: 0 // The Pomodoro timer circle CircularProgress { Layout.alignment: Qt.AlignHCenter lineWidth: 8 value: { return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration; } implicitSize: 200 enableAnimation: true ColumnLayout { anchors.centerIn: parent spacing: 0 StyledText { Layout.alignment: Qt.AlignHCenter text: { let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); return `${minutes}:${seconds}`; } font.pixelSize: 40 color: Appearance.m3colors.m3onSurface } StyledText { Layout.alignment: Qt.AlignHCenter text: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colSubtext } } Rectangle { radius: Appearance.rounding.full color: Appearance.colors.colLayer2 anchors { right: parent.right bottom: parent.bottom } implicitWidth: 36 implicitHeight: implicitWidth StyledText { id: cycleText anchors.centerIn: parent color: Appearance.colors.colOnLayer2 text: TimerService.pomodoroCycle + 1 } } } // The Start/Stop and Reset buttons RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 10 RippleButton { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: TimerService.togglePomodoro() colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary } RippleButton { implicitHeight: 35 implicitWidth: 90 onClicked: TimerService.resetPomodoro() enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer colBackgroundHover: Appearance.colors.colErrorContainerHover colRipple: Appearance.colors.colErrorContainerActive contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: Translation.tr("Reset") color: Appearance.colors.colOnErrorContainer } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/PomodoroWidget.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { id: root property var tabButtonList: [ {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] // These are keybinds for stopwatch and pomodoro Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs if (event.key === Qt.Key_PageDown) { tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { tabBar.decrementCurrentIndex(); } event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (tabBar.currentIndex === 0) { TimerService.togglePomodoro() } else { TimerService.toggleStopwatch() } event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R if (tabBar.currentIndex === 0) { TimerService.resetPomodoro() } else { TimerService.stopwatchReset() } event.accepted = true } else if (event.key === Qt.Key_L) { // Record lap with L TimerService.stopwatchRecordLap() event.accepted = true } } ColumnLayout { anchors.fill: parent spacing: 0 SecondaryTabBar { id: tabBar currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { buttonText: modelData.name buttonIcon: modelData.icon } } } SwipeView { id: swipeView Layout.topMargin: 10 Layout.fillWidth: true Layout.fillHeight: true spacing: 10 clip: true currentIndex: tabBar.currentIndex // Tabs PomodoroTimer {} Stopwatch {} } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/Stopwatch.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Item { id: stopwatchTab Layout.fillWidth: true Layout.fillHeight: true Item { anchors { fill: parent topMargin: 8 leftMargin: 16 rightMargin: 16 } RowLayout { // Elapsed id: elapsedIndicator anchors { top: undefined verticalCenter: parent.verticalCenter left: controlButtons.left leftMargin: 6 } states: State { name: "hasLaps" when: TimerService.stopwatchLaps.length > 0 AnchorChanges { target: elapsedIndicator anchors.top: parent.top anchors.verticalCenter: undefined anchors.left: controlButtons.left } } transitions: Transition { AnchorAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } spacing: 0 StyledText { // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness font.pixelSize: 40 color: Appearance.m3colors.m3onSurface text: { let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') return `${minutes}:${seconds}` } } StyledText { Layout.fillWidth: true font.pixelSize: 40 color: Appearance.colors.colSubtext text: { return `:${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}` } } } // Laps StyledListView { id: lapsList anchors { top: elapsedIndicator.bottom bottom: controlButtons.top left: parent.left right: parent.right topMargin: 16 bottomMargin: 16 } spacing: 4 clip: true popin: true model: ScriptModel { values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) } delegate: Rectangle { id: lapItem required property int index required property var modelData property var horizontalPadding: 10 property var verticalPadding: 6 width: lapsList.width implicitHeight: lapRow.implicitHeight + verticalPadding * 2 implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 color: Appearance.colors.colLayer2 radius: Appearance.rounding.small RowLayout { id: lapRow anchors { fill: parent leftMargin: lapItem.horizontalPadding rightMargin: lapItem.horizontalPadding topMargin: lapItem.verticalPadding bottomMargin: lapItem.verticalPadding } StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext text: `${TimerService.stopwatchLaps.length - lapItem.index}.` } StyledText { font.pixelSize: Appearance.font.pixelSize.small text: { const lapTime = lapItem.modelData const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') const totalSeconds = Math.floor(lapTime) / 100 const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') return `${minutes}:${seconds}.${_10ms}` } } Item { Layout.fillWidth: true } StyledText { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colPrimary text: { const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1 const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0 const lapTime = lapItem.modelData - lastTime const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') const totalSeconds = Math.floor(lapTime) / 100 const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` } } } } } RowLayout { id: controlButtons anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 6 } spacing: 4 RippleButton { Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: { TimerService.toggleStopwatch() } colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive contentItem: StyledText { horizontalAlignment: Text.AlignHCenter color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") } } RippleButton { implicitHeight: 35 implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: { if (TimerService.stopwatchRunning) TimerService.stopwatchRecordLap() else TimerService.stopwatchReset() } enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0 colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive contentItem: StyledText { horizontalAlignment: Text.AlignHCenter text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/AbstractQuickPanel.qml ================================================ import QtQuick import qs.modules.common Rectangle { id: root radius: Appearance.rounding.normal color: Appearance.colors.colLayer1 signal openAudioOutputDialog() signal openAudioInputDialog() signal openBluetoothDialog() signal openNightLightDialog() signal openWifiDialog() } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/AndroidQuickPanel.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import qs.modules.ii.sidebarRight.quickToggles.androidStyle AbstractQuickPanel { id: root property bool editMode: false Layout.fillWidth: true // Sizes implicitHeight: (editMode ? contentItem.implicitHeight : usedRows.implicitHeight) + root.padding * 2 Behavior on implicitHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } property real spacing: 6 property real padding: 6 readonly property real baseCellWidth: { // This is the wrong calculation, but it looks correct in reality??? // (theoretically spacing should be multiplied by 1 column less) const availableWidth = root.width - (root.padding * 2) - (root.spacing * (root.columns)) return availableWidth / root.columns } readonly property real baseCellHeight: 56 // Toggles readonly property list availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition", "antiFlashbang"] readonly property int columns: Config.options.sidebar.quickToggles.android.columns readonly property list toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : [] readonly property list toggleRows: toggleRowsForList(toggles) readonly property list unusedToggles: { const types = availableToggleTypes.filter(type => !toggles.some(toggle => (toggle && toggle.type === type))) return types.map(type => { return { type: type, size: 1 } }) } readonly property list unusedToggleRows: toggleRowsForList(unusedToggles) function toggleRowsForList(togglesList) { var rows = []; var row = []; var totalSize = 0; // Total cols taken in current row for (var i = 0; i < togglesList.length; i++) { if (!togglesList[i]) continue; if (totalSize + togglesList[i].size > columns) { rows.push(row); row = []; totalSize = 0; } row.push(togglesList[i]); totalSize += togglesList[i].size; } if (row.length > 0) { rows.push(row); } return rows; } Column { id: contentItem anchors { fill: parent margins: root.padding } spacing: 12 Column { id: usedRows spacing: root.spacing Repeater { id: usedRowsRepeater model: ScriptModel { values: Array(root.toggleRows.length) } delegate: ButtonGroup { id: toggleRow required property int index property var modelData: root.toggleRows[index] property int startingIndex: { const rows = root.toggleRows; let sum = 0; for (let i = 0; i < index; i++) { sum += rows[i].length; } return sum; } spacing: root.spacing Repeater { model: ScriptModel { values: toggleRow?.modelData ?? [] objectProp: "type" } delegate: AndroidToggleDelegateChooser { startingIndex: toggleRow.startingIndex editMode: root.editMode baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight spacing: root.spacing onOpenAudioOutputDialog: root.openAudioOutputDialog() onOpenAudioInputDialog: root.openAudioInputDialog() onOpenBluetoothDialog: root.openBluetoothDialog() onOpenNightLightDialog: root.openNightLightDialog() onOpenWifiDialog: root.openWifiDialog() } } } } } FadeLoader { shown: root.editMode anchors { left: parent.left right: parent.right leftMargin: root.baseCellHeight / 2 rightMargin: root.baseCellHeight / 2 } sourceComponent: Rectangle { implicitHeight: 1 color: Appearance.colors.colOutlineVariant } } FadeLoader { shown: root.editMode sourceComponent: Column { id: unusedRows spacing: root.spacing Repeater { model: ScriptModel { values: Array(root.unusedToggleRows.length) } delegate: ButtonGroup { id: unusedToggleRow required property int index property var modelData: root.unusedToggleRows[index] spacing: root.spacing Repeater { model: ScriptModel { values: unusedToggleRow?.modelData ?? [] objectProp: "type" } delegate: AndroidToggleDelegateChooser { startingIndex: -1 editMode: root.editMode baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight spacing: root.spacing } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/ClassicQuickPanel.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell.Bluetooth import qs.modules.ii.sidebarRight.quickToggles.classicStyle AbstractQuickPanel { id: root Layout.alignment: Qt.AlignHCenter implicitWidth: buttonGroup.implicitWidth implicitHeight: buttonGroup.implicitHeight color: "transparent" ButtonGroup { id: buttonGroup spacing: 5 padding: 5 color: Appearance.colors.colLayer1 NetworkToggle { altAction: () => { root.openWifiDialog(); } } BluetoothToggle { altAction: () => { root.openBluetoothDialog(); } } NightLight {} GameMode {} IdleInhibitor {} EasyEffectsToggle {} CloudflareWarp {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidAntiFlashbangToggle.qml ================================================ import qs.modules.common.models.quickToggles import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { id: root toggleModel: AntiFlashbangToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidAudioToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { id: root toggleModel: AudioToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidBluetoothToggle.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.common.widgets import QtQuick import Quickshell import Quickshell.Bluetooth AndroidQuickToggleButton { id: root toggleModel: BluetoothToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidCloudflareWarpToggle.qml ================================================ import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell import Quickshell.Io AndroidQuickToggleButton { id: root toggleModel: CloudflareWarpToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidColorPickerToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { id: root toggleModel: ColorPickerToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidDarkModeToggle.qml ================================================ import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: DarkModeToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidEasyEffectsToggle.qml ================================================ import qs import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: EasyEffectsToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidGameModeToggle.qml ================================================ import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell import Quickshell.Io AndroidQuickToggleButton { toggleModel: GameModeToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidIdleInhibitorToggle.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.common.widgets import QtQuick AndroidQuickToggleButton { toggleModel: IdleInhibitorToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidMicToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: MicToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidMusicRecognition.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import QtQuick import Quickshell import Quickshell.Io import qs.services AndroidQuickToggleButton { toggleModel: MusicRecognitionToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNetworkToggle.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.common.widgets import QtQuick AndroidQuickToggleButton { id: root toggleModel: NetworkToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml ================================================ import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: NightLightToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNotificationToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: NotificationToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidOnScreenKeyboardToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell AndroidQuickToggleButton { toggleModel: OnScreenKeyboardToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell import Quickshell.Services.UPower AndroidQuickToggleButton { toggleModel: PowerProfilesToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.common.widgets GroupButton { id: root // Info to be passed to by repeater required property int buttonIndex required property var buttonData required property bool expandedSize required property real baseCellWidth required property real baseCellHeight required property real cellSpacing required property int cellSize // Signals signal openMenu() // Declared in specific toggles property QuickToggleModel toggleModel property string name: toggleModel?.name ?? "" property string statusText: (toggleModel?.hasStatusText) ? (toggleModel?.statusText || (toggled ? Translation.tr("Active") : Translation.tr("Inactive"))) : "" property string tooltipText: toggleModel?.tooltipText ?? "" property string buttonIcon: toggleModel?.icon ?? "close" property bool available: toggleModel?.available ?? true toggled: toggleModel?.toggled ?? false property var mainAction: toggleModel?.mainAction ?? null altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null) // Edit mode state property bool editMode: false // Sizing shenanigans baseWidth: root.baseCellWidth * cellSize + cellSpacing * (cellSize - 1) baseHeight: root.baseCellHeight enableImplicitWidthAnimation: !editMode && root.mouseArea.containsMouse enableImplicitHeightAnimation: !editMode && root.mouseArea.containsMouse Behavior on baseWidth { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on baseHeight { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } opacity: 0 Component.onCompleted: { opacity = 1 } Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } enabled: available || editMode padding: 6 horizontalPadding: padding verticalPadding: padding colBackground: Appearance.colors.colLayer2 colBackgroundToggled: (altAction && expandedSize) ? Appearance.colors.colLayer2 : Appearance.colors.colPrimary colBackgroundToggledHover: (altAction && expandedSize) ? Appearance.colors.colLayer2Hover : Appearance.colors.colPrimaryHover colBackgroundToggledActive: (altAction && expandedSize) ? Appearance.colors.colLayer2Active : Appearance.colors.colPrimaryActive buttonRadius: toggled ? Appearance.rounding.large : height / 2 buttonRadiusPressed: Appearance.rounding.normal property color colText: (toggled && !(altAction && expandedSize) && enabled) ? Appearance.colors.colOnPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer2, enabled ? 0 : 0.7) property color colIcon: expandedSize ? ((root.toggled) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer3) : colText onClicked: { if (root.expandedSize && root.altAction) root.altAction(); else root.mainAction(); } contentItem: RowLayout { id: contentItem spacing: 4 anchors { centerIn: root.expandedSize ? undefined : parent fill: root.expandedSize ? parent : undefined leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding } // Icon MouseArea { id: iconMouseArea hoverEnabled: true acceptedButtons: (root.expandedSize && root.altAction) ? Qt.LeftButton : Qt.NoButton Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true Layout.topMargin: root.verticalPadding Layout.bottomMargin: root.verticalPadding implicitHeight: iconBackground.implicitHeight implicitWidth: iconBackground.implicitWidth cursorShape: Qt.PointingHandCursor onClicked: root.mainAction() Rectangle { id: iconBackground anchors.fill: parent implicitWidth: height radius: root.radius - root.verticalPadding color: { const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3 const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1 return ColorUtils.transparentize(baseColor, transparentizeAmount) } Behavior on radius { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MaterialSymbol { anchors.centerIn: parent fill: root.toggled ? 1 : 0 iconSize: root.expandedSize ? 22 : 24 color: root.colIcon text: root.buttonIcon } // State layer Loader { anchors.fill: parent active: (root.expandedSize && root.altAction) sourceComponent: Rectangle { radius: iconBackground.radius color: ColorUtils.transparentize(root.colIcon, iconMouseArea.containsPress ? 0.88 : iconMouseArea.containsMouse ? 0.95 : 1) Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } } } // Text column for expanded size Loader { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true visible: root.expandedSize active: visible sourceComponent: Column { spacing: -2 StyledText { anchors { left: parent.left right: parent.right } font.pixelSize: Appearance.font.pixelSize.smallie font.weight: 600 color: root.colText elide: Text.ElideRight text: root.name } StyledText { visible: root.statusText anchors { left: parent.left right: parent.right } font { pixelSize: Appearance.font.pixelSize.smaller weight: 100 } color: root.colText elide: Text.ElideRight text: root.statusText } } } } MouseArea { // Blocking MouseArea for edit interactions id: editModeInteraction visible: root.editMode anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true acceptedButtons: Qt.AllButtons function toggleEnabled() { const index = root.buttonIndex; const toggleList = Config.options.sidebar.quickToggles.android.toggles; const buttonType = root.buttonData.type; if (!toggleList.find(toggle => toggle.type === buttonType)) { toggleList.push({ type: buttonType, size: 1 }); } else { toggleList.splice(index, 1); } } function toggleSize() { const index = root.buttonIndex; const toggleList = Config.options.sidebar.quickToggles.android.toggles; const buttonType = root.buttonData.type; if (!toggleList.find(toggle => toggle.type === buttonType)) return; toggleList[index].size = 3 - toggleList[index].size; // Alternate between 1 and 2 } function movePositionBy(offset) { const index = root.buttonIndex; const toggleList = Config.options.sidebar.quickToggles.android.toggles; const buttonType = root.buttonData.type; const targetIndex = index + offset; if (!toggleList.find(toggle => toggle.type === buttonType)) return; if (targetIndex < 0 || targetIndex >= toggleList.length) return; const temp = toggleList[index]; toggleList[index] = toggleList[targetIndex]; toggleList[targetIndex] = temp; } onReleased: (event) => { if (event.button === Qt.LeftButton) toggleEnabled(); } onPressed: (event) => { if (event.button === Qt.RightButton) toggleSize(); } onPressAndHold: (event) => { // Also toggle size toggleSize(); } onWheel: (event) => { const index = root.buttonIndex; const toggleList = Config.options.sidebar.quickToggles.android.toggles; const buttonType = root.buttonData.type; if (event.angleDelta.y < 0) { // Move to right movePositionBy(1); } else if (event.angleDelta.y > 0) { // Move to left movePositionBy(-1); } event.accepted = true; } } StyledToolTip { extraVisibleCondition: root.tooltipText !== "" text: root.tooltipText } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml ================================================ import qs import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.services import QtQuick import Quickshell import Quickshell.Hyprland AndroidQuickToggleButton { toggleModel: ScreenSnipToggle {} } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml ================================================ pragma ComponentBehavior: Bound import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth DelegateChooser { id: root property bool editMode: false required property real baseCellWidth required property real baseCellHeight required property real spacing required property int startingIndex signal openAudioOutputDialog() signal openAudioInputDialog() signal openBluetoothDialog() signal openNightLightDialog() signal openWifiDialog() role: "type" DelegateChoice { roleValue: "antiFlashbang"; AndroidAntiFlashbangToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openNightLightDialog() } } } DelegateChoice { roleValue: "audio"; AndroidAudioToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openAudioOutputDialog() } } } DelegateChoice { roleValue: "bluetooth"; AndroidBluetoothToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openBluetoothDialog() } } } DelegateChoice { roleValue: "cloudflareWarp"; AndroidCloudflareWarpToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "colorPicker"; AndroidColorPickerToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "darkMode"; AndroidDarkModeToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "easyEffects"; AndroidEasyEffectsToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "gameMode"; AndroidGameModeToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "idleInhibitor"; AndroidIdleInhibitorToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "mic"; AndroidMicToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openAudioInputDialog() } } } DelegateChoice { roleValue: "musicRecognition"; AndroidMusicRecognition { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "network"; AndroidNetworkToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openWifiDialog() } } } DelegateChoice { roleValue: "nightLight"; AndroidNightLightToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size onOpenMenu: { root.openNightLightDialog() } } } DelegateChoice { roleValue: "notifications"; AndroidNotificationToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "onScreenKeyboard"; AndroidOnScreenKeyboardToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "powerProfile"; AndroidPowerProfileToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } DelegateChoice { roleValue: "screenSnip"; AndroidScreenSnipToggle { required property int index required property var modelData buttonIndex: root.startingIndex + index buttonData: modelData editMode: root.editMode expandedSize: modelData.size > 1 baseCellWidth: root.baseCellWidth baseCellHeight: root.baseCellHeight cellSpacing: root.spacing cellSize: modelData.size } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/BluetoothToggle.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Bluetooth import Quickshell.Io import Quickshell.Hyprland QuickToggleButton { id: root visible: BluetoothStatus.available toggled: BluetoothStatus.enabled buttonIcon: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled" onClicked: { Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled } altAction: () => { Quickshell.execDetached(["bash", "-c", `${Config.options.apps.bluetooth}`]) GlobalStates.sidebarRightOpen = false } StyledToolTip { text: Translation.tr("%1 | Right-click to configure").arg( (BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth")) + (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "") ) } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/CloudflareWarp.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import Quickshell.Io import Quickshell QuickToggleButton { id: root toggled: false visible: false contentItem: CustomIcon { id: distroIcon source: 'cloudflare-dns-symbolic' anchors.centerIn: parent width: 16 height: 16 colorize: true color: root.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } onClicked: { if (toggled) { root.toggled = false Quickshell.execDetached(["warp-cli", "disconnect"]) } else { root.toggled = true Quickshell.execDetached(["warp-cli", "connect"]) } } Process { id: connectProc command: ["warp-cli", "connect"] onExited: (exitCode, exitStatus) => { if (exitCode !== 0) { Quickshell.execDetached(["notify-send", Translation.tr("Cloudflare WARP"), Translation.tr("Connection failed. Please inspect manually with the warp-cli command") , "-a", "Shell" ]) } } } Process { id: registrationProc command: ["warp-cli", "registration", "new"] onExited: (exitCode, exitStatus) => { console.log("Warp registration exited with code and status:", exitCode, exitStatus) if (exitCode === 0) { connectProc.running = true } else { Quickshell.execDetached(["notify-send", Translation.tr("Cloudflare WARP"), Translation.tr("Registration failed. Please inspect manually with the warp-cli command"), "-a", "Shell" ]) } } } Process { id: fetchActiveState running: true command: ["bash", "-c", "warp-cli status"] stdout: StdioCollector { id: warpStatusCollector onStreamFinished: { if (warpStatusCollector.text.length > 0) { root.visible = true } if (warpStatusCollector.text.includes("Unable")) { registrationProc.running = true } else if (warpStatusCollector.text.includes("Connected")) { root.toggled = true } else if (warpStatusCollector.text.includes("Disconnected")) { root.toggled = false } } } } StyledToolTip { text: Translation.tr("Cloudflare WARP (1.1.1.1)") } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/EasyEffectsToggle.qml ================================================ import qs.modules.common.widgets import qs import qs.services import QtQuick import Quickshell.Io import Quickshell import Quickshell.Hyprland QuickToggleButton { id: root visible: EasyEffects.available toggled: EasyEffects.active buttonIcon: "instant_mix" Component.onCompleted: { EasyEffects.fetchActiveState() } onClicked: { EasyEffects.toggle() } altAction: () => { Quickshell.execDetached(["bash", "-c", "flatpak run com.github.wwmm.easyeffects || easyeffects"]) GlobalStates.sidebarRightOpen = false } StyledToolTip { text: Translation.tr("EasyEffects | Right-click to configure") } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/GameMode.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import Quickshell import Quickshell.Io QuickToggleButton { id: root buttonIcon: "gamepad" toggled: toggled onClicked: { root.toggled = !root.toggled if (root.toggled) { Quickshell.execDetached(["bash", "-c", `hyprctl --batch "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"`]) } else { Quickshell.execDetached(["hyprctl", "reload"]) } } Process { id: fetchActiveState running: true command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`] onExited: (exitCode, exitStatus) => { root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit } } StyledToolTip { text: Translation.tr("Game mode") } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/IdleInhibitor.qml ================================================ import qs.modules.common.widgets import qs.services QuickToggleButton { id: root toggled: Idle.inhibit buttonIcon: "coffee" onClicked: { Idle.toggleInhibit() } StyledToolTip { text: Translation.tr("Keep system awake") } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.sidebarRight.quickToggles import qs import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland QuickToggleButton { toggled: Network.wifiStatus !== "disabled" buttonIcon: Network.materialSymbol onClicked: Network.toggleWifi() altAction: () => { Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]) GlobalStates.sidebarRightOpen = false } StyledToolTip { text: Translation.tr("%1 | Right-click to configure").arg(Network.networkName) } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/NightLight.qml ================================================ import QtQuick import qs.modules.common import qs.modules.common.widgets import qs.services import Quickshell.Io QuickToggleButton { id: nightLightButton toggled: Hyprsunset.temperatureActive buttonIcon: Config.options.light.night.automatic ? "night_sight_auto" : "bedtime" onClicked: { Hyprsunset.toggleTemperature() } altAction: () => { Config.options.light.night.automatic = !Config.options.light.night.automatic } Component.onCompleted: { Hyprsunset.fetchState() } StyledToolTip { text: Translation.tr("Night Light | Right-click to toggle Auto mode") } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick GroupButton { id: button property string buttonIcon baseWidth: 40 baseHeight: 40 clickedWidth: baseWidth + 20 toggled: false buttonRadius: (altAction && toggled) ? Appearance?.rounding.normal : Math.min(baseHeight, baseWidth) / 2 buttonRadiusPressed: Appearance?.rounding?.small contentItem: MaterialSymbol { anchors.centerIn: parent iconSize: 22 fill: toggled ? 1 : 0 color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: buttonIcon Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Item { id: root required property var taskList property string emptyPlaceholderIcon property string emptyPlaceholderText property int todoListItemSpacing: 5 property int todoListItemPadding: 8 property int listBottomPadding: 80 StyledListView { id: listView anchors.fill: parent spacing: root.todoListItemSpacing animateAppearance: false model: ScriptModel { values: root.taskList } delegate: Item { id: todoItem required property var modelData property bool pendingDoneToggle: false property bool pendingDelete: false property bool enableHeightAnimation: false implicitHeight: todoItemRectangle.implicitHeight width: ListView.view.width clip: true Behavior on implicitHeight { enabled: enableHeightAnimation NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } Rectangle { id: todoItemRectangle anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom implicitHeight: todoContentRowLayout.implicitHeight color: Appearance.colors.colLayer2 radius: Appearance.rounding.small ColumnLayout { id: todoContentRowLayout anchors.left: parent.left anchors.right: parent.right StyledText { id: todoContentText Layout.fillWidth: true // Needed for wrapping Layout.leftMargin: 10 Layout.rightMargin: 10 Layout.topMargin: todoListItemPadding text: todoItem.modelData.content wrapMode: Text.Wrap } RowLayout { Layout.leftMargin: 10 Layout.rightMargin: 10 Layout.bottomMargin: todoListItemPadding Item { Layout.fillWidth: true } TodoItemActionButton { Layout.fillWidth: false onClicked: { if (!todoItem.modelData.done) Todo.markDone(todoItem.modelData.originalIndex); else Todo.markUnfinished(todoItem.modelData.originalIndex); } contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: todoItem.modelData.done ? "remove_done" : "check" iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } } TodoItemActionButton { Layout.fillWidth: false onClicked: { Todo.deleteItem(todoItem.modelData.originalIndex); } contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: "delete_forever" iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } } } } } } } Item { // Placeholder when list is empty visible: opacity > 0 opacity: taskList.length === 0 ? 1 : 0 anchors.fill: parent Behavior on opacity { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } ColumnLayout { anchors.centerIn: parent spacing: 5 MaterialSymbol { Layout.alignment: Qt.AlignHCenter iconSize: 55 color: Appearance.m3colors.m3outline text: emptyPlaceholderIcon } StyledText { Layout.alignment: Qt.AlignHCenter font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3outline horizontalAlignment: Text.AlignHCenter text: emptyPlaceholderText } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TodoItemActionButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick RippleButton { id: button property string buttonText: "" property string tooltipText: "" implicitHeight: 30 implicitWidth: implicitHeight Behavior on implicitWidth { SmoothedAnimation { velocity: Appearance.animation.elementMove.velocity } } buttonRadius: Appearance.rounding.small contentItem: StyledText { text: buttonText horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnLayer1 } StyledToolTip { text: tooltipText extraVisibleCondition: tooltipText.length > 0 } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TodoWidget.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls import QtQuick.Layouts Item { id: root property var tabButtonList: [{"icon": "checklist", "name": Translation.tr("Unfinished")}, {"name": Translation.tr("Done"), "icon": "check_circle"}] property bool showAddDialog: false property int dialogMargins: 20 property int fabSize: 48 property int fabMargins: 14 Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageDown) { tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { tabBar.decrementCurrentIndex(); } event.accepted = true; } // Open add dialog on "N" (any modifiers) else if (event.key === Qt.Key_N) { root.showAddDialog = true event.accepted = true; } // Close dialog on Esc if open else if (event.key === Qt.Key_Escape && root.showAddDialog) { root.showAddDialog = false event.accepted = true; } } ColumnLayout { anchors.fill: parent spacing: 0 SecondaryTabBar { id: tabBar currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { buttonText: modelData.name buttonIcon: modelData.icon } } } SwipeView { id: swipeView Layout.topMargin: 10 Layout.fillWidth: true Layout.fillHeight: true spacing: 10 clip: true currentIndex: tabBar.currentIndex // To Do tab TaskList { listBottomPadding: root.fabSize + root.fabMargins * 2 emptyPlaceholderIcon: "check_circle" emptyPlaceholderText: Translation.tr("Nothing here!") taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return !item.done; }) } TaskList { listBottomPadding: root.fabSize + root.fabMargins * 2 emptyPlaceholderIcon: "checklist" emptyPlaceholderText: Translation.tr("Finished tasks will go here") taskList: Todo.list .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); }) .filter(function(item) { return item.done; }) } } } // + FAB StyledRectangularShadow { target: fabButton radius: fabButton.buttonRadius blur: 0.6 * Appearance.sizes.elevationMargin } FloatingActionButton { id: fabButton anchors.right: parent.right anchors.bottom: parent.bottom anchors.rightMargin: root.fabMargins anchors.bottomMargin: root.fabMargins onClicked: root.showAddDialog = true iconText: "add" } Item { anchors.fill: parent z: 9999 visible: opacity > 0 opacity: root.showAddDialog ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration easing.type: Appearance.animation.elementMoveFast.type easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } } onVisibleChanged: { if (!visible) { todoInput.text = "" fabButton.focus = true } } Rectangle { // Scrim anchors.fill: parent radius: Appearance.rounding.small color: Appearance.colors.colScrim MouseArea { hoverEnabled: true anchors.fill: parent preventStealing: true propagateComposedEvents: false } } Rectangle { // The dialog id: dialog anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.margins: root.dialogMargins implicitHeight: dialogColumnLayout.implicitHeight color: Appearance.m3colors.m3surfaceContainerHigh radius: Appearance.rounding.normal function addTask() { if (todoInput.text.length > 0) { Todo.addTask(todoInput.text) todoInput.text = "" root.showAddDialog = false tabBar.setCurrentIndex(0) // Show unfinished tasks } } ColumnLayout { id: dialogColumnLayout anchors.fill: parent spacing: 16 StyledText { Layout.topMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 Layout.alignment: Qt.AlignLeft color: Appearance.m3colors.m3onSurface font.pixelSize: Appearance.font.pixelSize.larger text: Translation.tr("Add task") } TextField { id: todoInput Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 padding: 10 color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant renderType: Text.NativeRendering selectedTextColor: Appearance.m3colors.m3onSecondaryContainer selectionColor: Appearance.colors.colSecondaryContainer placeholderText: Translation.tr("Task description") placeholderTextColor: Appearance.m3colors.m3outline focus: root.showAddDialog onAccepted: dialog.addTask() background: Rectangle { anchors.fill: parent radius: Appearance.rounding.verysmall border.width: 2 border.color: todoInput.activeFocus ? Appearance.colors.colPrimary : Appearance.m3colors.m3outline color: "transparent" } cursorDelegate: Rectangle { width: 1 color: todoInput.activeFocus ? Appearance.colors.colPrimary : "transparent" radius: 1 } } RowLayout { Layout.bottomMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 Layout.alignment: Qt.AlignRight spacing: 5 DialogButton { buttonText: Translation.tr("Cancel") onClicked: root.showAddDialog = false } DialogButton { buttonText: Translation.tr("Add") enabled: todoInput.text.length > 0 onClicked: dialog.addTask() } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts import Quickshell.Services.Pipewire RippleButton { id: button required property bool input buttonRadius: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover colRipple: Appearance.colors.colLayer2Active implicitHeight: contentItem.implicitHeight + 6 * 2 implicitWidth: contentItem.implicitWidth + 6 * 2 contentItem: RowLayout { anchors.fill: parent anchors.margins: 5 spacing: 5 MaterialSymbol { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: false Layout.leftMargin: 5 color: Appearance.colors.colOnLayer2 iconSize: Appearance.font.pixelSize.hugeass text: input ? "mic_external_on" : "media_output" } ColumnLayout { Layout.fillWidth: true Layout.rightMargin: 5 spacing: 0 StyledText { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.normal text: input ? Translation.tr("Input") : Translation.tr("Output") color: Appearance.colors.colOnLayer2 } StyledText { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.smaller text: (input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description) ?? Translation.tr("Unknown") color: Appearance.m3colors.m3outline animateChange: true } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialog.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire WindowDialog { id: root property bool isSink: true backgroundHeight: 600 WindowDialogTitle { text: root.isSink ? Translation.tr("Audio output") : Translation.tr("Audio input") } WindowDialogSeparator { Layout.topMargin: -22 Layout.leftMargin: 0 Layout.rightMargin: 0 } VolumeDialogContent { isSink: root.isSink } WindowDialogButtonRow { DialogButton { buttonText: Translation.tr("Details") onClicked: { Quickshell.execDetached(["bash", "-c", `${Config.options.apps.volumeMixer}`]); GlobalStates.sidebarRightOpen = false; } } Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Done") onClicked: root.dismiss() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire ColumnLayout { id: root required property bool isSink readonly property list appPwNodes: isSink ? Audio.outputAppNodes : Audio.inputAppNodes readonly property list devices: isSink ? Audio.outputDevices : Audio.inputDevices readonly property bool hasApps: appPwNodes.length > 0 spacing: 16 DialogSectionListView { Layout.fillHeight: true topMargin: 14 model: ScriptModel { values: root.appPwNodes } delegate: VolumeMixerEntry { anchors { left: parent?.left right: parent?.right } required property var modelData node: modelData } PagePlaceholder { icon: "widgets" title: Translation.tr("No applications") shown: !root.hasApps shape: MaterialShape.Shape.Cookie7Sided } } StyledComboBox { id: deviceSelector Layout.fillHeight: false Layout.fillWidth: true Layout.bottomMargin: 6 model: root.devices.map(node => Audio.friendlyDeviceName(node)) currentIndex: root.devices.findIndex(item => { if (root.isSink) { return item.id === Pipewire.defaultAudioSink?.id } else { return item.id === Pipewire.defaultAudioSource?.id } }) onActivated: (index) => { print(index) const item = root.devices[index] if (root.isSink) { Audio.setDefaultSink(item) } else { Audio.setDefaultSource(item) } } } component DialogSectionListView: StyledListView { Layout.fillWidth: true Layout.topMargin: -22 Layout.bottomMargin: -16 Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large topMargin: 12 bottomMargin: 12 leftMargin: 20 rightMargin: 20 clip: true spacing: 4 animateAppearance: false } Component { id: listElementComp ListElement {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire import Qt5Compat.GraphicalEffects Item { id: root required property PwNode node PwObjectTracker { objects: [root.node] } implicitHeight: rowLayout.implicitHeight RowLayout { id: rowLayout anchors.fill: parent spacing: 6 MouseArea { property real size: 36 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.preferredWidth: size Layout.preferredHeight: size cursorShape: Qt.PointingHandCursor onClicked: root.node.audio.muted = !root.node.audio.muted hoverEnabled: true property bool hovered: containsMouse StyledToolTip { text: root.node?.audio.muted ? Translation.tr("Click to unmute") : Translation.tr("Click to mute") } StyledImage { id: iconImg anchors.fill: parent visible: false source: { let icon; icon = AppSearch.guessIcon(root.node?.properties["application.icon-name"] ?? ""); if (AppSearch.iconExists(icon)) return Quickshell.iconPath(icon, "image-missing"); icon = AppSearch.guessIcon(root.node?.properties["node.name"] ?? ""); return Quickshell.iconPath(icon, "image-missing"); } } Desaturate { anchors.fill: iconImg source: iconImg desaturation: root.node?.audio.muted ? 1.0 : 0.0 visible: iconImg.source !== "" opacity: root.node?.audio.muted ? 0.4 : 1.0 Behavior on opacity { NumberAnimation { duration: 150 } } Behavior on desaturation { NumberAnimation { duration: 150 } } } MaterialSymbol { anchors.centerIn: parent visible: root.node?.audio.muted ?? false text: root.node?.isSink ? "volume_off" : "mic_off" iconSize: 22 color: Appearance.colors.colOnLayer1 } } ColumnLayout { Layout.fillWidth: true spacing: -4 StyledText { Layout.fillWidth: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext elide: Text.ElideRight text: { // application.name -> description -> name const app = Audio.appNodeDisplayName(root.node); const media = root.node.properties["media.name"]; return media != undefined ? `${app} • ${media}` : app; } } StyledSlider { id: slider value: root.node?.audio.volume ?? 0 onMoved: root.node.audio.volume = value configuration: StyledSlider.Configuration.S } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml ================================================ import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell WindowDialog { id: root backgroundHeight: 600 WindowDialogTitle { text: Translation.tr("Connect to Wi-Fi") } WindowDialogSeparator { visible: !Network.wifiScanning } StyledIndeterminateProgressBar { visible: Network.wifiScanning Layout.fillWidth: true Layout.topMargin: -8 Layout.bottomMargin: -8 Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large } ListView { Layout.fillHeight: true Layout.fillWidth: true Layout.topMargin: -15 Layout.bottomMargin: -16 Layout.leftMargin: -Appearance.rounding.large Layout.rightMargin: -Appearance.rounding.large clip: true spacing: 0 model: ScriptModel { values: Network.friendlyWifiNetworks } delegate: WifiNetworkItem { required property WifiAccessPoint modelData wifiNetwork: modelData width: ListView.view.width } } WindowDialogSeparator {} WindowDialogButtonRow { DialogButton { buttonText: Translation.tr("Details") onClicked: { Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]); GlobalStates.sidebarRightOpen = false; } } Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Done") onClicked: root.dismiss() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiNetworkItem.qml ================================================ import qs import qs.modules.common import qs.modules.common.widgets import qs.services import qs.services.network import QtQuick import QtQuick.Layouts DialogListItem { id: root required property WifiAccessPoint wifiNetwork enabled: !(Network.wifiConnectTarget === root.wifiNetwork && !wifiNetwork?.active) active: (wifiNetwork?.askingPassword || wifiNetwork?.active) ?? false onClicked: { Network.connectToWifiNetwork(wifiNetwork); } contentItem: ColumnLayout { anchors { fill: parent topMargin: root.verticalPadding bottomMargin: root.verticalPadding leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding } spacing: 0 RowLayout { // Name spacing: 10 MaterialSymbol { iconSize: Appearance.font.pixelSize.larger property int strength: root.wifiNetwork?.strength ?? 0 text: strength > 80 ? "signal_wifi_4_bar" : strength > 60 ? "network_wifi_3_bar" : strength > 40 ? "network_wifi_2_bar" : strength > 20 ? "network_wifi_1_bar" : "signal_wifi_0_bar" color: Appearance.colors.colOnSurfaceVariant } StyledText { Layout.fillWidth: true color: Appearance.colors.colOnSurfaceVariant elide: Text.ElideRight text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown") textFormat: Text.PlainText } MaterialSymbol { visible: (root.wifiNetwork?.isSecure || root.wifiNetwork?.active) ?? false text: root.wifiNetwork?.active ? "check" : Network.wifiConnectTarget === root.wifiNetwork ? "settings_ethernet" : "lock" iconSize: Appearance.font.pixelSize.larger color: Appearance.colors.colOnSurfaceVariant } } ColumnLayout { // Password id: passwordPrompt Layout.topMargin: 8 visible: root.wifiNetwork?.askingPassword ?? false MaterialTextField { id: passwordField Layout.fillWidth: true placeholderText: Translation.tr("Password") // Password echoMode: TextInput.Password inputMethodHints: Qt.ImhSensitiveData onAccepted: { Network.changePassword(root.wifiNetwork, passwordField.text); } } RowLayout { Layout.fillWidth: true Item { Layout.fillWidth: true } DialogButton { buttonText: Translation.tr("Cancel") onClicked: { root.wifiNetwork.askingPassword = false; } } DialogButton { buttonText: Translation.tr("Connect") onClicked: { Network.changePassword(root.wifiNetwork, passwordField.text); } } } } ColumnLayout { // Public wifi login page id: publicWifiPortal Layout.topMargin: 8 visible: (root.wifiNetwork?.active && (root.wifiNetwork?.security ?? "").trim().length === 0) ?? false RowLayout { DialogButton { Layout.fillWidth: true buttonText: Translation.tr("Open network portal") colBackground: Appearance.colors.colLayer4 colBackgroundHover: Appearance.colors.colLayer4Hover colRipple: Appearance.colors.colLayer4Active onClicked: { Network.openPublicWifiPortal() GlobalStates.sidebarRightOpen = false } } } } Item { Layout.fillHeight: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/BatteryIndicator.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts import qs.modules.ii.bar as Bar MouseArea { id: root property bool borderless: Config.options.bar.borderless readonly property var chargeState: Battery.chargeState readonly property bool isCharging: Battery.isCharging readonly property bool isPluggedIn: Battery.isPluggedIn readonly property real percentage: Battery.percentage readonly property bool isLow: percentage <= Config.options.battery.low / 100 implicitHeight: batteryProgress.implicitHeight hoverEnabled: !Config.options.bar.tooltips.clickToShow ClippedProgressBar { id: batteryProgress anchors.centerIn: parent vertical: true valueBarWidth: 20 valueBarHeight: 36 value: percentage // value: 1 highlightColor: (isLow && !isCharging) ? Appearance.m3colors.m3error : Appearance.colors.colOnSecondaryContainer font { pixelSize: 13 weight: Font.DemiBold } textMask: Item { anchors.centerIn: parent width: batteryProgress.valueBarWidth height: batteryProgress.valueBarHeight Column { anchors.centerIn: parent spacing: -4 MaterialSymbol { id: boltIcon anchors.horizontalCenter: parent.horizontalCenter fill: 1 text: { if (batteryProgress.value == 1) { return "check"; } else if (root.isCharging) { return "bolt"; } else { return Icons.getBatteryIcon(Battery.percentage * 100); } } iconSize: Appearance.font.pixelSize.normal animateChange: true } StyledText { visible: text.length <= 2 anchors.horizontalCenter: parent.horizontalCenter font: batteryProgress.font text: batteryProgress.text } } } } Bar.BatteryPopup { id: batteryPopup hoverTarget: root } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/Resource.qml ================================================ import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import QtQuick Item { id: root required property string iconName required property double percentage property int warningThreshold: 100 implicitHeight: resourceProgress.implicitHeight implicitWidth: Appearance.sizes.verticalBarWidth property bool warning: percentage * 100 >= warningThreshold ClippedFilledCircularProgress { id: resourceProgress anchors.centerIn: parent value: percentage enableAnimation: false colPrimary: root.warning ? Appearance.colors.colError : Appearance.colors.colOnSecondaryContainer accountForLightBleeding: !root.warning MaterialSymbol { font.weight: Font.Medium fill: 1 text: root.iconName iconSize: 13 color: Appearance.colors.colOnSecondaryContainer } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton enabled: root.visible } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/Resources.qml ================================================ import qs.services import qs.modules.common import QtQuick import QtQuick.Layouts import qs.modules.ii.bar as Bar MouseArea { id: root property bool alwaysShowAllResources: false implicitHeight: columnLayout.implicitHeight implicitWidth: columnLayout.implicitWidth hoverEnabled: !Config.options.bar.tooltips.clickToShow ColumnLayout { id: columnLayout spacing: 10 anchors.fill: parent Resource { Layout.alignment: Qt.AlignHCenter iconName: "memory" percentage: ResourceUsage.memoryUsedPercentage warningThreshold: Config.options.bar.resources.memoryWarningThreshold } Resource { Layout.alignment: Qt.AlignHCenter iconName: "swap_horiz" percentage: ResourceUsage.swapUsedPercentage warningThreshold: Config.options.bar.resources.swapWarningThreshold } Resource { Layout.alignment: Qt.AlignHCenter iconName: "planner_review" percentage: ResourceUsage.cpuUsage warningThreshold: Config.options.bar.resources.cpuWarningThreshold } } Bar.ResourcesPopup { hoverTarget: root } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Services.UPower import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions Scope { id: bar property bool showBarBackground: Config.options.bar.showBackground Variants { // For each monitor model: { const screens = Quickshell.screens; const list = Config.options.bar.screenList; if (!list || list.length === 0) return screens; return screens.filter(screen => list.includes(screen.name)); } LazyLoader { id: barLoader active: GlobalStates.barOpen && !GlobalStates.screenLocked required property ShellScreen modelData component: PanelWindow { // Bar window id: barRoot screen: barLoader.modelData property var brightnessMonitor: Brightness.getMonitorForScreen(barLoader.modelData) Timer { id: showBarTimer interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) repeat: false onTriggered: { barRoot.superShow = true } } Connections { target: GlobalStates function onSuperDownChanged() { if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; if (GlobalStates.superDown) showBarTimer.restart(); else { showBarTimer.stop(); barRoot.superShow = false; } } } property bool superShow: false property bool mustShow: hoverRegion.containsMouse || superShow exclusionMode: ExclusionMode.Ignore exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 : Appearance.sizes.baseVerticalBarWidth + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) WlrLayershell.namespace: "quickshell:verticalBar" // WlrLayershell.layer: WlrLayer.Overlay // TODO enable this when bar can hide when fullscreen implicitWidth: Appearance.sizes.verticalBarWidth + Appearance.rounding.screenRounding mask: Region { item: hoverMaskRegion } color: "transparent" // Positioning anchors { left: !Config.options.bar.bottom right: Config.options.bar.bottom top: true bottom: true } // Include in focus grab Component.onCompleted: { GlobalFocusGrab.addPersistent(barRoot); } Component.onDestruction: { GlobalFocusGrab.removePersistent(barRoot); } MouseArea { id: hoverRegion hoverEnabled: true anchors.fill: parent Item { id: hoverMaskRegion anchors { fill: barContent leftMargin: -Config.options.bar.autoHide.hoverRegionWidth rightMargin: -Config.options.bar.autoHide.hoverRegionWidth } } VerticalBarContent { id: barContent implicitWidth: Appearance.sizes.verticalBarWidth anchors { top: parent.top bottom: parent.bottom left: parent.left right: undefined leftMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.verticalBarWidth : 0 rightMargin: 0 } Behavior on anchors.leftMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on anchors.rightMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } states: State { name: "right" when: Config.options.bar.bottom AnchorChanges { target: barContent anchors { top: parent.top bottom: parent.bottom left: undefined right: parent.right } } PropertyChanges { target: barContent anchors.topMargin: 0 anchors.rightMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0 } } } // Round decorators Loader { id: roundDecorators anchors { top: parent.top bottom: parent.bottom left: barContent.right right: undefined } width: Appearance.rounding.screenRounding active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug states: State { name: "right" when: Config.options.bar.bottom AnchorChanges { target: roundDecorators anchors { top: parent.top bottom: parent.bottom left: undefined right: barContent.left } } } sourceComponent: Item { implicitHeight: Appearance.rounding.screenRounding RoundCorner { id: topCorner anchors { left: parent.left right: parent.right top: parent.top } implicitSize: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.TopLeft states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { topCorner.corner: RoundCorner.CornerEnum.TopRight } } } RoundCorner { id: bottomCorner anchors { bottom: parent.bottom left: !Config.options.bar.bottom ? parent.left : undefined right: Config.options.bar.bottom ? parent.right : undefined } implicitSize: Appearance.rounding.screenRounding color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" corner: RoundCorner.CornerEnum.BottomLeft states: State { name: "bottom" when: Config.options.bar.bottom PropertyChanges { bottomCorner.corner: RoundCorner.CornerEnum.BottomRight } } } } } } } } } IpcHandler { target: "bar" function toggle(): void { GlobalStates.barOpen = !GlobalStates.barOpen } function close(): void { GlobalStates.barOpen = false } function open(): void { GlobalStates.barOpen = true } } GlobalShortcut { name: "barToggle" description: "Toggles bar on press" onPressed: { GlobalStates.barOpen = !GlobalStates.barOpen; } } GlobalShortcut { name: "barOpen" description: "Opens bar on press" onPressed: { GlobalStates.barOpen = true; } } GlobalShortcut { name: "barClose" description: "Closes bar on press" onPressed: { GlobalStates.barOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBarContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import Quickshell.Services.UPower import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.ii.bar as Bar Item { // Bar content region id: root property var screen: root.QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) component HorizontalBarSeparator: Rectangle { Layout.leftMargin: Appearance.sizes.baseBarHeight / 3 Layout.rightMargin: Appearance.sizes.baseBarHeight / 3 Layout.fillWidth: true implicitHeight: 1 color: Appearance.colors.colOutlineVariant } // Background shadow Loader { active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 anchors.fill: barBackground sourceComponent: StyledRectangularShadow { anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor target: barBackground } } // Background Rectangle { id: barBackground anchors { fill: parent margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed } color: Config.options.bar.showBackground ? Appearance.colors.colLayer0 : "transparent" radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0 border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0 border.color: Appearance.colors.colLayer0Border } FocusedScrollMouseArea { // Top section | scroll to change brightness id: barTopSectionMouseArea anchors.top: parent.top implicitHeight: topSectionColumnLayout.implicitHeight implicitWidth: Appearance.sizes.baseVerticalBarWidth height: (root.height - middleSection.height) / 2 width: Appearance.sizes.verticalBarWidth onScrollDown: Brightness.decreaseBrightness() onScrollUp: Brightness.increaseBrightness() onMovedAway: GlobalStates.osdBrightnessOpen = false onPressed: event => { if (event.button === Qt.LeftButton) GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } ColumnLayout { // Content id: topSectionColumnLayout anchors.fill: parent spacing: 10 Bar.LeftSidebarButton { // Left sidebar button Layout.alignment: Qt.AlignHCenter Layout.topMargin: (Appearance.sizes.baseVerticalBarWidth - implicitWidth) / 2 + Appearance.sizes.hyprlandGapsOut colBackground: barTopSectionMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) } Item { Layout.fillHeight: true } } } Column { // Middle section id: middleSection anchors.centerIn: parent spacing: 4 Bar.BarGroup { vertical: true padding: 8 Resources { Layout.fillWidth: true Layout.fillHeight: false } HorizontalBarSeparator {} VerticalMedia { Layout.fillWidth: true Layout.fillHeight: false } } HorizontalBarSeparator { visible: Config.options?.bar.borderless } Bar.BarGroup { id: middleCenterGroup vertical: true padding: 6 Bar.Workspaces { id: workspacesWidget vertical: true MouseArea { // Right-click to toggle overview anchors.fill: parent acceptedButtons: Qt.RightButton onPressed: event => { if (event.button === Qt.RightButton) { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } } } } HorizontalBarSeparator { visible: Config.options?.bar.borderless } Bar.BarGroup { vertical: true padding: 8 VerticalClockWidget { Layout.fillWidth: true Layout.fillHeight: false } HorizontalBarSeparator { visible: Battery.available } BatteryIndicator { visible: Battery.available Layout.fillWidth: true Layout.fillHeight: false } } } FocusedScrollMouseArea { // Bottom section | scroll to change volume id: barBottomSectionMouseArea anchors { left: parent.left right: parent.right bottom: parent.bottom } implicitWidth: Appearance.sizes.baseVerticalBarWidth implicitHeight: bottomSectionColumnLayout.implicitHeight onScrollDown: Audio.decrementVolume(); onScrollUp: Audio.incrementVolume(); onMovedAway: GlobalStates.osdVolumeOpen = false; onPressed: event => { if (event.button === Qt.LeftButton) { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } } ColumnLayout { id: bottomSectionColumnLayout anchors.fill: parent spacing: 4 Item { Layout.fillWidth: true Layout.fillHeight: true } Bar.SysTray { vertical: true Layout.fillWidth: true Layout.fillHeight: false invertSide: Config?.options.bar.bottom } RippleButton { // Right sidebar button id: rightSidebarButton Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter Layout.bottomMargin: Appearance.rounding.screenRounding Layout.fillHeight: false implicitHeight: indicatorsColumnLayout.implicitHeight + 4 * 2 implicitWidth: indicatorsColumnLayout.implicitWidth + 6 * 2 buttonRadius: Appearance.rounding.full colBackground: barBottomSectionMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive toggled: GlobalStates.sidebarRightOpen property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0 Behavior on colText { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } onPressed: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } ColumnLayout { id: indicatorsColumnLayout anchors.centerIn: parent property real realSpacing: 6 spacing: 0 Revealer { vertical: true reveal: Audio.sink?.audio?.muted ?? false Layout.fillWidth: true Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0 Behavior on Layout.bottomMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } MaterialSymbol { text: "volume_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } Revealer { vertical: true reveal: Audio.source?.audio?.muted ?? false Layout.fillWidth: true Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0 Behavior on Layout.topMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } MaterialSymbol { text: "mic_off" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } Bar.HyprlandXkbIndicator { vertical: true Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: indicatorsColumnLayout.realSpacing color: rightSidebarButton.colText } Revealer { vertical: true reveal: Notifications.silent || Notifications.unread > 0 Layout.fillWidth: true Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0 implicitHeight: reveal ? notificationUnreadCount.implicitHeight : 0 implicitWidth: reveal ? notificationUnreadCount.implicitWidth : 0 Behavior on Layout.bottomMargin { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Bar.NotificationUnreadCount { id: notificationUnreadCount } } MaterialSymbol { text: Network.materialSymbol iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } MaterialSymbol { Layout.topMargin: indicatorsColumnLayout.realSpacing visible: BluetoothStatus.available text: BluetoothStatus.connected ? "bluetooth_connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled" iconSize: Appearance.font.pixelSize.larger color: rightSidebarButton.colText } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalClockWidget.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Layouts import qs.modules.ii.bar as Bar Item { id: root property bool borderless: Config.options.bar.borderless implicitHeight: column.implicitHeight implicitWidth: Appearance.sizes.verticalBarWidth readonly property string dateTimeString: DateTime.time readonly property bool hasAmPm: dateTimeString.toLowerCase().includes("am") || dateTimeString.toLowerCase().includes("pm") Column { id: column anchors.centerIn: parent spacing: root.hasAmPm ? 6 : 0 Column { anchors.horizontalCenter: parent.horizontalCenter spacing: -4 Repeater { model: root.dateTimeString.split(/[: ]/) delegate: StyledText { required property string modelData anchors.horizontalCenter: parent.horizontalCenter font.pixelSize: { if (modelData.match(/am|pm/i)) return Appearance.font.pixelSize.smaller; else // Smaller "am"/"pm" text return Appearance.font.pixelSize.large; } color: Appearance.colors.colOnLayer1 text: modelData.padStart(2, "0") } } } StyledText { anchors.horizontalCenter: parent.horizontalCenter font.pixelSize: Appearance.font.pixelSize.smallest color: Appearance.colors.colOnLayer1 text: DateTime.shortDate } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: !Config.options.bar.tooltips.clickToShow Bar.ClockWidgetPopup { hoverTarget: mouseArea } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalMedia.qml ================================================ import qs.modules.common import qs.modules.common.widgets import qs.services import qs import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Quickshell.Services.Mpris import qs.modules.ii.bar as Bar MouseArea { id: root property bool borderless: Config.options.bar.borderless readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr("No media") Layout.fillHeight: true implicitHeight: mediaCircProg.implicitHeight implicitWidth: Appearance.sizes.verticalBarWidth Timer { running: activePlayer?.playbackState == MprisPlaybackState.Playing interval: Config.options.resources.updateInterval repeat: true onTriggered: activePlayer.positionChanged() } acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton hoverEnabled: !Config.options.bar.tooltips.clickToShow onPressed: (event) => { if (event.button === Qt.MiddleButton) { activePlayer.togglePlaying(); } else if (event.button === Qt.BackButton) { activePlayer.previous(); } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) { activePlayer.next(); } else if (event.button === Qt.LeftButton) { GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen } } ClippedFilledCircularProgress { id: mediaCircProg anchors.centerIn: parent implicitSize: 20 lineWidth: Appearance.rounding.unsharpen value: activePlayer?.position / activePlayer?.length colPrimary: Appearance.colors.colOnSecondaryContainer enableAnimation: false Item { anchors.centerIn: parent width: mediaCircProg.implicitSize height: mediaCircProg.implicitSize MaterialSymbol { anchors.centerIn: parent fill: 1 text: activePlayer?.isPlaying ? "pause" : "music_note" iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer } } } Bar.StyledPopup { hoverTarget: root active: GlobalStates.mediaControlsOpen ? false : root.containsMouse Column { anchors.centerIn: parent spacing: 4 Bar.StyledPopupHeaderRow { icon: "music_note" label: Translation.tr("Media") } StyledText { color: Appearance.colors.colOnSurfaceVariant text: `${cleanedTitle}${activePlayer?.trackArtist ? '\n' + activePlayer.trackArtist : ''}` } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperDirectoryItem.qml ================================================ import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects MouseArea { id: root required property var fileModelData property bool isDirectory: fileModelData.fileIsDir property bool useThumbnail: Images.isValidImageByName(fileModelData.fileName) property alias colBackground: background.color property alias colText: wallpaperItemName.color property alias radius: background.radius property alias margins: background.anchors.margins property alias padding: wallpaperItemColumnLayout.anchors.margins margins: Appearance.sizes.wallpaperSelectorItemMargins padding: Appearance.sizes.wallpaperSelectorItemPadding signal activated() hoverEnabled: true onClicked: root.activated() Rectangle { id: background anchors.fill: parent radius: Appearance.rounding.normal Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } ColumnLayout { id: wallpaperItemColumnLayout anchors.fill: parent spacing: 4 Item { id: wallpaperItemImageContainer Layout.fillHeight: true Layout.fillWidth: true Loader { id: thumbnailShadowLoader active: thumbnailImageLoader.active && thumbnailImageLoader.item.status === Image.Ready anchors.fill: thumbnailImageLoader sourceComponent: StyledRectangularShadow { target: thumbnailImageLoader anchors.fill: undefined radius: Appearance.rounding.small } } Loader { id: thumbnailImageLoader anchors.fill: parent active: root.useThumbnail sourceComponent: ThumbnailImage { id: thumbnailImage generateThumbnail: false sourcePath: fileModelData.filePath cache: false fillMode: Image.PreserveAspectCrop clip: true Connections { target: Wallpapers function onThumbnailGenerated(directory) { if (thumbnailImage.status !== Image.Error) return; if (FileUtils.parentDirectory(thumbnailImage.sourcePath) !== FileUtils.trimFileProtocol(directory)) return; thumbnailImage.source = ""; thumbnailImage.source = thumbnailImage.thumbnailPath; } function onThumbnailGeneratedFile(filePath) { if (thumbnailImage.status !== Image.Error) return; if (Qt.resolvedUrl(thumbnailImage.sourcePath) !== Qt.resolvedUrl(filePath)) return; thumbnailImage.source = ""; thumbnailImage.source = thumbnailImage.thumbnailPath; } } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: wallpaperItemImageContainer.width height: wallpaperItemImageContainer.height radius: Appearance.rounding.small } } } } Loader { id: iconLoader active: !root.useThumbnail anchors.fill: parent sourceComponent: DirectoryIcon { fileModelData: root.fileModelData } } } StyledText { id: wallpaperItemName Layout.fillWidth: true Layout.leftMargin: 10 Layout.rightMargin: 10 horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight font.pixelSize: Appearance.font.pixelSize.smaller Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } text: fileModelData.fileName } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root Loader { id: wallpaperSelectorLoader active: GlobalStates.wallpaperSelectorOpen sourceComponent: PanelWindow { id: panelWindow readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:wallpaperSelector" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors.top: true margins { top: Config?.options.bar.vertical ? Appearance.sizes.hyprlandGapsOut : Appearance.sizes.barHeight + Appearance.sizes.hyprlandGapsOut } mask: Region { item: content } implicitHeight: Appearance.sizes.wallpaperSelectorHeight implicitWidth: Appearance.sizes.wallpaperSelectorWidth Component.onCompleted: { GlobalFocusGrab.addDismissable(panelWindow); } Component.onDestruction: { GlobalFocusGrab.removeDismissable(panelWindow); } Connections { target: GlobalFocusGrab function onDismissed() { GlobalStates.wallpaperSelectorOpen = false; } } WallpaperSelectorContent { id: content anchors { fill: parent } } } } function toggleWallpaperSelector() { if (Config.options.wallpaperSelector.useSystemFileDialog) { Wallpapers.openFallbackPicker(Appearance.m3colors.darkmode); return; } GlobalStates.wallpaperSelectorOpen = !GlobalStates.wallpaperSelectorOpen } IpcHandler { target: "wallpaperSelector" function toggle(): void { root.toggleWallpaperSelector(); } function random(): void { Wallpapers.randomFromCurrentFolder(); } } GlobalShortcut { name: "wallpaperSelectorToggle" description: "Toggle wallpaper selector" onPressed: { root.toggleWallpaperSelector(); } } GlobalShortcut { name: "wallpaperSelectorRandom" description: "Select random wallpaper in current folder" onPressed: { Wallpapers.randomFromCurrentFolder(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io MouseArea { id: root property int columns: 4 property real previewCellAspectRatio: 4 / 3 property bool useDarkMode: Appearance.m3colors.darkmode function updateThumbnails() { const totalImageMargin = (Appearance.sizes.wallpaperSelectorItemMargins + Appearance.sizes.wallpaperSelectorItemPadding) * 2; const thumbnailSizeName = Images.thumbnailSizeNameForDimensions(grid.cellWidth - totalImageMargin, grid.cellHeight - totalImageMargin); Wallpapers.generateThumbnail(thumbnailSizeName); } Connections { target: Wallpapers function onDirectoryChanged() { root.updateThumbnails(); } } function handleFilePasting(event) { const currentClipboardEntry = Cliphist.entries[0]; if (/^\d+\tfile:\/\/\S+/.test(currentClipboardEntry)) { const url = StringUtils.cleanCliphistEntry(currentClipboardEntry); Wallpapers.setDirectory(FileUtils.trimFileProtocol(decodeURIComponent(url))); event.accepted = true; } else { event.accepted = false; // No image, let text pasting proceed } } function selectWallpaperPath(filePath) { if (filePath && filePath.length > 0) { Wallpapers.select(filePath, root.useDarkMode); filterField.text = ""; } } acceptedButtons: Qt.BackButton | Qt.ForwardButton onPressed: event => { if (event.button === Qt.BackButton) { Wallpapers.navigateBack(); } else if (event.button === Qt.ForwardButton) { Wallpapers.navigateForward(); } } Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { GlobalStates.wallpaperSelectorOpen = false; event.accepted = true; } else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle "paste to go to" in pickers root.handleFilePasting(event); } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Up) { Wallpapers.navigateUp(); event.accepted = true; } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Left) { Wallpapers.navigateBack(); event.accepted = true; } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Right) { Wallpapers.navigateForward(); event.accepted = true; } else if (event.key === Qt.Key_Left) { grid.moveSelection(-1); event.accepted = true; } else if (event.key === Qt.Key_Right) { grid.moveSelection(1); event.accepted = true; } else if (event.key === Qt.Key_Up) { grid.moveSelection(-grid.columns); event.accepted = true; } else if (event.key === Qt.Key_Down) { grid.moveSelection(grid.columns); event.accepted = true; } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { grid.activateCurrent(); event.accepted = true; } else if (event.key === Qt.Key_Backspace) { if (filterField.text.length > 0) { filterField.text = filterField.text.substring(0, filterField.text.length - 1); } filterField.forceActiveFocus(); event.accepted = true; } else if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_L) { addressBar.focusBreadcrumb(); event.accepted = true; } else if (event.key === Qt.Key_Slash) { filterField.forceActiveFocus(); event.accepted = true; } else { if (event.text.length > 0) { filterField.text += event.text; filterField.cursorPosition = filterField.text.length; filterField.forceActiveFocus(); } event.accepted = true; } } implicitHeight: mainLayout.implicitHeight implicitWidth: mainLayout.implicitWidth StyledRectangularShadow { target: wallpaperGridBackground } Rectangle { id: wallpaperGridBackground anchors { fill: parent margins: Appearance.sizes.elevationMargin } focus: true border.width: 1 border.color: Appearance.colors.colLayer0Border color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 property int calculatedRows: Math.ceil(grid.count / grid.columns) implicitWidth: gridColumnLayout.implicitWidth implicitHeight: gridColumnLayout.implicitHeight RowLayout { id: mainLayout anchors.fill: parent spacing: -4 Rectangle { Layout.fillHeight: true Layout.margins: 4 implicitWidth: quickDirColumnLayout.implicitWidth implicitHeight: quickDirColumnLayout.implicitHeight color: Appearance.colors.colLayer1 radius: wallpaperGridBackground.radius - Layout.margins ColumnLayout { id: quickDirColumnLayout anchors.fill: parent spacing: 0 StyledText { Layout.margins: 12 font { pixelSize: Appearance.font.pixelSize.normal weight: Font.Medium } text: Translation.tr("Pick a wallpaper") } ListView { // Quick dirs Layout.fillHeight: true Layout.margins: 4 implicitWidth: 140 clip: true model: [ { icon: "home", name: "Home", path: Directories.home }, { icon: "docs", name: "Documents", path: Directories.documents }, { icon: "download", name: "Downloads", path: Directories.downloads }, { icon: "image", name: "Pictures", path: Directories.pictures }, { icon: "movie", name: "Videos", path: Directories.videos }, { icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" }, { icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` }, ...(Config.options.policies.weeb === 1 ? [ { icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` } ] : []),] delegate: RippleButton { id: quickDirButton required property var modelData anchors { left: parent.left right: parent.right } onClicked: Wallpapers.setDirectory(quickDirButton.modelData.path) enabled: modelData.icon.length > 0 toggled: Wallpapers.directory === Qt.resolvedUrl(modelData.path) colBackgroundToggled: Appearance.colors.colSecondaryContainer colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover colRippleToggled: Appearance.colors.colSecondaryContainerActive buttonRadius: height / 2 implicitHeight: 38 contentItem: RowLayout { MaterialSymbol { color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1 iconSize: Appearance.font.pixelSize.larger text: quickDirButton.modelData.icon fill: quickDirButton.toggled ? 1 : 0 } StyledText { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1 text: quickDirButton.modelData.name } } } } } } ColumnLayout { id: gridColumnLayout Layout.fillWidth: true Layout.fillHeight: true AddressBar { id: addressBar Layout.margins: 4 Layout.fillWidth: true Layout.fillHeight: false directory: Wallpapers.effectiveDirectory onNavigateToDirectory: path => { Wallpapers.setDirectory(path.length == 0 ? "/" : path); } radius: wallpaperGridBackground.radius - Layout.margins } Item { id: gridDisplayRegion Layout.fillWidth: true Layout.fillHeight: true StyledIndeterminateProgressBar { id: indeterminateProgressBar visible: Wallpapers.thumbnailGenerationRunning && value == 0 anchors { bottom: parent.top left: parent.left right: parent.right leftMargin: 4 rightMargin: 4 } } StyledProgressBar { visible: Wallpapers.thumbnailGenerationRunning && value > 0 value: Wallpapers.thumbnailGenerationProgress anchors.fill: indeterminateProgressBar } GridView { id: grid visible: Wallpapers.folderModel.count > 0 readonly property int columns: root.columns readonly property int rows: Math.max(1, Math.ceil(count / columns)) property int currentIndex: 0 anchors.fill: parent cellWidth: width / root.columns cellHeight: cellWidth / root.previewCellAspectRatio interactive: true clip: true keyNavigationWraps: true boundsBehavior: Flickable.StopAtBounds bottomMargin: extraOptions.implicitHeight ScrollBar.vertical: StyledScrollBar {} Component.onCompleted: { root.updateThumbnails(); } function moveSelection(delta) { currentIndex = Math.max(0, Math.min(grid.model.count - 1, currentIndex + delta)); positionViewAtIndex(currentIndex, GridView.Contain); } function activateCurrent() { const filePath = grid.model.get(currentIndex, "filePath"); root.selectWallpaperPath(filePath); } model: Wallpapers.folderModel onModelChanged: currentIndex = 0 delegate: WallpaperDirectoryItem { required property var modelData required property int index fileModelData: modelData width: grid.cellWidth height: grid.cellHeight colBackground: (index === grid?.currentIndex || containsMouse) ? Appearance.colors.colPrimary : (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colSecondaryContainer : ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) colText: (index === grid.currentIndex || containsMouse) ? Appearance.colors.colOnPrimary : (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer0 onEntered: { grid.currentIndex = index; } onActivated: { root.selectWallpaperPath(fileModelData.filePath); } } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: gridDisplayRegion.width height: gridDisplayRegion.height radius: wallpaperGridBackground.radius } } } Row { id: extraOptions anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: 8 } spacing: 6 Toolbar { IconToolbarButton { implicitWidth: height onClicked: { Wallpapers.openFallbackPicker(root.useDarkMode); GlobalStates.wallpaperSelectorOpen = false; } altAction: () => { Wallpapers.openFallbackPicker(root.useDarkMode); GlobalStates.wallpaperSelectorOpen = false; Config.options.wallpaperSelector.useSystemFileDialog = true; } text: "open_in_new" StyledToolTip { text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior") } } IconToolbarButton { implicitWidth: height onClicked: { Wallpapers.randomFromCurrentFolder(); } text: "ifl" StyledToolTip { text: Translation.tr("Pick random from this folder") } } IconToolbarButton { implicitWidth: height onClicked: root.useDarkMode = !root.useDarkMode text: root.useDarkMode ? "dark_mode" : "light_mode" StyledToolTip { text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)") } } ToolbarTextField { id: filterField placeholderText: focus ? Translation.tr("Search wallpapers") : Translation.tr("Hit \"/\" to search") // Style clip: true font.pixelSize: Appearance.font.pixelSize.small // Search onTextChanged: { Wallpapers.searchQuery = text; } Keys.onPressed: event => { if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle "paste to go to" in pickers root.handleFilePasting(event); return; } else if (text.length !== 0) { // No filtering, just navigate grid if (event.key === Qt.Key_Down) { grid.moveSelection(grid.columns); event.accepted = true; return; } if (event.key === Qt.Key_Up) { grid.moveSelection(-grid.columns); event.accepted = true; return; } } event.accepted = false; } } } ToolbarPairedFab { iconText: "close" onClicked: GlobalStates.wallpaperSelectorOpen = false; StyledToolTip { text: Translation.tr("Cancel wallpaper selection") } } } } } } } Connections { target: GlobalStates function onWallpaperSelectorOpenChanged() { if (GlobalStates.wallpaperSelectorOpen && monitorIsFocused) { filterField.forceActiveFocus(); } } } Connections { target: Wallpapers function onChanged() { GlobalStates.wallpaperSelectorOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/About.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "box" title: Translation.tr("Distro") RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 20 Layout.topMargin: 10 Layout.bottomMargin: 10 IconImage { implicitSize: 80 source: Quickshell.iconPath(SystemInfo.logo) } ColumnLayout { Layout.alignment: Qt.AlignVCenter // spacing: 10 StyledText { text: SystemInfo.distroName font.pixelSize: Appearance.font.pixelSize.title } StyledText { font.pixelSize: Appearance.font.pixelSize.normal text: SystemInfo.homeUrl textFormat: Text.MarkdownText onLinkActivated: (link) => { Qt.openUrlExternally(link) } PointingHandLinkHover {} } } } Flow { Layout.fillWidth: true spacing: 5 RippleButtonWithIcon { materialIcon: "auto_stories" mainText: Translation.tr("Documentation") onClicked: { Qt.openUrlExternally(SystemInfo.documentationUrl) } } RippleButtonWithIcon { materialIcon: "support" mainText: Translation.tr("Help & Support") onClicked: { Qt.openUrlExternally(SystemInfo.supportUrl) } } RippleButtonWithIcon { materialIcon: "bug_report" mainText: Translation.tr("Report a Bug") onClicked: { Qt.openUrlExternally(SystemInfo.bugReportUrl) } } RippleButtonWithIcon { materialIcon: "policy" materialIconFill: false mainText: Translation.tr("Privacy Policy") onClicked: { Qt.openUrlExternally(SystemInfo.privacyPolicyUrl) } } } } ContentSection { icon: "folder_managed" title: Translation.tr("Dotfiles") RowLayout { Layout.alignment: Qt.AlignHCenter spacing: 20 Layout.topMargin: 10 Layout.bottomMargin: 10 IconImage { implicitSize: 80 source: Quickshell.iconPath("illogical-impulse") } ColumnLayout { Layout.alignment: Qt.AlignVCenter // spacing: 10 StyledText { text: Translation.tr("illogical-impulse") font.pixelSize: Appearance.font.pixelSize.title } StyledText { text: "https://github.com/end-4/dots-hyprland" font.pixelSize: Appearance.font.pixelSize.normal textFormat: Text.MarkdownText onLinkActivated: (link) => { Qt.openUrlExternally(link) } PointingHandLinkHover {} } } } Flow { Layout.fillWidth: true spacing: 5 RippleButtonWithIcon { materialIcon: "auto_stories" mainText: Translation.tr("Documentation") onClicked: { Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/") } } RippleButtonWithIcon { materialIcon: "adjust" materialIconFill: false mainText: Translation.tr("Issues") onClicked: { Qt.openUrlExternally("https://github.com/end-4/dots-hyprland/issues") } } RippleButtonWithIcon { materialIcon: "forum" mainText: Translation.tr("Discussions") onClicked: { Qt.openUrlExternally("https://github.com/end-4/dots-hyprland/discussions") } } RippleButtonWithIcon { materialIcon: "favorite" mainText: Translation.tr("Donate") onClicked: { Qt.openUrlExternally("https://github.com/sponsors/end-4") } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml ================================================ import QtQuick import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "colors" title: Translation.tr("Color generation") ConfigSwitch { buttonIcon: "hardware" text: Translation.tr("Shell & utilities") checked: Config.options.appearance.wallpaperTheming.enableAppsAndShell onCheckedChanged: { Config.options.appearance.wallpaperTheming.enableAppsAndShell = checked; } } ConfigSwitch { buttonIcon: "tv_options_input_settings" text: Translation.tr("Qt apps") checked: Config.options.appearance.wallpaperTheming.enableQtApps onCheckedChanged: { Config.options.appearance.wallpaperTheming.enableQtApps = checked; } StyledToolTip { text: Translation.tr("Shell & utilities theming must also be enabled") } } ConfigSwitch { buttonIcon: "terminal" text: Translation.tr("Terminal") checked: Config.options.appearance.wallpaperTheming.enableTerminal onCheckedChanged: { Config.options.appearance.wallpaperTheming.enableTerminal = checked; } StyledToolTip { text: Translation.tr("Shell & utilities theming must also be enabled") } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "dark_mode" text: Translation.tr("Force dark mode in terminal") checked: Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode onCheckedChanged: { Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode= checked; } StyledToolTip { text: Translation.tr("Ignored if terminal theming is not enabled") } } } ConfigSpinBox { icon: "invert_colors" text: Translation.tr("Terminal: Harmony (%)") value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmony * 100 from: 0 to: 100 stepSize: 10 onValueChanged: { Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmony = value / 100; } } ConfigSpinBox { icon: "gradient" text: Translation.tr("Terminal: Harmonize threshold") value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold from: 0 to: 100 stepSize: 10 onValueChanged: { Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold = value; } } ConfigSpinBox { icon: "format_color_text" text: Translation.tr("Terminal: Foreground boost (%)") value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost * 100 from: 0 to: 100 stepSize: 10 onValueChanged: { Config.options.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost = value / 100; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "sync_alt" title: Translation.tr("Parallax") ConfigSwitch { buttonIcon: "unfold_more_double" text: Translation.tr("Vertical") checked: Config.options.background.parallax.vertical onCheckedChanged: { Config.options.background.parallax.vertical = checked; } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "counter_1" text: Translation.tr("Depends on workspace") checked: Config.options.background.parallax.enableWorkspace onCheckedChanged: { Config.options.background.parallax.enableWorkspace = checked; } } ConfigSwitch { buttonIcon: "side_navigation" text: Translation.tr("Depends on sidebars") checked: Config.options.background.parallax.enableSidebar onCheckedChanged: { Config.options.background.parallax.enableSidebar = checked; } } } ConfigSpinBox { icon: "loupe" text: Translation.tr("Preferred wallpaper zoom (%)") value: Config.options.background.parallax.workspaceZoom * 100 from: 10 to: 200 stepSize: 1 onValueChanged: { Config.options.background.parallax.workspaceZoom = value / 100; } } } ContentSection { id: settingsClock icon: "clock_loader_40" title: Translation.tr("Widget: Clock") function stylePresent(styleName) { if (!Config.options.background.widgets.clock.showOnlyWhenLocked && Config.options.background.widgets.clock.style === styleName) { return true; } if (Config.options.background.widgets.clock.styleLocked === styleName) { return true; } return false; } readonly property bool digitalPresent: stylePresent("digital") readonly property bool cookiePresent: stylePresent("cookie") ConfigRow { Layout.fillWidth: true ConfigSwitch { Layout.fillWidth: false buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.background.widgets.clock.enable onCheckedChanged: { Config.options.background.widgets.clock.enable = checked; } } Item { Layout.fillWidth: true } ConfigSelectionArray { Layout.fillWidth: false currentValue: Config.options.background.widgets.clock.placementStrategy onSelected: newValue => { Config.options.background.widgets.clock.placementStrategy = newValue; } options: [ { displayName: Translation.tr("Draggable"), icon: "drag_pan", value: "free" }, { displayName: Translation.tr("Least busy"), icon: "category", value: "leastBusy" }, { displayName: Translation.tr("Most busy"), icon: "shapes", value: "mostBusy" }, ] } } ConfigSwitch { buttonIcon: "lock_clock" text: Translation.tr("Show only when locked") checked: Config.options.background.widgets.clock.showOnlyWhenLocked onCheckedChanged: { Config.options.background.widgets.clock.showOnlyWhenLocked = checked; } } ConfigRow { ContentSubsection { visible: !Config.options.background.widgets.clock.showOnlyWhenLocked title: Translation.tr("Clock style") Layout.fillWidth: true ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.style onSelected: newValue => { Config.options.background.widgets.clock.style = newValue; } options: [ { displayName: Translation.tr("Digital"), icon: "timer_10", value: "digital" }, { displayName: Translation.tr("Cookie"), icon: "cookie", value: "cookie" } ] } } ContentSubsection { title: Translation.tr("Clock style (locked)") Layout.fillWidth: false ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.styleLocked onSelected: newValue => { Config.options.background.widgets.clock.styleLocked = newValue; } options: [ { displayName: Translation.tr("Digital"), icon: "timer_10", value: "digital" }, { displayName: Translation.tr("Cookie"), icon: "cookie", value: "cookie" } ] } } } ContentSubsection { visible: settingsClock.digitalPresent title: Translation.tr("Digital clock settings") tooltip: Translation.tr("Font width and roundness settings are only available for some fonts like Google Sans Flex") ConfigRow { uniform: true ConfigSwitch { buttonIcon: "vertical_distribute" text: Translation.tr("Vertical") checked: Config.options.background.widgets.clock.digital.vertical onCheckedChanged: { Config.options.background.widgets.clock.digital.vertical = checked; } } ConfigSwitch { buttonIcon: "animation" text: Translation.tr("Animate time change") checked: Config.options.background.widgets.clock.digital.animateChange onCheckedChanged: { Config.options.background.widgets.clock.digital.animateChange = checked; } } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "date_range" text: Translation.tr("Show date") checked: Config.options.background.widgets.clock.digital.showDate onCheckedChanged: { Config.options.background.widgets.clock.digital.showDate = checked; } } ConfigSwitch { buttonIcon: "activity_zone" text: Translation.tr("Use adaptive alignment") checked: Config.options.background.widgets.clock.digital.adaptiveAlignment onCheckedChanged: { Config.options.background.widgets.clock.digital.adaptiveAlignment = checked; } StyledToolTip { text: Translation.tr("Aligns the date and quote to left, center or right depending on its position on the screen.") } } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family") text: Config.options.background.widgets.clock.digital.font.family wrapMode: TextEdit.Wrap onTextChanged: { Config.options.background.widgets.clock.digital.font.family = text; } } ConfigSlider { text: Translation.tr("Font weight") value: Config.options.background.widgets.clock.digital.font.weight usePercentTooltip: false buttonIcon: "format_bold" from: 1 to: 1000 stopIndicatorValues: [350] onValueChanged: { Config.options.background.widgets.clock.digital.font.weight = value; } } ConfigSlider { text: Translation.tr("Font size") value: Config.options.background.widgets.clock.digital.font.size usePercentTooltip: false buttonIcon: "format_size" from: 50 to: 700 stopIndicatorValues: [90] onValueChanged: { Config.options.background.widgets.clock.digital.font.size = value; } } ConfigSlider { text: Translation.tr("Font width") value: Config.options.background.widgets.clock.digital.font.width usePercentTooltip: false buttonIcon: "fit_width" from: 25 to: 125 stopIndicatorValues: [100] onValueChanged: { Config.options.background.widgets.clock.digital.font.width = value; } } ConfigSlider { text: Translation.tr("Font roundness") value: Config.options.background.widgets.clock.digital.font.roundness usePercentTooltip: false buttonIcon: "line_curve" from: 0 to: 100 onValueChanged: { Config.options.background.widgets.clock.digital.font.roundness = value; } } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Cookie clock settings") ConfigSwitch { buttonIcon: "wand_stars" text: Translation.tr("Auto styling with Gemini") checked: Config.options.background.widgets.clock.cookie.aiStyling onCheckedChanged: { Config.options.background.widgets.clock.cookie.aiStyling = checked; } StyledToolTip { text: Translation.tr("Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.") } } ConfigSwitch { buttonIcon: "airwave" text: Translation.tr("Use old sine wave cookie implementation") checked: Config.options.background.widgets.clock.cookie.useSineCookie onCheckedChanged: { Config.options.background.widgets.clock.cookie.useSineCookie = checked; } StyledToolTip { text: Translation.tr("Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing") } } ConfigSpinBox { icon: "add_triangle" text: Translation.tr("Sides") value: Config.options.background.widgets.clock.cookie.sides from: 0 to: 40 stepSize: 1 onValueChanged: { Config.options.background.widgets.clock.cookie.sides = value; } } ConfigSwitch { buttonIcon: "autoplay" text: Translation.tr("Constantly rotate") checked: Config.options.background.widgets.clock.cookie.constantlyRotate onCheckedChanged: { Config.options.background.widgets.clock.cookie.constantlyRotate = checked; } StyledToolTip { text: Translation.tr("Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.") } } ConfigRow { ConfigSwitch { enabled: Config.options.background.widgets.clock.cookie.dialNumberStyle === "dots" || Config.options.background.widgets.clock.cookie.dialNumberStyle === "full" buttonIcon: "brightness_7" text: Translation.tr("Hour marks") checked: Config.options.background.widgets.clock.cookie.hourMarks onEnabledChanged: { checked = Config.options.background.widgets.clock.cookie.hourMarks; } onCheckedChanged: { Config.options.background.widgets.clock.cookie.hourMarks = checked; } StyledToolTip { text: Translation.tr("Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons") } } ConfigSwitch { enabled: Config.options.background.widgets.clock.cookie.dialNumberStyle !== "numbers" buttonIcon: "timer_10" text: Translation.tr("Digits in the middle") checked: Config.options.background.widgets.clock.cookie.timeIndicators onEnabledChanged: { checked = Config.options.background.widgets.clock.cookie.timeIndicators; } onCheckedChanged: { Config.options.background.widgets.clock.cookie.timeIndicators = checked; } StyledToolTip { text: Translation.tr("Can't be turned on when using 'Numbers' dial style for aesthetic reasons") } } } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Dial style") ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.cookie.dialNumberStyle onSelected: newValue => { Config.options.background.widgets.clock.cookie.dialNumberStyle = newValue; if (newValue !== "dots" && newValue !== "full") { Config.options.background.widgets.clock.cookie.hourMarks = false; } if (newValue === "numbers") { Config.options.background.widgets.clock.cookie.timeIndicators = false; } } options: [ { displayName: "", icon: "block", value: "none" }, { displayName: Translation.tr("Dots"), icon: "graph_6", value: "dots" }, { displayName: Translation.tr("Full"), icon: "history_toggle_off", value: "full" }, { displayName: Translation.tr("Numbers"), icon: "counter_1", value: "numbers" } ] } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Hour hand") ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.cookie.hourHandStyle onSelected: newValue => { Config.options.background.widgets.clock.cookie.hourHandStyle = newValue; } options: [ { displayName: "", icon: "block", value: "hide" }, { displayName: Translation.tr("Classic"), icon: "radio", value: "classic" }, { displayName: Translation.tr("Hollow"), icon: "circle", value: "hollow" }, { displayName: Translation.tr("Fill"), icon: "eraser_size_5", value: "fill" }, ] } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Minute hand") ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.cookie.minuteHandStyle onSelected: newValue => { Config.options.background.widgets.clock.cookie.minuteHandStyle = newValue; } options: [ { displayName: "", icon: "block", value: "hide" }, { displayName: Translation.tr("Classic"), icon: "radio", value: "classic" }, { displayName: Translation.tr("Thin"), icon: "line_end", value: "thin" }, { displayName: Translation.tr("Medium"), icon: "eraser_size_2", value: "medium" }, { displayName: Translation.tr("Bold"), icon: "eraser_size_4", value: "bold" }, ] } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Second hand") ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.cookie.secondHandStyle onSelected: newValue => { Config.options.background.widgets.clock.cookie.secondHandStyle = newValue; } options: [ { displayName: "", icon: "block", value: "hide" }, { displayName: Translation.tr("Classic"), icon: "radio", value: "classic" }, { displayName: Translation.tr("Line"), icon: "line_end", value: "line" }, { displayName: Translation.tr("Dot"), icon: "adjust", value: "dot" }, ] } } ContentSubsection { visible: settingsClock.cookiePresent title: Translation.tr("Date style") ConfigSelectionArray { currentValue: Config.options.background.widgets.clock.cookie.dateStyle onSelected: newValue => { Config.options.background.widgets.clock.cookie.dateStyle = newValue; } options: [ { displayName: "", icon: "block", value: "hide" }, { displayName: Translation.tr("Bubble"), icon: "bubble_chart", value: "bubble" }, { displayName: Translation.tr("Border"), icon: "rotate_right", value: "border" }, { displayName: Translation.tr("Rect"), icon: "rectangle", value: "rect" } ] } } ContentSubsection { title: Translation.tr("Quote") ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.background.widgets.clock.quote.enable onCheckedChanged: { Config.options.background.widgets.clock.quote.enable = checked; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Quote") text: Config.options.background.widgets.clock.quote.text wrapMode: TextEdit.Wrap onTextChanged: { Config.options.background.widgets.clock.quote.text = text; } } } } ContentSection { icon: "weather_mix" title: Translation.tr("Widget: Weather") ConfigRow { Layout.fillWidth: true ConfigSwitch { Layout.fillWidth: false buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.background.widgets.weather.enable onCheckedChanged: { Config.options.background.widgets.weather.enable = checked; } } Item { Layout.fillWidth: true } ConfigSelectionArray { Layout.fillWidth: false currentValue: Config.options.background.widgets.weather.placementStrategy onSelected: newValue => { Config.options.background.widgets.weather.placementStrategy = newValue; } options: [ { displayName: Translation.tr("Draggable"), icon: "drag_pan", value: "free" }, { displayName: Translation.tr("Least busy"), icon: "category", value: "leastBusy" }, { displayName: Translation.tr("Most busy"), icon: "shapes", value: "mostBusy" }, ] } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/BarConfig.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "notifications" title: Translation.tr("Notifications") ConfigSwitch { buttonIcon: "counter_2" text: Translation.tr("Unread indicator: show count") checked: Config.options.bar.indicators.notifications.showUnreadCount onCheckedChanged: { Config.options.bar.indicators.notifications.showUnreadCount = checked; } } } ContentSection { icon: "spoke" title: Translation.tr("Positioning") ConfigRow { ContentSubsection { title: Translation.tr("Bar position") Layout.fillWidth: true ConfigSelectionArray { currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0) onSelected: newValue => { Config.options.bar.bottom = (newValue & 1) !== 0; Config.options.bar.vertical = (newValue & 2) !== 0; } options: [ { displayName: Translation.tr("Top"), icon: "arrow_upward", value: 0 // bottom: false, vertical: false }, { displayName: Translation.tr("Left"), icon: "arrow_back", value: 2 // bottom: false, vertical: true }, { displayName: Translation.tr("Bottom"), icon: "arrow_downward", value: 1 // bottom: true, vertical: false }, { displayName: Translation.tr("Right"), icon: "arrow_forward", value: 3 // bottom: true, vertical: true } ] } } ContentSubsection { title: Translation.tr("Automatically hide") Layout.fillWidth: false ConfigSelectionArray { currentValue: Config.options.bar.autoHide.enable onSelected: newValue => { Config.options.bar.autoHide.enable = newValue; // Update local copy } options: [ { displayName: Translation.tr("No"), icon: "close", value: false }, { displayName: Translation.tr("Yes"), icon: "check", value: true } ] } } } ConfigRow { ContentSubsection { title: Translation.tr("Corner style") Layout.fillWidth: true ConfigSelectionArray { currentValue: Config.options.bar.cornerStyle onSelected: newValue => { Config.options.bar.cornerStyle = newValue; // Update local copy } options: [ { displayName: Translation.tr("Hug"), icon: "line_curve", value: 0 }, { displayName: Translation.tr("Float"), icon: "page_header", value: 1 }, { displayName: Translation.tr("Rect"), icon: "toolbar", value: 2 } ] } } ContentSubsection { title: Translation.tr("Group style") Layout.fillWidth: false ConfigSelectionArray { currentValue: Config.options.bar.borderless onSelected: newValue => { Config.options.bar.borderless = newValue; // Update local copy } options: [ { displayName: Translation.tr("Pills"), icon: "location_chip", value: false }, { displayName: Translation.tr("Line-separated"), icon: "split_scene", value: true } ] } } } } ContentSection { icon: "shelf_auto_hide" title: Translation.tr("Tray") ConfigSwitch { buttonIcon: "keep" text: Translation.tr('Make icons pinned by default') checked: Config.options.tray.invertPinnedItems onCheckedChanged: { Config.options.tray.invertPinnedItems = checked; } } ConfigSwitch { buttonIcon: "colors" text: Translation.tr('Tint icons') checked: Config.options.tray.monochromeIcons onCheckedChanged: { Config.options.tray.monochromeIcons = checked; } } } ContentSection { icon: "widgets" title: Translation.tr("Utility buttons") ConfigRow { uniform: true ConfigSwitch { buttonIcon: "content_cut" text: Translation.tr("Screen snip") checked: Config.options.bar.utilButtons.showScreenSnip onCheckedChanged: { Config.options.bar.utilButtons.showScreenSnip = checked; } } ConfigSwitch { buttonIcon: "colorize" text: Translation.tr("Color picker") checked: Config.options.bar.utilButtons.showColorPicker onCheckedChanged: { Config.options.bar.utilButtons.showColorPicker = checked; } } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "keyboard" text: Translation.tr("Keyboard toggle") checked: Config.options.bar.utilButtons.showKeyboardToggle onCheckedChanged: { Config.options.bar.utilButtons.showKeyboardToggle = checked; } } ConfigSwitch { buttonIcon: "mic" text: Translation.tr("Mic toggle") checked: Config.options.bar.utilButtons.showMicToggle onCheckedChanged: { Config.options.bar.utilButtons.showMicToggle = checked; } } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "dark_mode" text: Translation.tr("Dark/Light toggle") checked: Config.options.bar.utilButtons.showDarkModeToggle onCheckedChanged: { Config.options.bar.utilButtons.showDarkModeToggle = checked; } } ConfigSwitch { buttonIcon: "speed" text: Translation.tr("Performance Profile toggle") checked: Config.options.bar.utilButtons.showPerformanceProfileToggle onCheckedChanged: { Config.options.bar.utilButtons.showPerformanceProfileToggle = checked; } } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "videocam" text: Translation.tr("Record") checked: Config.options.bar.utilButtons.showScreenRecord onCheckedChanged: { Config.options.bar.utilButtons.showScreenRecord = checked; } } } } ContentSection { icon: "cloud" title: Translation.tr("Weather") ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.bar.weather.enable onCheckedChanged: { Config.options.bar.weather.enable = checked; } } } ContentSection { icon: "workspaces" title: Translation.tr("Workspaces") ConfigSwitch { buttonIcon: "counter_1" text: Translation.tr('Always show numbers') checked: Config.options.bar.workspaces.alwaysShowNumbers onCheckedChanged: { Config.options.bar.workspaces.alwaysShowNumbers = checked; } } ConfigSwitch { buttonIcon: "award_star" text: Translation.tr('Show app icons') checked: Config.options.bar.workspaces.showAppIcons onCheckedChanged: { Config.options.bar.workspaces.showAppIcons = checked; } } ConfigSwitch { buttonIcon: "colors" text: Translation.tr('Tint app icons') checked: Config.options.bar.workspaces.monochromeIcons onCheckedChanged: { Config.options.bar.workspaces.monochromeIcons = checked; } } ConfigSpinBox { icon: "view_column" text: Translation.tr("Workspaces shown") value: Config.options.bar.workspaces.shown from: 1 to: 30 stepSize: 1 onValueChanged: { Config.options.bar.workspaces.shown = value; } } ConfigSpinBox { icon: "touch_long" text: Translation.tr("Number show delay when pressing Super (ms)") value: Config.options.bar.workspaces.showNumberDelay from: 0 to: 1000 stepSize: 50 onValueChanged: { Config.options.bar.workspaces.showNumberDelay = value; } } ContentSubsection { title: Translation.tr("Number style") ConfigSelectionArray { currentValue: JSON.stringify(Config.options.bar.workspaces.numberMap) onSelected: newValue => { Config.options.bar.workspaces.numberMap = JSON.parse(newValue) } options: [ { displayName: Translation.tr("Normal"), icon: "timer_10", value: '[]' }, { displayName: Translation.tr("Han chars"), icon: "square_dot", value: '["一","二","三","四","五","六","七","八","九","十","十一","十二","十三","十四","十五","十六","十七","十八","十九","二十"]' }, { displayName: Translation.tr("Roman"), icon: "account_balance", value: '["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII","XIII","XIV","XV","XVI","XVII","XVIII","XIX","XX"]' } ] } } } ContentSection { icon: "tooltip" title: Translation.tr("Tooltips") ConfigSwitch { buttonIcon: "ads_click" text: Translation.tr("Click to show") checked: Config.options.bar.tooltips.clickToShow onCheckedChanged: { Config.options.bar.tooltips.clickToShow = checked; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets ContentPage { forceWidth: true Process { id: translationProc property string locale: "" command: [Directories.aiTranslationScriptPath, translationProc.locale] } ContentSection { icon: "volume_up" title: Translation.tr("Audio") ConfigSwitch { buttonIcon: "hearing" text: Translation.tr("Earbang protection") checked: Config.options.audio.protection.enable onCheckedChanged: { Config.options.audio.protection.enable = checked; } StyledToolTip { text: Translation.tr("Prevents abrupt increments and restricts volume limit") } } ConfigRow { enabled: Config.options.audio.protection.enable ConfigSpinBox { icon: "arrow_warm_up" text: Translation.tr("Max allowed increase") value: Config.options.audio.protection.maxAllowedIncrease from: 0 to: 100 stepSize: 2 onValueChanged: { Config.options.audio.protection.maxAllowedIncrease = value; } } ConfigSpinBox { icon: "vertical_align_top" text: Translation.tr("Volume limit") value: Config.options.audio.protection.maxAllowed from: 0 to: 154 // pavucontrol allows up to 153% stepSize: 2 onValueChanged: { Config.options.audio.protection.maxAllowed = value; } } } } ContentSection { icon: "battery_android_full" title: Translation.tr("Battery") ConfigRow { uniform: true ConfigSpinBox { icon: "warning" text: Translation.tr("Low warning") value: Config.options.battery.low from: 0 to: 100 stepSize: 5 onValueChanged: { Config.options.battery.low = value; } } ConfigSpinBox { icon: "dangerous" text: Translation.tr("Critical warning") value: Config.options.battery.critical from: 0 to: 100 stepSize: 5 onValueChanged: { Config.options.battery.critical = value; } } } ConfigRow { uniform: false Layout.fillWidth: false ConfigSwitch { buttonIcon: "pause" text: Translation.tr("Automatic suspend") checked: Config.options.battery.automaticSuspend onCheckedChanged: { Config.options.battery.automaticSuspend = checked; } StyledToolTip { text: Translation.tr("Automatically suspends the system when battery is low") } } ConfigSpinBox { enabled: Config.options.battery.automaticSuspend text: Translation.tr("at") value: Config.options.battery.suspend from: 0 to: 100 stepSize: 5 onValueChanged: { Config.options.battery.suspend = value; } } } ConfigRow { uniform: true ConfigSpinBox { icon: "charger" text: Translation.tr("Full warning") value: Config.options.battery.full from: 0 to: 101 stepSize: 5 onValueChanged: { Config.options.battery.full = value; } } } } ContentSection { icon: "language" title: Translation.tr("Language") ContentSubsection { title: Translation.tr("Interface Language") tooltip: Translation.tr("Select the language for the user interface.\n\"Auto\" will use your system's locale.") StyledComboBox { id: languageSelector buttonIcon: "language" textRole: "displayName" model: [ { displayName: Translation.tr("Auto (System)"), value: "auto" }, ...Translation.allAvailableLanguages.map(lang => { return { displayName: lang, value: lang }; })] currentIndex: { const index = model.findIndex(item => item.value === Config.options.language.ui); return index !== -1 ? index : 0; } onActivated: index => { Config.options.language.ui = model[index].value; } } } ContentSubsection { title: Translation.tr("Generate translation with Gemini") tooltip: Translation.tr("You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.") ConfigRow { MaterialTextArea { id: localeInput Layout.fillWidth: true placeholderText: Translation.tr("Locale code, e.g. fr_FR, de_DE, zh_CN...") text: Config.options.language.ui === "auto" ? Qt.locale().name : Config.options.language.ui } RippleButtonWithIcon { id: generateTranslationBtn Layout.fillHeight: true nerdIcon: "" enabled: !translationProc.running || (translationProc.locale !== localeInput.text.trim()) mainText: enabled ? Translation.tr("Generate\nTypically takes 2 minutes") : Translation.tr("Generating...\nDon't close this window!") onClicked: { translationProc.locale = localeInput.text.trim(); translationProc.running = false; translationProc.running = true; } } } } } ContentSection { icon: "rule" title: Translation.tr("Policies") ConfigRow { // AI policy ColumnLayout { ContentSubsectionLabel { text: Translation.tr("AI") } ConfigSelectionArray { currentValue: Config.options.policies.ai onSelected: newValue => { Config.options.policies.ai = newValue; } options: [ { displayName: Translation.tr("No"), icon: "close", value: 0 }, { displayName: Translation.tr("Yes"), icon: "check", value: 1 }, { displayName: Translation.tr("Local only"), icon: "sync_saved_locally", value: 2 } ] } } // Weeb policy ColumnLayout { ContentSubsectionLabel { text: Translation.tr("Weeb") } ConfigSelectionArray { currentValue: Config.options.policies.weeb onSelected: newValue => { Config.options.policies.weeb = newValue; } options: [ { displayName: Translation.tr("No"), icon: "close", value: 0 }, { displayName: Translation.tr("Yes"), icon: "check", value: 1 }, { displayName: Translation.tr("Closet"), icon: "ev_shadow", value: 2 } ] } } } } ContentSection { icon: "notification_sound" title: Translation.tr("Sounds") ConfigRow { uniform: true ConfigSwitch { buttonIcon: "battery_android_full" text: Translation.tr("Battery") checked: Config.options.sounds.battery onCheckedChanged: { Config.options.sounds.battery = checked; } } ConfigSwitch { buttonIcon: "av_timer" text: Translation.tr("Pomodoro") checked: Config.options.sounds.pomodoro onCheckedChanged: { Config.options.sounds.pomodoro = checked; } } } } ContentSection { icon: "nest_clock_farsight_analog" title: Translation.tr("Time") ConfigSwitch { buttonIcon: "pace" text: Translation.tr("Second precision") checked: Config.options.time.secondPrecision onCheckedChanged: { Config.options.time.secondPrecision = checked; } StyledToolTip { text: Translation.tr("Enable if you want clocks to show seconds accurately") } } ContentSubsection { title: Translation.tr("Format") tooltip: "" ConfigSelectionArray { currentValue: Config.options.time.format onSelected: newValue => { if (newValue === "hh:mm") { Quickshell.execDetached(["bash", "-c", `sed -i 's/\\TIME12\\b/TIME/' '${FileUtils.trimFileProtocol(Directories.config)}/hypr/hyprlock.conf'`]); } else { Quickshell.execDetached(["bash", "-c", `sed -i 's/\\TIME\\b/TIME12/' '${FileUtils.trimFileProtocol(Directories.config)}/hypr/hyprlock.conf'`]); } Config.options.time.format = newValue; } options: [ { displayName: Translation.tr("24h"), value: "hh:mm" }, { displayName: Translation.tr("12h am/pm"), value: "h:mm ap" }, { displayName: Translation.tr("12h AM/PM"), value: "h:mm AP" }, ] } } } ContentSection { icon: "work_alert" title: Translation.tr("Work safety") ConfigSwitch { buttonIcon: "assignment" text: Translation.tr("Hide clipboard images copied from sussy sources") checked: Config.options.workSafety.enable.clipboard onCheckedChanged: { Config.options.workSafety.enable.clipboard = checked; } } ConfigSwitch { buttonIcon: "wallpaper" text: Translation.tr("Hide sussy/anime wallpapers") checked: Config.options.workSafety.enable.wallpaper onCheckedChanged: { Config.options.workSafety.enable.wallpaper = checked; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "keyboard" title: Translation.tr("Cheat sheet") ContentSubsection { title: Translation.tr("Super key symbol") tooltip: Translation.tr("You can also manually edit cheatsheet.superKey") ConfigSelectionArray { currentValue: Config.options.cheatsheet.superKey onSelected: newValue => { Config.options.cheatsheet.superKey = newValue; } // Use a nerdfont to see the icons options: ([ "󰖳", "", "󰨡", "", "󰌽", "󰣇", "", "", "", "", "", "󱄛", "", "", "", "⌘", "󰀲", "󰟍", "" ]).map(icon => { return { displayName: icon, value: icon } }) } } ConfigSwitch { buttonIcon: "󰘵" text: Translation.tr("Use macOS-like symbols for mods keys") checked: Config.options.cheatsheet.useMacSymbol onCheckedChanged: { Config.options.cheatsheet.useMacSymbol = checked; } StyledToolTip { text: Translation.tr("e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc") } } ConfigSwitch { buttonIcon: "󱊶" text: Translation.tr("Use symbols for function keys") checked: Config.options.cheatsheet.useFnSymbol onCheckedChanged: { Config.options.cheatsheet.useFnSymbol = checked; } StyledToolTip { text: Translation.tr("e.g. 󱊫 for F1, 󱊶 for F12") } } ConfigSwitch { buttonIcon: "󰍽" text: Translation.tr("Use symbols for mouse") checked: Config.options.cheatsheet.useMouseSymbol onCheckedChanged: { Config.options.cheatsheet.useMouseSymbol = checked; } StyledToolTip { text: Translation.tr("Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"") } } ConfigSwitch { buttonIcon: "highlight_keyboard_focus" text: Translation.tr("Split buttons") checked: Config.options.cheatsheet.splitButtons onCheckedChanged: { Config.options.cheatsheet.splitButtons = checked; } StyledToolTip { text: Translation.tr("Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")") } } ConfigSpinBox { text: Translation.tr("Keybind font size") value: Config.options.cheatsheet.fontSize.key from: 8 to: 30 stepSize: 1 onValueChanged: { Config.options.cheatsheet.fontSize.key = value; } } ConfigSpinBox { text: Translation.tr("Description font size") value: Config.options.cheatsheet.fontSize.comment from: 8 to: 30 stepSize: 1 onValueChanged: { Config.options.cheatsheet.fontSize.comment = value; } } } ContentSection { icon: "call_to_action" title: Translation.tr("Dock") ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.dock.enable onCheckedChanged: { Config.options.dock.enable = checked; } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "highlight_mouse_cursor" text: Translation.tr("Hover to reveal") checked: Config.options.dock.hoverToReveal onCheckedChanged: { Config.options.dock.hoverToReveal = checked; } } ConfigSwitch { buttonIcon: "keep" text: Translation.tr("Pinned on startup") checked: Config.options.dock.pinnedOnStartup onCheckedChanged: { Config.options.dock.pinnedOnStartup = checked; } } } ConfigSwitch { buttonIcon: "colors" text: Translation.tr("Tint app icons") checked: Config.options.dock.monochromeIcons onCheckedChanged: { Config.options.dock.monochromeIcons = checked; } } } ContentSection { icon: "lock" title: Translation.tr("Lock screen") ConfigSwitch { buttonIcon: "water_drop" text: Translation.tr('Use Hyprlock (instead of Quickshell)') checked: Config.options.lock.useHyprlock onCheckedChanged: { Config.options.lock.useHyprlock = checked; } StyledToolTip { text: Translation.tr("If you want to somehow use fingerprint unlock...") } } ConfigSwitch { buttonIcon: "account_circle" text: Translation.tr('Launch on startup') checked: Config.options.lock.launchOnStartup onCheckedChanged: { Config.options.lock.launchOnStartup = checked; } } ContentSubsection { title: Translation.tr("Security") ConfigSwitch { buttonIcon: "settings_power" text: Translation.tr('Require password to power off/restart') checked: Config.options.lock.security.requirePasswordToPower onCheckedChanged: { Config.options.lock.security.requirePasswordToPower = checked; } StyledToolTip { text: Translation.tr("Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen") } } ConfigSwitch { buttonIcon: "key_vertical" text: Translation.tr('Also unlock keyring') checked: Config.options.lock.security.unlockKeyring onCheckedChanged: { Config.options.lock.security.unlockKeyring = checked; } StyledToolTip { text: Translation.tr("This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)") } } } ContentSubsection { title: Translation.tr("Style: general") ConfigSwitch { buttonIcon: "center_focus_weak" text: Translation.tr('Center clock') checked: Config.options.lock.centerClock onCheckedChanged: { Config.options.lock.centerClock = checked; } } ConfigSwitch { buttonIcon: "info" text: Translation.tr('Show "Locked" text') checked: Config.options.lock.showLockedText onCheckedChanged: { Config.options.lock.showLockedText = checked; } } ConfigSwitch { buttonIcon: "shapes" text: Translation.tr('Use varying shapes for password characters') checked: Config.options.lock.materialShapeChars onCheckedChanged: { Config.options.lock.materialShapeChars = checked; } } } ContentSubsection { title: Translation.tr("Style: Blurred") ConfigSwitch { buttonIcon: "blur_on" text: Translation.tr('Enable blur') checked: Config.options.lock.blur.enable onCheckedChanged: { Config.options.lock.blur.enable = checked; } } ConfigSpinBox { icon: "loupe" text: Translation.tr("Extra wallpaper zoom (%)") value: Config.options.lock.blur.extraZoom * 100 from: 1 to: 150 stepSize: 2 onValueChanged: { Config.options.lock.blur.extraZoom = value / 100; } } } } ContentSection { icon: "notifications" title: Translation.tr("Notifications") ConfigSpinBox { icon: "av_timer" text: Translation.tr("Timeout duration (if not defined by notification) (ms)") value: Config.options.notifications.timeout from: 1000 to: 60000 stepSize: 1000 onValueChanged: { Config.options.notifications.timeout = value; } } ConfigSwitch { buttonIcon: "monitor" text: Translation.tr("Force specific monitor") checked: Config.options.notifications.forceMonitor.enable onCheckedChanged: { Config.options.notifications.forceMonitor.enable = checked; } StyledToolTip { text: Translation.tr("If you have multiple monitors and want notifications to only show on one of them, enable this and enter the monitor name below (e.g., eDP-1)") } } ConfigRow { enabled: Config.options.notifications.forceMonitor.enable MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Monitor name to show notifications on (e.g., eDP-1)") text: Config.options.notifications.forceMonitor.name wrapMode: TextEdit.Wrap onTextChanged: { Config.options.notifications.forceMonitor.name = text; } } } } ContentSection { icon: "select_window" title: Translation.tr("Overlay: General") ConfigSwitch { buttonIcon: "high_density" text: Translation.tr("Enable opening zoom animation") checked: Config.options.overlay.openingZoomAnimation onCheckedChanged: { Config.options.overlay.openingZoomAnimation = checked; } } ConfigSwitch { buttonIcon: "texture" text: Translation.tr("Darken screen") checked: Config.options.overlay.darkenScreen onCheckedChanged: { Config.options.overlay.darkenScreen = checked; } } } ContentSection { icon: "point_scan" title: Translation.tr("Overlay: Crosshair") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Crosshair code (in Valorant's format)") text: Config.options.crosshair.code wrapMode: TextEdit.Wrap onTextChanged: { Config.options.crosshair.code = text; } } RowLayout { StyledText { Layout.leftMargin: 10 color: Appearance.colors.colSubtext font.pixelSize: Appearance.font.pixelSize.smallie text: Translation.tr("Press Super+G to open the overlay and pin the crosshair") } Item { Layout.fillWidth: true } RippleButtonWithIcon { id: editorButton buttonRadius: Appearance.rounding.full materialIcon: "open_in_new" mainText: Translation.tr("Open editor") onClicked: { Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`); } StyledToolTip { text: "www.vcrdb.net" } } } } ContentSection { icon: "point_scan" title: Translation.tr("Overlay: Floating Image") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Image source") text: Config.options.overlay.floatingImage.imageSource wrapMode: TextEdit.Wrap onTextChanged: { Config.options.overlay.floatingImage.imageSource = text; } } } ContentSection { icon: "screenshot_frame_2" title: Translation.tr("Region selector (screen snipping/Google Lens)") ContentSubsection { title: Translation.tr("Hint target regions") ConfigRow { ConfigSwitch { buttonIcon: "select_window" text: Translation.tr('Windows') checked: Config.options.regionSelector.targetRegions.windows onCheckedChanged: { Config.options.regionSelector.targetRegions.windows = checked; } } ConfigSwitch { buttonIcon: "right_panel_open" text: Translation.tr('Layers') checked: Config.options.regionSelector.targetRegions.layers onCheckedChanged: { Config.options.regionSelector.targetRegions.layers = checked; } } ConfigSwitch { buttonIcon: "nearby" text: Translation.tr('Content') checked: Config.options.regionSelector.targetRegions.content onCheckedChanged: { Config.options.regionSelector.targetRegions.content = checked; } StyledToolTip { text: Translation.tr("Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.") } } } } ContentSubsection { title: Translation.tr("Google Lens") ConfigSelectionArray { currentValue: Config.options.search.imageSearch.useCircleSelection ? "circle" : "rectangles" onSelected: newValue => { Config.options.search.imageSearch.useCircleSelection = (newValue === "circle"); } options: [ { icon: "activity_zone", value: "rectangles", displayName: Translation.tr("Rectangular selection") }, { icon: "gesture", value: "circle", displayName: Translation.tr("Circle to Search") } ] } } ContentSubsection { title: Translation.tr("Rectangular selection") ConfigSwitch { buttonIcon: "point_scan" text: Translation.tr("Show aim lines") checked: Config.options.regionSelector.rect.showAimLines onCheckedChanged: { Config.options.regionSelector.rect.showAimLines = checked; } } } ContentSubsection { title: Translation.tr("Circle selection") ConfigSpinBox { icon: "eraser_size_3" text: Translation.tr("Stroke width") value: Config.options.regionSelector.circle.strokeWidth from: 1 to: 20 stepSize: 1 onValueChanged: { Config.options.regionSelector.circle.strokeWidth = value; } } ConfigSpinBox { icon: "screenshot_frame_2" text: Translation.tr("Padding") value: Config.options.regionSelector.circle.padding from: 0 to: 100 stepSize: 5 onValueChanged: { Config.options.regionSelector.circle.padding = value; } } } } ContentSection { icon: "side_navigation" title: Translation.tr("Sidebars") ConfigSwitch { buttonIcon: "memory" text: Translation.tr('Keep right sidebar loaded') checked: Config.options.sidebar.keepRightSidebarLoaded onCheckedChanged: { Config.options.sidebar.keepRightSidebarLoaded = checked; } StyledToolTip { text: Translation.tr("When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help") } } ConfigSwitch { buttonIcon: "translate" text: Translation.tr('Enable translator') checked: Config.options.sidebar.translator.enable onCheckedChanged: { Config.options.sidebar.translator.enable = checked; } } ContentSubsection { title: Translation.tr("Quick toggles") ConfigSelectionArray { Layout.fillWidth: false currentValue: Config.options.sidebar.quickToggles.style onSelected: newValue => { Config.options.sidebar.quickToggles.style = newValue; } options: [ { displayName: Translation.tr("Classic"), icon: "password_2", value: "classic" }, { displayName: Translation.tr("Android"), icon: "action_key", value: "android" } ] } ConfigSpinBox { enabled: Config.options.sidebar.quickToggles.style === "android" icon: "splitscreen_left" text: Translation.tr("Columns") value: Config.options.sidebar.quickToggles.android.columns from: 1 to: 8 stepSize: 1 onValueChanged: { Config.options.sidebar.quickToggles.android.columns = value; } } } ContentSubsection { title: Translation.tr("Sliders") ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.sidebar.quickSliders.enable onCheckedChanged: { Config.options.sidebar.quickSliders.enable = checked; } } ConfigSwitch { buttonIcon: "brightness_6" text: Translation.tr("Brightness") enabled: Config.options.sidebar.quickSliders.enable checked: Config.options.sidebar.quickSliders.showBrightness onCheckedChanged: { Config.options.sidebar.quickSliders.showBrightness = checked; } } ConfigSwitch { buttonIcon: "volume_up" text: Translation.tr("Volume") enabled: Config.options.sidebar.quickSliders.enable checked: Config.options.sidebar.quickSliders.showVolume onCheckedChanged: { Config.options.sidebar.quickSliders.showVolume = checked; } } ConfigSwitch { buttonIcon: "mic" text: Translation.tr("Microphone") enabled: Config.options.sidebar.quickSliders.enable checked: Config.options.sidebar.quickSliders.showMic onCheckedChanged: { Config.options.sidebar.quickSliders.showMic = checked; } } } ContentSubsection { title: Translation.tr("Corner open") tooltip: Translation.tr("Allows you to open sidebars by clicking or hovering screen corners regardless of bar position") ConfigRow { uniform: true ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.sidebar.cornerOpen.enable onCheckedChanged: { Config.options.sidebar.cornerOpen.enable = checked; } } } ConfigSwitch { buttonIcon: "highlight_mouse_cursor" text: Translation.tr("Hover to trigger") checked: Config.options.sidebar.cornerOpen.clickless onCheckedChanged: { Config.options.sidebar.cornerOpen.clickless = checked; } StyledToolTip { text: Translation.tr("When this is off you'll have to click") } } Row { ConfigSwitch { enabled: !Config.options.sidebar.cornerOpen.clickless text: Translation.tr("Force hover open at absolute corner") checked: Config.options.sidebar.cornerOpen.clicklessCornerEnd onCheckedChanged: { Config.options.sidebar.cornerOpen.clicklessCornerEnd = checked; } StyledToolTip { text: Translation.tr("When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll") } } ConfigSpinBox { icon: "arrow_cool_down" text: Translation.tr("with vertical offset") value: Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset from: 0 to: 20 stepSize: 1 onValueChanged: { Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset = value; } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton StyledToolTip { extraVisibleCondition: mouseArea.containsMouse text: Translation.tr("Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge") } } } } ConfigRow { uniform: true ConfigSwitch { buttonIcon: "vertical_align_bottom" text: Translation.tr("Place at bottom") checked: Config.options.sidebar.cornerOpen.bottom onCheckedChanged: { Config.options.sidebar.cornerOpen.bottom = checked; } StyledToolTip { text: Translation.tr("Place the corners to trigger at the bottom") } } ConfigSwitch { buttonIcon: "unfold_more_double" text: Translation.tr("Value scroll") checked: Config.options.sidebar.cornerOpen.valueScroll onCheckedChanged: { Config.options.sidebar.cornerOpen.valueScroll = checked; } StyledToolTip { text: Translation.tr("Brightness and volume") } } } ConfigSwitch { buttonIcon: "visibility" text: Translation.tr("Visualize region") checked: Config.options.sidebar.cornerOpen.visualize onCheckedChanged: { Config.options.sidebar.cornerOpen.visualize = checked; } } ConfigRow { ConfigSpinBox { icon: "arrow_range" text: Translation.tr("Region width") value: Config.options.sidebar.cornerOpen.cornerRegionWidth from: 1 to: 300 stepSize: 1 onValueChanged: { Config.options.sidebar.cornerOpen.cornerRegionWidth = value; } } ConfigSpinBox { icon: "height" text: Translation.tr("Region height") value: Config.options.sidebar.cornerOpen.cornerRegionHeight from: 1 to: 300 stepSize: 1 onValueChanged: { Config.options.sidebar.cornerOpen.cornerRegionHeight = value; } } } } } ContentSection { icon: "voting_chip" title: Translation.tr("On-screen display") ConfigSpinBox { icon: "av_timer" text: Translation.tr("Timeout (ms)") value: Config.options.osd.timeout from: 100 to: 3000 stepSize: 100 onValueChanged: { Config.options.osd.timeout = value; } } } ContentSection { icon: "overview_key" title: Translation.tr("Overview") ConfigSwitch { buttonIcon: "check" text: Translation.tr("Enable") checked: Config.options.overview.enable onCheckedChanged: { Config.options.overview.enable = checked; } } ConfigSwitch { buttonIcon: "center_focus_strong" text: Translation.tr("Center icons") checked: Config.options.overview.centerIcons onCheckedChanged: { Config.options.overview.centerIcons = checked; } } ConfigSpinBox { icon: "loupe" text: Translation.tr("Scale (%)") value: Config.options.overview.scale * 100 from: 1 to: 100 stepSize: 1 onValueChanged: { Config.options.overview.scale = value / 100; } } ConfigRow { uniform: true ConfigSpinBox { icon: "splitscreen_bottom" text: Translation.tr("Rows") value: Config.options.overview.rows from: 1 to: 20 stepSize: 1 onValueChanged: { Config.options.overview.rows = value; } } ConfigSpinBox { icon: "splitscreen_right" text: Translation.tr("Columns") value: Config.options.overview.columns from: 1 to: 20 stepSize: 1 onValueChanged: { Config.options.overview.columns = value; } } } ConfigRow { uniform: true ConfigSelectionArray { currentValue: Config.options.overview.orderRightLeft onSelected: newValue => { Config.options.overview.orderRightLeft = newValue } options: [ { displayName: Translation.tr("Left to right"), icon: "arrow_forward", value: 0 }, { displayName: Translation.tr("Right to left"), icon: "arrow_back", value: 1 } ] } ConfigSelectionArray { currentValue: Config.options.overview.orderBottomUp onSelected: newValue => { Config.options.overview.orderBottomUp = newValue } options: [ { displayName: Translation.tr("Top-down"), icon: "arrow_downward", value: 0 }, { displayName: Translation.tr("Bottom-up"), icon: "arrow_upward", value: 1 } ] } } } ContentSection { icon: "wallpaper_slideshow" title: Translation.tr("Wallpaper selector") ConfigSwitch { buttonIcon: "ad" text: Translation.tr('Use system file picker') checked: Config.options.wallpaperSelector.useSystemFileDialog onCheckedChanged: { Config.options.wallpaperSelector.useSystemFileDialog = checked; } } } ContentSection { icon: "text_format" title: Translation.tr("Fonts") ContentSubsection { title: Translation.tr("Main font") tooltip: Translation.tr("Used for general UI text") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name (e.g., Google Sans Flex)") text: Config.options.appearance.fonts.main wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.main = text; } } } ContentSubsection { title: Translation.tr("Numbers font") tooltip: Translation.tr("Used for displaying numbers") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name") text: Config.options.appearance.fonts.numbers wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.numbers = text; } } } ContentSubsection { title: Translation.tr("Title font") tooltip: Translation.tr("Used for headings and titles") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name") text: Config.options.appearance.fonts.title wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.title = text; } } } ContentSubsection { title: Translation.tr("Monospace font") tooltip: Translation.tr("Used for code and terminal") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name (e.g., JetBrains Mono NF)") text: Config.options.appearance.fonts.monospace wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.monospace = text; } } } ContentSubsection { title: Translation.tr("Nerd font icons") tooltip: Translation.tr("Font used for Nerd Font icons") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name (e.g., JetBrains Mono NF)") text: Config.options.appearance.fonts.iconNerd wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.iconNerd = text; } } } ContentSubsection { title: Translation.tr("Reading font") tooltip: Translation.tr("Used for reading large blocks of text") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name (e.g., Readex Pro)") text: Config.options.appearance.fonts.reading wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.reading = text; } } } ContentSubsection { title: Translation.tr("Expressive font") tooltip: Translation.tr("Used for decorative/expressive text") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Font family name (e.g., Space Grotesk)") text: Config.options.appearance.fonts.expressive wrapMode: TextEdit.NoWrap onTextChanged: { Config.options.appearance.fonts.expressive = text; } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/QuickConfig.qml ================================================ import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions ContentPage { forceWidth: true Process { id: randomWallProc property string status: "" property string scriptPath: `${Directories.scriptPath}/colors/random/random_konachan_wall.sh` command: ["bash", "-c", FileUtils.trimFileProtocol(randomWallProc.scriptPath)] stdout: SplitParser { onRead: data => { randomWallProc.status = data.trim(); } } } component SmallLightDarkPreferenceButton: RippleButton { id: smallLightDarkPreferenceButton required property bool dark property color colText: toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2 padding: 5 Layout.fillWidth: true toggled: Appearance.m3colors.darkmode === dark colBackground: Appearance.colors.colLayer2 onClicked: { Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --mode ${dark ? "dark" : "light"} --noswitch`]); } contentItem: Item { anchors.centerIn: parent ColumnLayout { anchors.centerIn: parent spacing: 0 MaterialSymbol { Layout.alignment: Qt.AlignHCenter iconSize: 30 text: dark ? "dark_mode" : "light_mode" color: smallLightDarkPreferenceButton.colText } StyledText { Layout.alignment: Qt.AlignHCenter text: dark ? Translation.tr("Dark") : Translation.tr("Light") font.pixelSize: Appearance.font.pixelSize.smaller color: smallLightDarkPreferenceButton.colText } } } } // Wallpaper selection ContentSection { icon: "format_paint" title: Translation.tr("Wallpaper & Colors") Layout.fillWidth: true RowLayout { Layout.fillWidth: true Item { implicitWidth: 340 implicitHeight: 200 StyledImage { id: wallpaperPreview anchors.fill: parent fillMode: Image.PreserveAspectCrop source: Config.options.background.wallpaperPath cache: false layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: 360 height: 200 radius: Appearance.rounding.normal } } } } ColumnLayout { RippleButtonWithIcon { enabled: !randomWallProc.running visible: Config.options.policies.weeb === 1 Layout.fillWidth: true buttonRadius: Appearance.rounding.small materialIcon: "ifl" mainText: randomWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan") onClicked: { randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_konachan_wall.sh`; randomWallProc.running = true; } StyledToolTip { text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers") } } RippleButtonWithIcon { enabled: !randomWallProc.running visible: Config.options.policies.weeb === 1 Layout.fillWidth: true buttonRadius: Appearance.rounding.small materialIcon: "ifl" mainText: randomWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: osu! seasonal") onClicked: { randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_osu_wall.sh`; randomWallProc.running = true; } StyledToolTip { text: Translation.tr("Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers") } } RippleButtonWithIcon { Layout.fillWidth: true materialIcon: "wallpaper" StyledToolTip { text: Translation.tr("Pick wallpaper image on your system") } onClicked: { Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`); } mainContentComponent: Component { RowLayout { spacing: 10 StyledText { font.pixelSize: Appearance.font.pixelSize.small text: Translation.tr("Choose file") color: Appearance.colors.colOnSecondaryContainer } RowLayout { spacing: 3 KeyboardKey { key: "Ctrl" } KeyboardKey { key: Config.options.cheatsheet.superKey ?? "󰖳" } StyledText { Layout.alignment: Qt.AlignVCenter text: "+" } KeyboardKey { key: "T" } } } } } RowLayout { Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.fillHeight: true uniformCellSizes: true SmallLightDarkPreferenceButton { Layout.fillHeight: true dark: false } SmallLightDarkPreferenceButton { Layout.fillHeight: true dark: true } } } } ConfigSelectionArray { currentValue: Config.options.appearance.palette.type onSelected: newValue => { Config.options.appearance.palette.type = newValue; Quickshell.execDetached(["bash", "-c", `${Directories.wallpaperSwitchScriptPath} --noswitch`]); } options: [ { "value": "auto", "displayName": Translation.tr("Auto") }, { "value": "scheme-content", "displayName": Translation.tr("Content") }, { "value": "scheme-expressive", "displayName": Translation.tr("Expressive") }, { "value": "scheme-fidelity", "displayName": Translation.tr("Fidelity") }, { "value": "scheme-fruit-salad", "displayName": Translation.tr("Fruit Salad") }, { "value": "scheme-monochrome", "displayName": Translation.tr("Monochrome") }, { "value": "scheme-neutral", "displayName": Translation.tr("Neutral") }, { "value": "scheme-rainbow", "displayName": Translation.tr("Rainbow") }, { "value": "scheme-tonal-spot", "displayName": Translation.tr("Tonal Spot") } ] } ConfigSwitch { buttonIcon: "ev_shadow" text: Translation.tr("Transparency") checked: Config.options.appearance.transparency.enable onCheckedChanged: { Config.options.appearance.transparency.enable = checked; } } } ContentSection { icon: "screenshot_monitor" title: Translation.tr("Bar & screen") ConfigRow { ContentSubsection { title: Translation.tr("Bar position") ConfigSelectionArray { currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0) onSelected: newValue => { Config.options.bar.bottom = (newValue & 1) !== 0; Config.options.bar.vertical = (newValue & 2) !== 0; } options: [ { displayName: Translation.tr("Top"), icon: "arrow_upward", value: 0 // bottom: false, vertical: false }, { displayName: Translation.tr("Left"), icon: "arrow_back", value: 2 // bottom: false, vertical: true }, { displayName: Translation.tr("Bottom"), icon: "arrow_downward", value: 1 // bottom: true, vertical: false }, { displayName: Translation.tr("Right"), icon: "arrow_forward", value: 3 // bottom: true, vertical: true } ] } } ContentSubsection { title: Translation.tr("Bar style") ConfigSelectionArray { currentValue: Config.options.bar.cornerStyle onSelected: newValue => { Config.options.bar.cornerStyle = newValue; // Update local copy } options: [ { displayName: Translation.tr("Hug"), icon: "line_curve", value: 0 }, { displayName: Translation.tr("Float"), icon: "page_header", value: 1 }, { displayName: Translation.tr("Rect"), icon: "toolbar", value: 2 } ] } } } ConfigRow { ContentSubsection { title: Translation.tr("Screen round corner") ConfigSelectionArray { currentValue: Config.options.appearance.fakeScreenRounding onSelected: newValue => { Config.options.appearance.fakeScreenRounding = newValue; } options: [ { displayName: Translation.tr("No"), icon: "close", value: 0 }, { displayName: Translation.tr("Yes"), icon: "check", value: 1 }, { displayName: Translation.tr("When not fullscreen"), icon: "fullscreen_exit", value: 2 } ] } } } } NoticeBox { Layout.fillWidth: true text: Translation.tr('Not all options are available in this app. You should also check the config file by hitting the "Config file" button on the topleft corner or opening %1 manually.').arg(Directories.shellConfigPath) Item { Layout.fillWidth: true } RippleButtonWithIcon { id: copyPathButton property bool justCopied: false Layout.fillWidth: false buttonRadius: Appearance.rounding.small materialIcon: justCopied ? "check" : "content_copy" mainText: justCopied ? Translation.tr("Path copied") : Translation.tr("Copy path") onClicked: { copyPathButton.justCopied = true Quickshell.clipboardText = FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`); revertTextTimer.restart(); } colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer) colBackgroundHover: Appearance.colors.colPrimaryContainerHover colRipple: Appearance.colors.colPrimaryContainerActive Timer { id: revertTextTimer interval: 1500 onTriggered: { copyPathButton.justCopied = false } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.widgets ContentPage { forceWidth: true ContentSection { icon: "neurology" title: Translation.tr("AI") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("System prompt") text: Config.options.ai.systemPrompt wrapMode: TextEdit.Wrap onTextChanged: { Qt.callLater(() => { Config.options.ai.systemPrompt = text; }); } } } ContentSection { icon: "music_cast" title: Translation.tr("Music Recognition") ConfigSpinBox { icon: "timer_off" text: Translation.tr("Total duration timeout (s)") value: Config.options.musicRecognition.timeout from: 10 to: 100 stepSize: 2 onValueChanged: { Config.options.musicRecognition.timeout = value; } } ConfigSpinBox { icon: "av_timer" text: Translation.tr("Polling interval (s)") value: Config.options.musicRecognition.interval from: 2 to: 10 stepSize: 1 onValueChanged: { Config.options.musicRecognition.interval = value; } } } ContentSection { icon: "cell_tower" title: Translation.tr("Networking") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("User agent (for services that require it)") text: Config.options.networking.userAgent wrapMode: TextEdit.Wrap onTextChanged: { Config.options.networking.userAgent = text; } } } ContentSection { icon: "memory" title: Translation.tr("Resources") ConfigSpinBox { icon: "av_timer" text: Translation.tr("Polling interval (ms)") value: Config.options.resources.updateInterval from: 100 to: 10000 stepSize: 100 onValueChanged: { Config.options.resources.updateInterval = value; } } } ContentSection { icon: "file_open" title: Translation.tr("Save paths") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Video Recording Path") text: Config.options.screenRecord.savePath wrapMode: TextEdit.Wrap onTextChanged: { Config.options.screenRecord.savePath = text; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Screenshot Path (leave empty to just copy)") text: Config.options.screenSnip.savePath wrapMode: TextEdit.Wrap onTextChanged: { Config.options.screenSnip.savePath = text; } } } ContentSection { icon: "search" title: Translation.tr("Search") ConfigSwitch { text: Translation.tr("Use Levenshtein distance-based algorithm instead of fuzzy") checked: Config.options.search.sloppy onCheckedChanged: { Config.options.search.sloppy = checked; } StyledToolTip { text: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)") } } ContentSubsection { title: Translation.tr("Prefixes") ConfigRow { uniform: true MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Action") text: Config.options.search.prefix.action wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.action = text; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Clipboard") text: Config.options.search.prefix.clipboard wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.clipboard = text; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Emojis") text: Config.options.search.prefix.emojis wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.emojis = text; } } } ConfigRow { uniform: true MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Math") text: Config.options.search.prefix.math wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.math = text; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Shell command") text: Config.options.search.prefix.shellCommand wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.shellCommand = text; } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Web search") text: Config.options.search.prefix.webSearch wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.prefix.webSearch = text; } } } } ContentSubsection { title: Translation.tr("Web search") MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("Base URL") text: Config.options.search.engineBaseUrl wrapMode: TextEdit.Wrap onTextChanged: { Config.options.search.engineBaseUrl = text; } } } } // There's no update indicator in ii for now so we shouldn't show this yet // ContentSection { // icon: "deployed_code_update" // title: Translation.tr("System updates (Arch only)") // ConfigSwitch { // text: Translation.tr("Enable update checks") // checked: Config.options.updates.enableCheck // onCheckedChanged: { // Config.options.updates.enableCheck = checked; // } // } // ConfigSpinBox { // icon: "av_timer" // text: Translation.tr("Check interval (mins)") // value: Config.options.updates.checkInterval // from: 60 // to: 1440 // stepSize: 60 // onValueChanged: { // Config.options.updates.checkInterval = value; // } // } // } ContentSection { icon: "weather_mix" title: Translation.tr("Weather") ConfigRow { ConfigSwitch { buttonIcon: "assistant_navigation" text: Translation.tr("Enable GPS based location") checked: Config.options.bar.weather.enableGPS onCheckedChanged: { Config.options.bar.weather.enableGPS = checked; } } ConfigSwitch { buttonIcon: "thermometer" text: Translation.tr("Fahrenheit unit") checked: Config.options.bar.weather.useUSCS onCheckedChanged: { Config.options.bar.weather.useUSCS = checked; } StyledToolTip { text: Translation.tr("It may take a few seconds to update") } } } MaterialTextArea { Layout.fillWidth: true placeholderText: Translation.tr("City name") text: Config.options.bar.weather.city wrapMode: TextEdit.Wrap onTextChanged: { Config.options.bar.weather.city = text; } } ConfigSpinBox { icon: "av_timer" text: Translation.tr("Polling interval (m)") value: Config.options.bar.weather.fetchInterval from: 5 to: 50 stepSize: 5 onValueChanged: { Config.options.bar.weather.fetchInterval = value; } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/README.md ================================================ ## Waffle A recreation of Windoes. It's WIP! - If you install illogical-impulse fully, you can press Super+Alt+W to switch to this style. - If you're just copying the Quickshell config, run the config as usual (`qs -c ii`) then run `qs -c ii ipc call panelFamily cycle` ## From EWW version to Quickshell Just a reflection, in case anyone's interested. My blog is probably a better place for this, but it does not exist. Besides, this is going to change as I do more stuff. Currently there's just the bar. ### Improvements - QtQuick's `Button` has the `{top/bottom/left/right}Inset` properties, so we can have clickable regions expanding beyond the button background for free. With EWW it was annoying to wrap the button content with an `eventbox` that has some padding, then somehow use CSS selectors to make sure hovering effects work. I have to admit, (a large) part of that annoyance was with how bad my copy-pasting coding practice was at the time, but still... - Fancy effects: Gtk3 CSS does not support transformations. In QtQuick we can smack `rotation` and `scale` almost everywhere, so it's simple to make bouncy icons and rotating chevrons - Quickshell provides a system tray service (EWW does now but didn't at the time I created the EWW Windoes version), so now there's no Waybar needed for the tray. - QtQuick has `Loader`s, so we can have this live-switchable from the main style without killing the widget system, moving styles to the correct folder, and relaunching. - This time my computer is powerful enough to run a VM, so I don't have to occasionally reboot to take quick screenshots for reference. I try to make everything pixel-perfect so this is necessary. Speaking about pixel-perfectness, in the EWW version I hardcoded sizes, but this time I'm still doing that (lol), BUT that's normal and not a problem because Qt has the `QT_SCALE_FACTOR` env var for scaling. (Please feel free to prove me wrong in saying Gtk3 doesn't have that magic) ### Challenges - Qt is not Gtk and definitely not React - We don't get directional border on QtQuick `Rectangle`s like in CSS. I was able to get around this with manual drawing, but it was a bit more work - Fluent Icons is difficult to use, compared to Material Symbols - No React, so no clean use via a library. - If we use the font, there's no proper, searchable **codepoint** cheatsheet like Nerd Fonts, and there's no ligatures - I resorted to downloading individual SVGs. Not that nice, but it's better than scanning the whole table of icons every time I want one. For this we have fluenticon.com and fluenticons.co, but icons are awkwardly named and there's no alias. Why is the reload/refresh icon called "arrow-sync"? Well, the name is not misleading, but arguably reload/refresh are more common actions. From Fluent Design's [page on Iconography](https://fluent2.microsoft.design/iconography): > Fluent system icons are literal metaphors and are named for the shape or object they represent, not the functionality they provide "sync" is functionality. ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter.mainPage WBarAttachedPanelContent { id: root readonly property bool barAtBottom: Config.options.waffles.bar.bottom contentItem: ColumnLayout { // This somewhat sophisticated anchoring is needed to make opening anim not jump abruptly when stuff appear anchors { left: parent.left right: parent.right top: root.barAtBottom ? undefined : parent.top bottom: root.barAtBottom ? parent.bottom : undefined margins: root.visualMargin bottomMargin: 0 } spacing: 12 WPane { opacity: (MprisController.activePlayer != null && MprisController.isRealPlayer(MprisController.activePlayer)) ? 1 : 0 Layout.fillWidth: true contentItem: MediaPaneContent {} } WPane { Layout.fillWidth: true contentItem: WStackView { id: stackView implicitWidth: initItem.implicitWidth implicitHeight: initItem.implicitHeight initialItem: WPanelPageColumn { id: initItem MainPageBody {} WPanelSeparator {} MainPageFooter {} } Component.onCompleted: { ActionCenterContext.stackView = this; } MouseArea { anchors.fill: parent acceptedButtons: Qt.BackButton onClicked: { ActionCenterContext.back(); } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import Quickshell Singleton { id: root property StackView stackView function push(component) { if (stackView) { stackView.push(component) } } function back() { if (stackView && stackView.depth > 1) { stackView.pop() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/ExpandableChoiceButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter WChoiceButton { id: root property bool expanded: false checked: expanded clip: true horizontalPadding: 12 verticalPadding: 6 animateChoiceHighlight: false Behavior on implicitHeight { animation: Looks.transition.resize.createObject(this) } onClicked: expanded = !expanded } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/HeaderRow.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.modules.waffle.looks import qs.modules.waffle.actionCenter RowLayout { id: root required property string title spacing: 4 WPanelIconButton { iconName: "arrow-left" onClicked: ActionCenterContext.back() } WText { id: titleText Layout.fillWidth: true elide: Text.ElideRight text: root.title font.pixelSize: Looks.font.pixelSize.large } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/MediaPaneContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks Rectangle { id: root implicitHeight: 176 implicitWidth: 358 color: Looks.colors.bgPanelBody anchors.fill: parent readonly property var activePlayer: MprisController.activePlayer Column { anchors { fill: parent leftMargin: 23 rightMargin: 23 topMargin: 16 bottomMargin: 20 } spacing: 25 AppInfoRow { anchors { left: parent.left right: parent.right } } TrackInfoRow { anchors { left: parent.left right: parent.right } } ControlButtonsRow { anchors.horizontalCenter: parent.horizontalCenter } } component AppInfoRow: RowLayout { id: appInfo spacing: 8 property var desktopEntry: { const desktopEntryString = root.activePlayer?.desktopEntry ?? ""; return DesktopEntries.byId(desktopEntryString); } FluentIcon { implicitSize: 20 icon: appInfo.desktopEntry?.icon || "music-note-2" monochrome: !appInfo.desktopEntry?.icon } WText { Layout.fillWidth: true text: appInfo.desktopEntry?.name ?? Translation.tr("Media") horizontalAlignment: Text.AlignLeft elide: Text.ElideRight } } component TrackInfoRow: RowLayout { spacing: 16 ColumnLayout { id: trackInfo Layout.fillWidth: true spacing: 0 WText { Layout.fillWidth: true font.weight: Looks.font.weight.strong font.pixelSize: Looks.font.pixelSize.large elide: Text.ElideRight text: StringUtils.cleanMusicTitle(root.activePlayer?.trackTitle) || Translation.tr("Unknown Title") } WText { Layout.fillWidth: true elide: Text.ElideRight text: root.activePlayer?.trackArtist || Translation.tr("Unknown Artist") } } StyledImage { id: artImage Layout.preferredWidth: 58 Layout.preferredHeight: trackInfo.implicitHeight source: MprisController.activeTrack?.artUrl || "" fillMode: Image.PreserveAspectFit layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: artImage.width height: artImage.height Rectangle { anchors.centerIn: parent width: artImage.paintedWidth height: artImage.paintedHeight radius: Looks.radius.medium } } } } } component ControlButtonsRow: RowLayout { spacing: 26 MediaControlButton { iconName: "previous" enabled: root.activePlayer?.canGoPrevious ?? false onClicked: root.activePlayer?.previous() } MediaControlButton { readonly property bool playing: root.activePlayer?.isPlaying ?? false iconName: playing ? "pause" : "play" enabled: (playing && root.activePlayer?.canPause) || (!playing && root.activePlayer?.canPlay) onClicked: root.activePlayer?.togglePlaying() } MediaControlButton { iconName: "next" enabled: root.activePlayer?.canGoNext ?? false onClicked: root.activePlayer?.next() } } component MediaControlButton: WBorderlessButton { id: controlButton required property string iconName implicitHeight: 40 implicitWidth: 40 contentItem: Item { FluentIcon { anchors.centerIn: parent icon: controlButton.iconName monochrome: true filled: true implicitSize: 18 } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/SectionText.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks WText { Layout.leftMargin: 12 Layout.rightMargin: 12 Layout.topMargin: 6 Layout.bottomMargin: 6 font { weight: Looks.font.weight.stronger pixelSize: Looks.font.pixelSize.large } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/ToggleItem.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter RowLayout { id: root required property string name property alias description: descriptionText.text property alias iconName: iconWidget.icon property alias checked: switchWidget.checked spacing: 10 FluentIcon { id: iconWidget visible: !!root.iconName Layout.leftMargin: 12 Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop icon: root.iconName implicitSize: 18 } ColumnLayout { Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop Layout.fillWidth: true spacing: 1 // Name WText { Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Looks.font.pixelSize.large text: root.name } // Description WText { id: descriptionText Layout.fillWidth: true wrapMode: Text.Wrap color: Looks.colors.subfg } } MouseArea { Layout.rightMargin: 12 implicitWidth: switchRow.implicitWidth implicitHeight: switchRow.implicitHeight onPressed: switchWidget.down = true onReleased: switchWidget.down = false onClicked: switchWidget.checked = !switchWidget.checked Row { id: switchRow spacing: 12 WTextWithFixedWidth { longestText: "Off" // The larger one text: switchWidget.checked ? Translation.tr("On") : Translation.tr("Off") font.pixelSize: Looks.font.pixelSize.large } WSwitch { id: switchWidget Layout.alignment: Qt.AlignVCenter } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: root Connections { target: GlobalStates function onSidebarLeftOpenChanged() { if (GlobalStates.sidebarLeftOpen) panelLoader.active = true; } } Loader { id: panelLoader active: GlobalStates.sidebarLeftOpen sourceComponent: PanelWindow { id: panelWindow exclusiveZone: 0 WlrLayershell.namespace: "quickshell:actionCenter" WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { bottom: Config.options.waffles.bar.bottom top: !Config.options.waffles.bar.bottom right: true } implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight HyprlandFocusGrab { id: focusGrab active: true windows: [panelWindow] onCleared: content.close() } Connections { target: GlobalStates function onSidebarLeftOpenChanged() { if (!GlobalStates.sidebarLeftOpen) content.close(); } } ActionCenterContent { id: content anchors.fill: parent onClosed: { GlobalStates.sidebarLeftOpen = false; panelLoader.active = false; } } } } function toggleOpen() { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } IpcHandler { target: "sidebarLeft" function toggle() { root.toggleOpen(); } } GlobalShortcut { name: "sidebarLeftToggle" description: "Toggles left sidebar on press" onPressed: root.toggleOpen() } IpcHandler { target: "mediaControls" function toggle(): void { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } } GlobalShortcut { name: "mediaControlsToggle" description: "Toggles media controls on press" onPressed: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Bluetooth import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter Item { id: root Component.onCompleted: { if (Bluetooth.defaultAdapter.enabled) Bluetooth.defaultAdapter.discovering = true; } Component.onDestruction: { Bluetooth.defaultAdapter.discovering = false; } WPanelPageColumn { anchors.fill: parent BodyRectangle { implicitHeight: 400 implicitWidth: 50 ColumnLayout { anchors.fill: parent anchors.margins: 4 spacing: 4 ColumnLayout { implicitHeight: headerRow.implicitHeight Layout.fillWidth: true spacing: 0 RowLayout { Layout.fillWidth: true spacing: 0 HeaderRow { id: headerRow Layout.fillWidth: true title: Translation.tr("Bluetooth") } WSwitch { id: toggleSwitch Layout.rightMargin: 12 checked: Bluetooth.defaultAdapter?.enabled ?? false onCheckedChanged: { if (Bluetooth.defaultAdapter) { Bluetooth.defaultAdapter.enabled = checked; if (checked) { Bluetooth.defaultAdapter.discovering = true; } else { Bluetooth.defaultAdapter.discovering = false; } } } } } FadeLoader { Layout.leftMargin: -4 Layout.rightMargin: -4 Layout.fillWidth: true shown: Bluetooth.defaultAdapter?.discovering ?? false visible: true sourceComponent: WIndeterminateProgressBar {} } } StyledListView { id: listView Layout.fillHeight: true Layout.fillWidth: true animateAppearance: false contentHeight: contentLayout.implicitHeight contentWidth: width clip: true spacing: 4 model: ScriptModel { values: BluetoothStatus.friendlyDeviceList } delegate: BluetoothDeviceItem { required property BluetoothDevice modelData device: modelData width: ListView.view.width } } } } WPanelSeparator {} FooterRectangle { WTextButton { anchors { verticalCenter: parent.verticalCenter left: parent.left } text: Translation.tr("More Bluetooth settings") onClicked: { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); Quickshell.execDetached(["bash", "-c", Config.options.apps.bluetooth]); } } WBorderlessButton { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 12 enabled: !Bluetooth.defaultAdapter?.discovering && Bluetooth.defaultAdapter?.enabled onClicked: { Bluetooth.defaultAdapter.discovering = true; } contentItem: FluentIcon { icon: "arrow-counterclockwise" } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothDeviceItem.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Bluetooth import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter ExpandableChoiceButton { id: root required property BluetoothDevice device contentItem: RowLayout { id: contentItem spacing: 20 // Device icon FluentIcon { Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop icon: WIcons.bluetoothDeviceIcon(root?.device) implicitSize: 18 } ColumnLayout { Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop Layout.fillWidth: true spacing: 0 WText { // Network name Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Looks.font.pixelSize.large text: root.device?.name || Translation.tr("Unknown device") textFormat: Text.PlainText } WText { // Status id: statusText Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Looks.font.pixelSize.large color: Looks.colors.subfg visible: root.device?.connected || root.expanded Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } text: { if (!root.device?.paired) return Translation.tr("Not connected"); let statusText = root.device?.connected ? Translation.tr("Connected") : Translation.tr("Paired"); if (!root.device?.batteryAvailable) return statusText; statusText += ` • ${Math.round(root.device?.battery * 100)}%`; return statusText; } } WButton { Layout.alignment: Qt.AlignRight horizontalAlignment: Text.AlignHCenter visible: root.expanded checked: !(root.device?.connected ?? false) colBackground: Looks.colors.bg2 colBackgroundHover: Looks.colors.bg2Hover colBackgroundActive: Looks.colors.bg2Active implicitHeight: 30 implicitWidth: 148 text: root.device?.connected ? Translation.tr("Disconnect") : Translation.tr("Connect") onClicked: { if (root.device?.connected) { root.device.disconnect(); } else { root.device.connect(); } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBody.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter BodyRectangle { id: root implicitHeight: contentLayout.implicitHeight ColumnLayout { id: contentLayout anchors.fill: parent spacing: 0 MainPageBodyToggles { id: togglesContainer Layout.fillWidth: true } Rectangle { implicitHeight: 1 Layout.fillWidth: true color: Looks.colors.bg1Border } MainPageBodySliders { Layout.margins: 12 Layout.topMargin: 18 Layout.bottomMargin: 14 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodySliders.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter import qs.modules.waffle.actionCenter.volumeControl ColumnLayout { id: root property var screen: root.QsWindow.window?.screen property var brightnessMonitor: Brightness.getMonitorForScreen(screen) spacing: 12 RowLayout { spacing: 4 WPanelIconButton { color: colBackground property real animationValue: root.brightnessMonitor?.brightness ?? 0 rotation: animationValue * 180 scale: 0.8 + animationValue * 0.2 iconName: "weather-sunny" Behavior on animationValue { animation: Looks.transition.longMovement.createObject(this) } } WSlider { Layout.fillWidth: true value: root.brightnessMonitor?.brightness ?? 0 scrollable: true onMoved: { root.brightnessMonitor?.setBrightness(value) } } WPanelIconButton { opacity: 0 } } RowLayout { spacing: 4 WPanelIconButton { iconName: WIcons.volumeIcon onClicked: Audio.toggleMute(); } WSlider { Layout.fillWidth: true value: Audio.sink.audio.volume scrollable: true onMoved: { Audio.sink.audio.volume = value; } } WPanelIconButton { Component { id: volumeControlComp VolumeControl {} } onClicked: { ActionCenterContext.push(volumeControlComp) } contentItem: Item { anchors.centerIn: parent Row { anchors.centerIn: parent spacing: -1 FluentIcon { anchors.verticalCenter: parent.verticalCenter implicitSize: 18 icon: "options" } FluentIcon { anchors.verticalCenter: parent.verticalCenter implicitSize: 12 icon: "chevron-right" } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml ================================================ pragma ComponentBehavior: Bound import Qt.labs.synchronizer import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter.toggles Item { id: root property int columns: 3 property int rows: 2 property int currentPage: 0 readonly property int itemsPerPage: columns * rows readonly property int pages: Math.ceil(toggles.length / itemsPerPage) property list toggles: Config.options.waffles.actionCenter.toggles property real padding: 22 property real reducedBottomPadding: 12 implicitHeight: swipeView.implicitHeight + (padding - swipeView.padding) * 2 - reducedBottomPadding function togglesInPage(index) { var start = index * root.itemsPerPage; var end = start + root.itemsPerPage; return root.toggles.slice(start, end); } function decreasePage() { if (root.currentPage > 0) { root.currentPage -= 1; } } function increasePage() { if (root.currentPage < (root.pages - 1)) { root.currentPage += 1; } } clip: true SwipeView { id: swipeView anchors { fill: parent topMargin: root.padding - swipeView.padding bottomMargin: root.padding - swipeView.padding - root.reducedBottomPadding } padding: 4 leftPadding: root.padding rightPadding: root.padding spacing: padding orientation: Qt.Vertical clip: true Synchronizer on currentIndex { property alias source: root.currentPage } Repeater { model: root.pages delegate: GridLayout { id: grid required property int index // width: SwipeView.view.width - root.padding * 2 // height: SwipeView.view.height - root.padding * 2 columns: root.columns rows: root.rows rowSpacing: 12 columnSpacing: 12 Repeater { model: ScriptModel { values: root.togglesInPage(grid.index) } delegate: ActionCenterTogglesDelegateChooser {} } } } } VerticalPageIndicator { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 6 currentIndex: root.currentPage count: root.pages onClicked: (index) => root.currentPage = index onIncreasePage: root.increasePage(); onDecreasePage: root.decreasePage(); } FocusedScrollMouseArea { z: 999 anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: false onScrollUp: root.decreasePage(); onScrollDown: root.increasePage(); } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageFooter.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter FooterRectangle { // Battery button WBorderlessButton { visible: Battery.available anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: 12 contentItem: Row { spacing: 4 FluentIcon { anchors.verticalCenter: parent.verticalCenter icon: WIcons.batteryLevelIcon FluentIcon { anchors.fill: parent icon: WIcons.batteryIcon } } WText { anchors.verticalCenter: parent.verticalCenter text: `${Math.round(Battery.percentage * 100) || 0}%` } } } // Settings button WBorderlessButton { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 12 onClicked: { GlobalStates.sidebarLeftOpen = false; Quickshell.execDetached(["qs", "-p", Quickshell.shellPath("settings.qml")]); } contentItem: FluentIcon { icon: "settings" } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/nightLight/NightLightControl.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Bluetooth import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter Item { id: root Component.onCompleted: { if (Bluetooth.defaultAdapter.enabled) Bluetooth.defaultAdapter.discovering = true; } Component.onDestruction: { Bluetooth.defaultAdapter.discovering = false; } WPanelPageColumn { anchors.fill: parent BodyRectangle { implicitHeight: 400 implicitWidth: 50 ColumnLayout { anchors.fill: parent anchors.margins: 4 spacing: 4 HeaderRow { id: headerRow Layout.fillWidth: true title: Translation.tr("Eye protection") } StyledFlickable { id: flickable Layout.fillHeight: true Layout.fillWidth: true contentHeight: contentLayout.implicitHeight contentWidth: width clip: true bottomMargin: 12 NightLightOptions { id: contentLayout width: flickable.width } } } } WPanelSeparator {} FooterRectangle {} } component NightLightOptions: ColumnLayout { spacing: 10 SectionText { text: Translation.tr("Night Light") } ToggleItem { name: Translation.tr("Automatic") description: Translation.tr("Turn on from sunset to sunrise") iconName: "auto" checked: Config.options.light.night.automatic onCheckedChanged: { Config.options.light.night.automatic = checked; } } ToggleItem { name: Translation.tr("Enable now") description: Translation.tr("More comfortable viewing at night") iconName: WIcons.nightLightIcon checked: Hyprsunset.temperatureActive onCheckedChanged: { Hyprsunset.toggleTemperature(checked); } } IntensityEntry { Layout.fillWidth: true } SectionText { text: Translation.tr("Anti-flashbang (experimental)") } ToggleItem { name: Translation.tr("Enable") description: Translation.tr("Balance brightness based on content") iconName: "flash-off" checked: Config.options.light.antiFlashbang.enable onCheckedChanged: { Config.options.light.antiFlashbang.enable = checked; } } } component IntensityEntry: RowLayout { spacing: 10 FluentIcon { id: iconWidget Layout.leftMargin: 12 Layout.topMargin: 4 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop icon: "temperature" implicitSize: 18 } ColumnLayout { Layout.fillWidth: true // Layout.leftMargin: 40 Layout.rightMargin: 12 spacing: 4 ColumnLayout { Layout.fillWidth: true spacing: 0 WText { Layout.fillWidth: true text: Translation.tr("Intensity") font.pixelSize: Looks.font.pixelSize.large } WText { Layout.fillWidth: true text: Translation.tr("Adjust the color temperature") color: Looks.colors.subfg } } WSlider { Layout.fillWidth: true from: 6500 to: 1200 value: Config.options.light.night.colorTemperature onMoved: Config.options.light.night.colorTemperature = value tooltipContent: Math.round((value - from) / (to - from) * 100) } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter // It should be perfectly fine to use just a Column here, but somehow // using ColumnLayout prevents weird opening anim stutter ColumnLayout { id: root required property QuickToggleModel toggleModel property string name: toggleModel?.name ?? "" property string statusText: (toggleModel?.hasStatusText) ? (toggleModel?.statusText || (toggled ? Translation.tr("Active") : Translation.tr("Inactive"))) : "" property string tooltipText: toggleModel?.tooltipText ?? "" required property string icon property bool available: toggleModel?.available ?? true property bool toggled: toggleModel?.toggled ?? false property var mainAction: toggleModel?.mainAction ?? null property var altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null) property bool hasMenu: toggleModel?.hasMenu ?? false property Component menu property color colBackground: toggled ? Looks.colors.accent : Looks.colors.bg2 property color colBackgroundHovered: toggled ? Looks.colors.accentHover : Looks.colors.bg2Hover property color colBackgroundActive: toggled ? Looks.colors.accentActive : Looks.colors.bg2Active property color colBorder: toggled ? Looks.colors.accentHover : Looks.colors.bg2Border property color colForeground: toggled ? Looks.colors.accentFg : Looks.colors.fg1 spacing: 0 property real wholeToggleWidth: 96 AcrylicRectangle { Layout.fillWidth: true implicitWidth: root.wholeToggleWidth implicitHeight: 48 color: root.colBackground radius: Looks.radius.medium RowLayout { anchors.fill: parent spacing: 0 ToggleFragment { topLeftRadius: Looks.radius.medium bottomLeftRadius: Looks.radius.medium topRightRadius: root.hasMenu ? 0 : Looks.radius.medium bottomRightRadius: root.hasMenu ? 0 : Looks.radius.medium iconName: root.icon onClicked: root.mainAction && root.mainAction() } FadeLoader { Layout.fillHeight: true Layout.fillWidth: true shown: root.hasMenu sourceComponent: ToggleFragment { topLeftRadius: 0 bottomLeftRadius: 0 topRightRadius: Looks.radius.medium bottomRightRadius: Looks.radius.medium iconName: "chevron-right" onClicked: { ActionCenterContext.stackView.push(root.menu) } } } } } Item { id: toggleNameWidget implicitWidth: root.wholeToggleWidth implicitHeight: 36 WText { id: toggleNameText anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right } horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight text: root.name } } component ToggleFragment: WButton { id: toggleFragment required property string iconName Layout.fillHeight: true Layout.fillWidth: true inset: 0 backgroundOpacity: 0.8 checked: root.toggled border.width: 1 border.color: root.colBorder contentItem: Item { anchors.centerIn: parent FluentIcon { anchors.centerIn: parent icon: toggleFragment.iconName implicitSize: 18 monochrome: true filled: root.toggled color: root.colForeground } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.models.quickToggles import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter.bluetooth import qs.modules.waffle.actionCenter.nightLight import qs.modules.waffle.actionCenter.volumeControl import qs.modules.waffle.actionCenter.wifi DelegateChooser { id: root // role: "type" is implied by usage DelegateChoice { roleValue: "antiFlashbang" ActionCenterToggleButton { toggleModel: AntiFlashbangToggle {} icon: "flash-off" menu: Component { NightLightControl {} } } } DelegateChoice { roleValue: "bluetooth" ActionCenterToggleButton { toggleModel: BluetoothToggle {} name: toggleModel.statusText icon: WIcons.bluetoothIcon menu: Component { BluetoothControl {} } } } DelegateChoice { roleValue: "cloudflareWarp" ActionCenterToggleButton { toggleModel: CloudflareWarpToggle {} icon: "cloudflare" } } DelegateChoice { roleValue: "colorPicker" ActionCenterToggleButton { toggleModel: ColorPickerToggle {} icon: "eyedropper" } } DelegateChoice { roleValue: "darkMode" ActionCenterToggleButton { toggleModel: DarkModeToggle {} icon: "dark-theme" } } DelegateChoice { roleValue: "easyEffects" ActionCenterToggleButton { toggleModel: EasyEffectsToggle {} icon: "device-eq" } } DelegateChoice { roleValue: "gameMode" ActionCenterToggleButton { toggleModel: GameModeToggle {} icon: "games" } } DelegateChoice { roleValue: "idleInhibitor" ActionCenterToggleButton { toggleModel: IdleInhibitorToggle {} icon: "drink-coffee" } } DelegateChoice { roleValue: "mic" ActionCenterToggleButton { toggleModel: MicToggle {} icon: WIcons.micIcon menu: Component { VolumeControl { output: false } } } } DelegateChoice { roleValue: "musicRecognition" ActionCenterToggleButton { toggleModel: MusicRecognitionToggle {} icon: "music-note-2" } } DelegateChoice { roleValue: "network" ActionCenterToggleButton { toggleModel: NetworkToggle {} name: toggleModel.statusText icon: WIcons.internetIcon menu: Component { WifiControl {} } } } DelegateChoice { roleValue: "nightLight" ActionCenterToggleButton { toggleModel: NightLightToggle {} icon: WIcons.nightLightIcon menu: Component { NightLightControl {} } } } DelegateChoice { roleValue: "notifications" ActionCenterToggleButton { toggleModel: NotificationToggle {} icon: WIcons.notificationsIcon } } DelegateChoice { roleValue: "onScreenKeyboard" ActionCenterToggleButton { toggleModel: OnScreenKeyboardToggle {} icon: GlobalStates.oskOpen ? "keyboard-dock" : "keyboard" } } DelegateChoice { roleValue: "powerProfile" ActionCenterToggleButton { toggleModel: PowerProfilesToggle {} icon: WIcons.powerProfileIcon name: toggleModel.statusText } } DelegateChoice { roleValue: "screenSnip" ActionCenterToggleButton { toggleModel: ScreenSnipToggle {} icon: "cut" } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/WNetworkToggle.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.actionCenter ActionCenterToggle { id: root name: Network.ethernet ? Translation.tr("Network") : Network.networkName } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeControl.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter Item { id: root property bool output: true WPanelPageColumn { anchors.fill: parent BodyRectangle { implicitHeight: 400 implicitWidth: 50 ColumnLayout { anchors.fill: parent anchors.margins: 4 spacing: 4 HeaderRow { Layout.fillWidth: true title: root.output ? Translation.tr("Sound output") : Translation.tr("Sound input") } StyledFlickable { id: flickable Layout.fillHeight: true Layout.fillWidth: true contentHeight: contentLayout.implicitHeight contentWidth: width clip: true AudioChoices { id: contentLayout width: flickable.width } } } } WPanelSeparator {} FooterRectangle { WButton { id: moreSettingsButton anchors { verticalCenter: parent.verticalCenter left: parent.left } implicitHeight: 40 implicitWidth: contentItem.implicitWidth + 30 color: "transparent" onClicked: { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); Quickshell.execDetached(["bash", "-c", Config.options.apps.volumeMixer]); } contentItem: Item { anchors.centerIn: parent implicitWidth: buttonText.implicitWidth WText { id: buttonText anchors.centerIn: parent text: Translation.tr("More volume settings") color: moreSettingsButton.pressed ? Looks.colors.fg : Looks.colors.fg1 } } } } } component AudioChoices: ColumnLayout { spacing: 4 SectionText { text: root.output ? Translation.tr("Output device") : Translation.tr("Input device") } Repeater { model: ScriptModel { values: root.output ? Audio.outputDevices : Audio.inputDevices } delegate: WChoiceButton { required property var modelData icon.name: WIcons.audioDeviceIcon(modelData) text: Audio.friendlyDeviceName(modelData) checked: (root.output ? Audio.sink : Audio.source) === modelData onClicked: { if (root.output) Audio.setDefaultSink(modelData); else Audio.setDefaultSource(modelData); } } } WPanelSeparator { visible: EasyEffects.available && root.output color: Looks.colors.bg2Hover } //////////////////////////////////////////////////////////// SectionText { visible: EasyEffects.available && root.output text: Translation.tr("Sound effects") } WChoiceButton { visible: EasyEffects.available && root.output text: Translation.tr("Off") checked: !EasyEffects.active onClicked: EasyEffects.disable() } WChoiceButton { visible: EasyEffects.available && root.output text: "EasyEffects" checked: EasyEffects.active onClicked: EasyEffects.enable() } WPanelSeparator { color: Looks.colors.bg2Hover } //////////////////////////////////////////////////////////// SectionText { visible: EasyEffects.available text: Translation.tr("Volume mixer") } VolumeEntry { node: root.output ? Audio.sink : Audio.source icon: root.output ? "speaker" : "mic-on" monochrome: true } Repeater { model: ScriptModel { values: root.output ? Audio.outputAppNodes : Audio.inputAppNodes } delegate: VolumeEntry { required property var modelData node: modelData } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeEntry.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter RowLayout { id: root required property PwNode node property alias icon: iconButton.iconName property alias monochrome: iconButton.monochrome monochrome: false PwObjectTracker { // Necessary for useful info to be present in 'node' objects: [root.node] } WPanelIconButton { id: iconButton iconName: WIcons.audioAppIcon(root.node) onClicked: root.node.audio.muted = !root.node?.audio.muted FluentIcon { id: muteIcon visible: root.node?.audio.muted ?? false anchors { bottom: parent.bottom right: parent.right margins: -1 } implicitSize: 16 icon: "speaker-mute" } WToolTip { extraVisibleCondition: iconButton.shouldShowTooltip text: Audio.appNodeDisplayName(root.node) } } WSlider { Layout.fillWidth: true Layout.rightMargin: 10 value: root.node?.audio.volume ?? 0 onMoved: root.node.audio.volume = value } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter ExpandableChoiceButton { id: root required property WifiAccessPoint wifiNetwork contentItem: RowLayout { id: contentItem spacing: 12 FluentIcon { // Duotone hack Layout.bottomMargin: 2 Layout.alignment: Qt.AlignTop property int strength: root.wifiNetwork?.strength ?? 0 icon: "wifi-1" implicitSize: 30 color: Looks.colors.inactiveIcon FluentIcon { // Signal property int strength: root.wifiNetwork?.strength ?? 0 icon: WIcons.wifiIconForStrength(strength) implicitSize: 30 FluentIcon { // Security anchors { right: parent.right bottom: parent.bottom } visible: root?.wifiNetwork?.isSecure ?? false icon: "lock-closed" filled: true implicitSize: 14 } } } ColumnLayout { Layout.topMargin: statusText.visible ? 4 : 7 Layout.bottomMargin: 4 Layout.alignment: Qt.AlignTop Layout.fillWidth: true spacing: 1 Behavior on Layout.topMargin { animation: Looks.transition.move.createObject(this) } WText { // Network name Layout.fillWidth: true elide: Text.ElideRight font.pixelSize: Looks.font.pixelSize.large text: root.wifiNetwork?.ssid ?? Translation.tr("Unknown") textFormat: Text.PlainText } WText { // Status id: statusText Layout.fillWidth: true elide: Text.ElideRight text: root.wifiNetwork?.active ? Translation.tr("Connected") : root.wifiNetwork?.isSecure ? Translation.tr("Secured") : Translation.tr("Not secured") font.pixelSize: Looks.font.pixelSize.large color: Looks.colors.subfg visible: root.wifiNetwork?.active || root.expanded Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } } WButton { Layout.alignment: Qt.AlignRight horizontalAlignment: Text.AlignHCenter visible: root.expanded checked: !(root.wifiNetwork?.active ?? false) colBackground: Looks.colors.bg2 colBackgroundHover: Looks.colors.bg2Hover colBackgroundActive: Looks.colors.bg2Active implicitHeight: 30 implicitWidth: 148 text: root.wifiNetwork?.active ? Translation.tr("Disconnect") : Translation.tr("Connect") onClicked: { if (root.wifiNetwork?.active) { Network.disconnectWifiNetwork(); } else { Network.connectToWifiNetwork(root.wifiNetwork); } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.actionCenter Item { id: root Component.onCompleted: { Network.rescanWifi(); } WPanelPageColumn { anchors.fill: parent BodyRectangle { implicitHeight: 400 implicitWidth: 50 ColumnLayout { anchors.fill: parent anchors.margins: 4 spacing: 4 ColumnLayout { implicitHeight: headerRow.implicitHeight Layout.fillWidth: true spacing: 0 RowLayout { Layout.fillWidth: true spacing: 0 HeaderRow { id: headerRow Layout.fillWidth: true title: Translation.tr("Wi-Fi") } WSwitch { id: toggleSwitch Layout.rightMargin: 12 checked: Network.wifiStatus !== "disabled" onCheckedChanged: { Network.enableWifi(checked); Network.rescanWifi(); } } } FadeLoader { Layout.leftMargin: -4 Layout.rightMargin: -4 Layout.fillWidth: true shown: Network.wifiScanning visible: true sourceComponent: WIndeterminateProgressBar {} } } StyledListView { id: listView Layout.fillHeight: true Layout.fillWidth: true animateAppearance: false contentHeight: contentLayout.implicitHeight contentWidth: width clip: true spacing: 4 model: ScriptModel { values: Network.friendlyWifiNetworks } delegate: WWifiNetworkItem { required property WifiAccessPoint modelData wifiNetwork: modelData width: ListView.view.width } } } } WPanelSeparator {} FooterRectangle { WTextButton { anchors { verticalCenter: parent.verticalCenter left: parent.left } text: Translation.tr("More Internet settings") onClicked: { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarLeft", "toggle"]); Quickshell.execDetached(["bash", "-c", Config.options.apps.network]); } } WBorderlessButton { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right anchors.rightMargin: 12 enabled: !Network.wifiScanning onClicked: { Network.rescanWifi(); } contentItem: FluentIcon { icon: "arrow-counterclockwise" } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/background/WaffleBackground.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.widgets.widgetCanvas import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs.modules.ii.background.widgets import qs.modules.ii.background.widgets.clock import qs.modules.ii.background.widgets.weather Variants { id: root model: Quickshell.screens PanelWindow { id: panelRoot required property var modelData screen: modelData exclusionMode: ExclusionMode.Ignore WlrLayershell.layer: WlrLayer.Bottom WlrLayershell.namespace: "quickshell:background" anchors { top: true bottom: true left: true right: true } color: "transparent" StyledImage { anchors.fill: parent source: Config.options.background.wallpaperPath fillMode: Image.PreserveAspectCrop } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml ================================================ import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import org.kde.kirigami as Kirigami import qs.services import qs.modules.common import qs.modules.waffle.looks BarButton { id: root required property string iconName property bool multiple: false property bool separateLightDark: false property alias tryCustomIcon: iconWidget.tryCustomIcon leftInset: 2 rightInset: 2 implicitWidth: height - topInset - bottomInset + leftInset + rightInset property real pressedScale: 5/6 onDownChanged: { scaleAnim.duration = root.down ? 150 : 200 scaleAnim.easing.bezierCurve = root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut contentItem.scale = root.down ? root.pressedScale : 1 // If/When we do dragging, the scale is 1.25 } background: Item { id: background BackgroundAcrylicRectangle { id: mainBgRect anchors.fill: parent layer.enabled: root.multiple layer.effect: OpacityMask { invert: true maskSource: Item { width: mainBgRect.width height: mainBgRect.height Rectangle { anchors.fill: parent anchors.rightMargin: 3 radius: mainBgRect.radius } } } } Loader { anchors.fill: parent anchors.rightMargin: 5 active: root.multiple sourceComponent: BackgroundAcrylicRectangle {} } } contentItem: Item { id: contentItem anchors.centerIn: root.background implicitHeight: iconWidget.implicitHeight implicitWidth: iconWidget.implicitWidth Behavior on scale { NumberAnimation { id: scaleAnim easing.type: Easing.BezierSpline } } WAppIcon { id: iconWidget anchors.centerIn: parent iconName: root.iconName separateLightDark: root.separateLightDark } } component BackgroundAcrylicRectangle: AcrylicRectangle { shiny: ((root.hovered && !root.down) || root.checked) color: root.color border.width: 1 border.color: root.colBackgroundBorder Behavior on border.color { animation: Looks.transition.color.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks AcrylicButton { id: root property var altAction: () => {} property var middleClickAction: () => {} Layout.fillHeight: true topInset: 4 bottomInset: 4 leftInset: 0 rightInset: 0 horizontalPadding: 8 colBackground: ColorUtils.transparentize(Looks.colors.bg1) MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onPressed: (event) => { root.down = true; } onReleased: (event) => { root.down = false; } onClicked: (event) => { if (event.button === Qt.LeftButton) root.clicked(); if (event.button === Qt.RightButton) root.altAction(); if (event.button === Qt.MiddleButton) root.middleClickAction(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/BarIconButton.qml ================================================ import QtQuick import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks import qs.modules.waffle.bar BarButton { id: root property alias iconName: iconContent.icon property alias iconSource: iconContent.source property alias iconSize: iconContent.implicitSize property alias iconRotation: iconContent.rotation property alias iconMonochrome: iconContent.monochrome property alias iconScale: iconContent.scale property alias tooltipText: tooltip.text property alias overlayingItems: iconContent.data implicitWidth: 32 contentItem: Item { anchors.centerIn: parent implicitWidth: iconContent.implicitWidth implicitHeight: iconContent.implicitHeight FluentIcon { id: iconContent anchors.centerIn: parent implicitSize: 16 icon: root.iconName monochrome: false } } BarToolTip { id: tooltip extraVisibleCondition: root.shouldShowTooltip && text !== "" } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks BarPopup { id: root default property var menuData property var model: [ { iconName: "start-here", text: "Start", action: () => {print("hello")} }, { type : "separator" }, ] readonly property bool hasIcons: model.some(item => item.iconName !== undefined && item.iconName !== "") padding: 2 contentItem: ColumnLayout { anchors.centerIn: parent spacing: 0 Repeater { model: root.model delegate: DelegateChooser { role: "type" DelegateChoice { roleValue: "separator" Rectangle { Layout.topMargin: 2 Layout.bottomMargin: 2 Layout.fillWidth: true implicitHeight: 1 color: Looks.colors.bg0Border } } DelegateChoice { roleValue: undefined WButton { id: btn Layout.fillWidth: true inset: 2 required property var modelData forceShowIcon: root.hasIcons icon.name: modelData.iconName ? modelData.iconName : "" monochromeIcon: modelData.monochromeIcon ?? true text: modelData.text ? modelData.text : "" onClicked: { if (modelData.action) modelData.action(); root.close(); } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Loader { id: root required property var contentItem property real padding: Looks.radius.large - Looks.radius.medium property bool noSmoothClosing: !Config.options.waffles.tweaks.smootherMenuAnimations property bool closeOnFocusLost: true signal focusCleared() property Item anchorItem: parent property real visualMargin: 12 readonly property bool barAtBottom: Config.options.waffles.bar.bottom property real ambientShadowWidth: 1 onFocusCleared: { if (!root.closeOnFocusLost) return; root.close() } function grabFocus() { // Doesn't work item.grabFocus(); } function close() { item.close(); } function updateAnchor() { item?.anchor.updateAnchor(); } active: false visible: active sourceComponent: PopupWindow { id: popupWindow visible: true Component.onCompleted: { openAnim.start(); } anchor { adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX item: root.anchorItem gravity: root.barAtBottom ? Edges.Top : Edges.Bottom edges: root.barAtBottom ? Edges.Top : Edges.Bottom } HyprlandFocusGrab { id: focusGrab active: true windows: [popupWindow] onCleared: root.focusCleared(); } function close() { if (root.noSmoothClosing) root.active = false; else closeAnim.start(); } function grabFocus() { focusGrab.active = true; // Doesn't work } implicitWidth: realContent.implicitWidth + (root.ambientShadowWidth * 2) + (root.visualMargin * 2) implicitHeight: realContent.implicitHeight + (root.ambientShadowWidth * 2) + (root.visualMargin * 2) property real sourceEdgeMargin: -implicitHeight PropertyAnimation { id: openAnim target: popupWindow property: "sourceEdgeMargin" to: (root.ambientShadowWidth + root.visualMargin) duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } SequentialAnimation { id: closeAnim PropertyAnimation { target: popupWindow property: "sourceEdgeMargin" to: -implicitHeight duration: 150 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } ScriptAction { script: { root.active = false; } } } color: "transparent" WAmbientShadow { target: realContent } Rectangle { id: realContent z: 1 anchors { left: parent.left right: parent.right top: root.barAtBottom ? undefined : parent.top bottom: root.barAtBottom ? parent.bottom : undefined margins: root.ambientShadowWidth + root.visualMargin // Opening anim bottomMargin: root.barAtBottom ? popupWindow.sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin) topMargin: root.barAtBottom ? (root.ambientShadowWidth + root.visualMargin) : popupWindow.sourceEdgeMargin } color: Looks.colors.bg1Base radius: Looks.radius.large // test implicitWidth: root.contentItem.implicitWidth + (root.padding * 2) implicitHeight: root.contentItem.implicitHeight + (root.padding * 2) children: [root.contentItem] } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/BarToolTip.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.waffle.looks WPopupToolTip { anchorEdges: Config.options.waffles.bar.bottom ? Edges.Top : Edges.Bottom } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml ================================================ import QtQuick import QtQuick.Layouts import org.kde.kirigami as Kirigami import qs import qs.services import qs.modules.common import qs.modules.waffle.looks AppButton { id: root iconName: checked ? "system-search-checked" : "system-search" separateLightDark: true checked: GlobalStates.searchOpen && LauncherSearch.query !== "" onClicked: { GlobalStates.searchOpen = !GlobalStates.searchOpen; // For now... } BarToolTip { id: tooltip text: Translation.tr("Search") extraVisibleCondition: root.shouldShowTooltip } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks // TODO: Replace the icon with QMLized svg (with /usr/lib/qt6/bin/svgtoqml) for proper micro-animation AppButton { id: root leftInset: Config.options.waffles.bar.leftAlignApps ? 12 : 0 iconName: down ? "start-here-pressed" : "start-here" checked: GlobalStates.searchOpen && LauncherSearch.query === "" onClicked: { GlobalStates.searchOpen = !GlobalStates.searchOpen; } BarToolTip { id: tooltip text: Translation.tr("Start") extraVisibleCondition: root.shouldShowTooltip } altAction: () => { contextMenu.active = true; } BarMenu { id: contextMenu model: [ { text: Translation.tr("Terminal"), action: () => { Quickshell.execDetached(["bash", "-c", Config.options.apps.terminal]); } }, { text: Translation.tr("Task Manager"), action: () => { Quickshell.execDetached(["bash", "-c", Config.options.apps.taskManager]); } }, { text: Translation.tr("Settings"), action: () => { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath("settings.qml")]); } }, { text: Translation.tr("File Explorer"), action: () => { Qt.openUrlExternally(Directories.home); } }, { text: Translation.tr("Search"), action: () => { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "overview", "toggle"]); } }, ] } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks BarButton { id: root checked: GlobalStates.sidebarLeftOpen onClicked: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; } contentItem: Item { anchors.fill: parent implicitHeight: column.implicitHeight implicitWidth: column.implicitWidth Row { id: column anchors { top: parent.top bottom: parent.bottom horizontalCenter: parent.horizontalCenter } spacing: 4 IconHoverArea { id: internetHoverArea iconItem: FluentIcon { anchors.verticalCenter: parent.verticalCenter icon: "wifi-1" color: Looks.colors.inactiveIcon FluentIcon { anchors.fill: parent icon: WIcons.internetIcon } } } IconHoverArea { id: volumeHoverArea iconItem: FluentIcon { anchors.verticalCenter: parent.verticalCenter icon: "speaker" color: Looks.colors.inactiveIcon FluentIcon { anchors.fill: parent icon: WIcons.volumeIcon } } onScrollDown: Audio.decrementVolume(); onScrollUp: Audio.incrementVolume(); } IconHoverArea { id: batteryHoverArea visible: Battery?.available ?? false iconItem: FluentIcon { anchors.verticalCenter: parent.verticalCenter icon: WIcons.batteryLevelIcon FluentIcon { anchors.fill: parent icon: WIcons.batteryIcon } } } } } component IconHoverArea: FocusedScrollMouseArea { id: hoverArea required property var iconItem anchors { top: parent.top bottom: parent.bottom } hoverEnabled: true implicitHeight: hoverArea.iconItem.implicitHeight implicitWidth: hoverArea.iconItem.implicitWidth onPressed: (event) => event.accepted = false; // Don't consume clicks children: [iconItem] } BarToolTip { extraVisibleCondition: root.shouldShowTooltip && internetHoverArea.containsMouse text: Translation.tr("%1\nInternet access").arg(Network.ethernet ? Translation.tr("Network") : Network.networkName) } BarToolTip { extraVisibleCondition: root.shouldShowTooltip && volumeHoverArea.containsMouse text: Translation.tr("Speakers (%1): %2") // .arg(Audio.sink?.nickname || Audio.sink?.description || Translation.tr("Unknown")) // .arg(Audio.sink?.audio.muted ? Translation.tr("Muted") : `${Math.round(Audio.sink?.audio.volume * 100) || 0}%`) // } BarToolTip { extraVisibleCondition: root.shouldShowTooltip && batteryHoverArea.containsMouse text: Translation.tr("Battery: %1%2") // .arg(`${Math.round(Battery.percentage * 100) || 0}%`) // .arg(Battery.isPluggedIn ? (" " + Translation.tr("(Plugged in)")) : "") } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/TaskViewButton.qml ================================================ import QtQuick import QtQuick.Layouts import org.kde.kirigami as Kirigami import qs import qs.services import qs.modules.common import qs.modules.waffle.looks AppButton { id: root iconName: (down && !checked) ? "task-view-pressed" : "task-view" pressedScale: checked ? 5/6 : 1 separateLightDark: true checked: GlobalStates.overviewOpen onClicked: { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } BarToolTip { extraVisibleCondition: root.shouldShowTooltip text: Translation.tr("Task View") // Should be a preview of workspaces, but we'll have this for now... } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs import qs.services import qs.modules.common import qs.modules.waffle.looks BarButton { id: root rightInset: 12 // For now this is the rightmost button. Desktop peek is useless. (for now) leftPadding: 12 rightPadding: 22 checked: GlobalStates.sidebarRightOpen onClicked: { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } contentItem: Item { // anchors.centerIn: parent implicitHeight: contentLayout.implicitHeight implicitWidth: contentLayout.implicitWidth Row { id: contentLayout anchors.centerIn: parent spacing: 7 Column { anchors.verticalCenter: parent.verticalCenter WText { anchors.right: parent.right text: DateTime.time } WText { anchors.right: parent.right text: DateTime.date } } FluentIcon { visible: Notifications.silent anchors.verticalCenter: parent.verticalCenter icon: "alert-snooze" implicitSize: 18 filled: true } } } BarToolTip { id: tooltip extraVisibleCondition: root.shouldShowTooltip text: `${Qt.locale().toString(DateTime.clock.date, "dddd, MMMM d, yyyy")}\n\n${Qt.locale().toString(DateTime.clock.date, "ddd hh:mm AP")}` } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml ================================================ import QtQuick import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks import qs.modules.waffle.bar.tray BarIconButton { id: root visible: Updates.updateAdvised || Updates.updateStronglyAdvised padding: 4 iconName: "arrow-sync" iconSize: 20 // Needed because the icon appears to have some padding iconMonochrome: true tooltipText: Translation.tr("Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages").arg(Updates.count) onClicked: { Quickshell.execDetached(["bash", "-c", Config.options.apps.update]); } overlayingItems: Rectangle { anchors { right: parent.right bottom: parent.bottom margins: 1 } implicitWidth: 8 implicitHeight: implicitWidth radius: height / 2 color: Updates.updateStronglyAdvised ? Looks.colors.warning : Looks.colors.accent } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: root LazyLoader { id: barLoader active: GlobalStates.barOpen component: Variants { model: Quickshell.screens delegate: PanelWindow { // Bar window id: barRoot required property var modelData screen: modelData exclusionMode: ExclusionMode.Ignore exclusiveZone: implicitHeight WlrLayershell.namespace: "quickshell:bar" anchors { left: true right: true bottom: Config.options.waffles.bar.bottom top: !Config.options.waffles.bar.bottom } color: "transparent" implicitHeight: content.implicitHeight implicitWidth: content.implicitWidth WaffleBarContent { id: content anchors.fill: parent } } } } IpcHandler { target: "bar" function toggle(): void { GlobalStates.barOpen = !GlobalStates.barOpen } function close(): void { GlobalStates.barOpen = false } function open(): void { GlobalStates.barOpen = true } } GlobalShortcut { name: "barToggle" description: "Toggles bar on press" onPressed: { GlobalStates.barOpen = !GlobalStates.barOpen; } } GlobalShortcut { name: "barOpen" description: "Opens bar on press" onPressed: { GlobalStates.barOpen = true; } } GlobalShortcut { name: "barClose" description: "Closes bar on press" onPressed: { GlobalStates.barOpen = false; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.bar.tasks import qs.modules.waffle.bar.tray Rectangle { id: root color: Looks.colors.bg0 implicitHeight: 48 Rectangle { id: border anchors { left: parent.left right: parent.right top: Config.options.waffles.bar.bottom ? parent.top : undefined bottom: Config.options.waffles.bar.bottom ? undefined : parent.bottom } color: Looks.colors.bg0Border implicitHeight: 1 } BarGroupRow { id: bloatRow anchors.left: parent.left opacity: Config.options.waffles.bar.leftAlignApps ? 0 : 1 visible: opacity > 0 Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } WidgetsButton {} } BarGroupRow { id: appsRow anchors.left: undefined anchors.horizontalCenter: parent.horizontalCenter states: State { name: "left" when: Config.options.waffles.bar.leftAlignApps AnchorChanges { target: appsRow anchors.left: parent.left anchors.horizontalCenter: undefined } } transitions: Transition { animations: Looks.transition.anchor.createObject(this) } StartButton {} SearchButton {} TaskViewButton {} Tasks {} } BarGroupRow { id: systemRow anchors.right: parent.right FadeLoader { Layout.fillHeight: true shown: Config.options.waffles.bar.leftAlignApps sourceComponent: WidgetsButton {} } Tray {} UpdatesButton {} SystemButton {} TimeButton {} } component BarGroupRow: RowLayout { anchors.top: parent.top anchors.bottom: parent.bottom spacing: 0 } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/WidgetsButton.qml ================================================ import QtQuick import QtQuick.Layouts import org.kde.kirigami as Kirigami import qs import qs.services import qs.modules.common import qs.modules.waffle.looks AppButton { id: root readonly property bool expandedForm: Config.options.waffles.bar.leftAlignApps leftInset: Config.options.waffles.bar.leftAlignApps ? 0 : 12 implicitWidth: expandedForm ? 148 : (height - topInset - bottomInset + leftInset + rightInset) iconName: "widgets" checked: GlobalStates.sidebarLeftOpen onClicked: { GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen } onDownChanged: { scaleAnim.duration = root.down ? 150 : 200 scaleAnim.easing.bezierCurve = root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut iconWidget.scale = root.down ? 5/6 : 1 // If/When we do dragging, the scale is 1.25 } contentItem: Item { anchors { verticalCenter: parent.verticalCenter left: root.expandedForm ? parent.left : undefined horizontalCenter: root.expandedForm ? undefined : background.horizontalCenter } implicitHeight: row.implicitHeight implicitWidth: row.implicitWidth Row { id: row anchors { verticalCenter: parent.verticalCenter left: root.expandedForm ? parent.left : undefined horizontalCenter: root.expandedForm ? undefined : parent.horizontalCenter margins: 8 } spacing: 6 WAppIcon { id: iconWidget anchors.verticalCenter: parent.verticalCenter iconName: root.iconName Behavior on scale { NumberAnimation { id: scaleAnim easing.type: Easing.BezierSpline } } } Column { visible: root.expandedForm anchors.verticalCenter: parent.verticalCenter WText { text: Translation.tr("Widgets") } } } } BarToolTip { extraVisibleCondition: root.shouldShowTooltip text: Translation.tr("Widgets") } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.bar import Quickshell AppButton { id: root required property var appEntry readonly property bool isSeparator: appEntry.appId === "SEPARATOR" property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId) Timer { // Retry looking up the desktop entry if it failed (e.g. database not loaded yet) property int retryCount: 5 interval: 1000 running: !root.isSeparator && root.desktopEntry === null && retryCount > 0 repeat: true onTriggered: { retryCount--; root.desktopEntry = DesktopEntries.heuristicLookup(root.appEntry.appId); } } property bool active: root.appEntry.toplevels.some(t => t.activated) property bool hasWindows: appEntry.toplevels.length > 0 signal hoverPreviewRequested() signal hoverPreviewDismissed() multiple: appEntry.toplevels.length > 1 checked: active iconName: AppSearch.guessIcon(appEntry.appId) tryCustomIcon: false onHoverTimedOut: { root.hoverPreviewRequested() } onClicked: { root.hoverTimer.stop() // Prevents preview showing up when clicking to focus if (root.multiple) { root.hoverPreviewRequested() } else if (root.appEntry.toplevels.length === 1) { root.appEntry.toplevels[0].activate() } else { root.desktopEntry.execute() } } middleClickAction: () => { if (root.desktopEntry) { desktopEntry.execute() } } altAction: () => { root.hoverPreviewDismissed() root.hoverTimer.stop() contextMenu.active = true; } // Active indicator Rectangle { id: activeIndicator opacity: root.hasWindows ? 1 : 0 anchors { horizontalCenter: root.background.horizontalCenter bottom: root.background.bottom bottomMargin: 1 } implicitWidth: root.active ? 16 : 6 implicitHeight: 3 radius: height / 2 color: root.active ? Looks.colors.accent : Looks.colors.accentUnfocused Behavior on implicitWidth { animation: Looks.transition.enter.createObject(this) } Behavior on color { animation: Looks.transition.color.createObject(this) } Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } } BarToolTip { extraVisibleCondition: root.shouldShowTooltip && !root.hasWindows text: desktopEntry ? desktopEntry.name : appEntry.appId } BarMenu { id: contextMenu noSmoothClosing: false // On the real thing this is always smooth model: [ ...((root.desktopEntry?.actions.length > 0) ? root.desktopEntry.actions.map(action =>({ iconName: action.icon, text: action.name, action: () => { action.execute() } })).concat({ type: "separator" }) : []), { iconName: root.iconName, text: root.desktopEntry ? root.desktopEntry.name : StringUtils.toTitleCase(appEntry.appId), monochromeIcon: false, action: () => { if (root.desktopEntry) { root.desktopEntry.execute() } } }, { iconName: root.appEntry.pinned ? "pin-off" : "pin", text: root.appEntry.pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"), action: () => { TaskbarApps.togglePin(root.appEntry.appId); } }, ...(root.appEntry.toplevels.length > 0 ? [{ iconName: "dismiss", text: root.multiple ? Translation.tr("Close all windows") : Translation.tr("Close window"), action: () => { for (let toplevel of root.appEntry.toplevels) { toplevel.close(); } } }] : []), ] } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml ================================================ import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import Quickshell PopupWindow { id: root ///////////////////// Properties //////////////////// required property bool tasksHovered property var appEntry property Item anchorItem //////////////////// Functions //////////////////// function close() { // Closing doesn't animate, not sure if they're just lazy or it's intentional marginBehavior.enabled = false; root.visible = false; } function open() { marginBehavior.enabled = true; root.visible = true; } function show(appEntry: var, button: Item) { root.appEntry = appEntry; root.anchorItem = button; root.anchor.updateAnchor(); root.open(); } ///////////////////// Internals ///////////////////// readonly property bool bottom: Config.options.waffles.bar.bottom property real visualMargin: 12 property real ambientShadowWidth: 1 visible: false color: "transparent" implicitWidth: contentItem.implicitWidth + ambientShadowWidth + (visualMargin * 2) implicitHeight: contentItem.implicitHeight + ambientShadowWidth + (visualMargin * 2) anchor { adjustment: PopupAdjustment.Slide item: root.anchorItem gravity: bottom ? Edges.Top : Edges.Bottom edges: bottom ? Edges.Top : Edges.Bottom } Timer { interval: 250 running: root.visible && !hoverChecker.containsMouse && !root.tasksHovered onTriggered: { root.close(); } } // Content MouseArea { id: hoverChecker anchors.fill: parent hoverEnabled: true // Shadow WAmbientShadow { target: contentItem } Rectangle { id: contentItem property real sourceEdgeMargin: root.visible ? (root.ambientShadowWidth + root.visualMargin) : -root.implicitHeight Behavior on sourceEdgeMargin { id: marginBehavior animation: Looks.transition.enter.createObject(this) } anchors { left: parent.left right: parent.right top: root.bottom ? undefined : parent.top bottom: root.bottom ? parent.bottom : undefined margins: root.ambientShadowWidth + root.visualMargin // Opening anim bottomMargin: root.bottom ? sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin) topMargin: root.bottom ? (root.ambientShadowWidth + root.visualMargin) : sourceEdgeMargin } color: Looks.colors.bg1Base radius: Looks.radius.large layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: contentItem.width height: contentItem.height radius: contentItem.radius } } // Testing implicitHeight: Math.min(158, windowsRow.implicitHeight) implicitWidth: windowsRow.implicitWidth RowLayout { id: windowsRow anchors.fill: parent Repeater { model: ScriptModel { values: root.appEntry?.toplevels ?? [] } delegate: WindowPreview { required property var modelData toplevel: modelData } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.waffle.looks MouseArea { id: root Layout.fillHeight: true implicitHeight: appRow.implicitHeight implicitWidth: appRow.implicitWidth hoverEnabled: true function showPreviewPopup(appEntry, button) { previewPopup.show(appEntry, button); } Behavior on implicitWidth { animation: Looks.transition.move.createObject(this) } WListView { id: appRow anchors { top: parent.top bottom: parent.bottom } orientation: Qt.Horizontal spacing: 0 implicitWidth: contentWidth clip: true interactive: false // TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow? model: ScriptModel { objectProp: "appId" values: TaskbarApps.apps.filter(app => app.appId !== "SEPARATOR") } delegate: TaskAppButton { required property var modelData appEntry: modelData onHoverPreviewRequested: { root.showPreviewPopup(appEntry, this); } onHoverPreviewDismissed: { previewPopup.close(); } } } // Previews popup TaskPreview { id: previewPopup tasksHovered: root.containsMouse anchor.window: root.QsWindow.window } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.bar import Quickshell import Quickshell.Wayland Button { id: root required property var toplevel property real previewWidthConstraint: 200 property real previewHeightConstraint: 110 padding: 5 Layout.fillHeight: true onClicked: { root.toplevel.activate(); // TODO: make this work with those who disable focus on activate because telegram is abusive } background: Rectangle { id: background radius: Looks.radius.medium color: root.down ? Looks.colors.bg2Active : (root.hovered ? Looks.colors.bg2Hover : ColorUtils.transparentize(Looks.colors.bg2)) Behavior on color { animation: Looks.transition.color.createObject(this) } } contentItem: ColumnLayout { id: contentItem anchors.fill: parent anchors.margins: root.padding spacing: 5 RowLayout { Layout.fillWidth: true Layout.fillHeight: false spacing: 8 WAppIcon { id: appIcon Layout.leftMargin: Looks.radius.large - root.padding + 2 Layout.alignment: Qt.AlignVCenter iconName: AppSearch.guessIcon(root.toplevel.appId) implicitSize: 16 } Item { id: appTitleContainer Layout.fillWidth: true Layout.fillHeight: true implicitHeight: closeButton.implicitHeight // Enforce height, because closeButton doesn't contribute when it's invisible WText { id: appTitleText anchors.fill: parent text: root.toplevel.title elide: Text.ElideRight font.pixelSize: Looks.font.pixelSize.large font.weight: Looks.font.weight.thin color: Looks.colors.fg1 } } WindowCloseButton { id: closeButton } } Item { Layout.fillWidth: true Layout.fillHeight: true Layout.margins: Looks.radius.large - root.padding Layout.topMargin: 0 implicitWidth: Math.max(screencopyView.implicitWidth, 80) implicitHeight: screencopyView.implicitHeight ScreencopyView { id: screencopyView anchors.centerIn: parent captureSource: root.toplevel live: true paintCursor: true constraintSize: Qt.size(root.previewWidthConstraint, root.previewHeightConstraint) } } } component WindowCloseButton: CloseButton { visible: root.hovered Layout.leftMargin: 4 implicitHeight: 30 implicitWidth: 30 radius: Looks.radius.large - root.padding onClicked: { root.toplevel.close(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Qt.labs.synchronizer import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.bar RowLayout { id: root property bool overflowOpen: false property bool dragging: false Layout.fillHeight: true spacing: 0 BarIconButton { id: overflowButton visible: (TrayService.unpinnedItems.length > 0 || root.dragging) checked: root.overflowOpen iconName: "chevron-down" iconMonochrome: true iconRotation: (Config.options.waffles.bar.bottom ? 180 : 0) + (root.overflowOpen ? 180 : 0) Behavior on iconRotation { animation: Looks.transition.rotate.createObject(this) } onClicked: { root.overflowOpen = !root.overflowOpen; } TrayOverflowMenu { id: trayOverflowLayout Synchronizer on active { property alias source: root.overflowOpen } } BarToolTip { extraVisibleCondition: overflowButton.shouldShowTooltip text: Translation.tr("Show hidden icons") } DropArea { id: pinDropArea anchors.fill: parent property bool willPin: false onEntered: willPin = true onExited: willPin = false } } Repeater { model: ScriptModel { values: TrayService.pinnedItems } delegate: TrayButton { id: trayButton required property var modelData item: modelData property real initialX property real initialY MouseArea { id: dragArea anchors.fill: parent drag.target: parent drag.axis: Drag.XAxis drag.threshold: 2 onPressed: event => { trayButton.Drag.hotSpot.x = event.x; trayButton.initialX = trayButton.x; root.dragging = true; trayButton.Drag.active = true; } onPositionChanged: { pinTooltip.updateAnchor(); } onReleased: { if (!dragArea.drag.active) { trayButton.click(); } else { if (pinDropArea.containsDrag && pinDropArea.willPin) { // Quickshell would crash if we don't hide this item first. Took me fucking 3 hours to figure out... trayButton.visible = false; TrayService.togglePin(trayButton.item.id); pinDropArea.willPin = false; } else { trayButton.x = trayButton.initialX; } } trayButton.Drag.active = false; root.dragging = false; } } BarToolTip { id: pinTooltip extraVisibleCondition: trayButton.Drag.active && pinDropArea.containsDrag && pinDropArea.willPin horizontalPadding: 6 verticalPadding: 6 realContentItem: FluentIcon { anchors.centerIn: parent icon: "pin-off" implicitSize: 18 } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.SystemTray import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.bar BarIconButton { id: root required property SystemTrayItem item property alias menuOpen: menu.visible readonly property bool barAtBottom: Config.options.waffles.bar.bottom iconSource: item.icon iconScale: 0 Component.onCompleted: { root.iconScale = 1 } Behavior on iconScale { animation: Looks.transition.enter.createObject(this) } onClicked: { item.activate(); } altAction: () => { if (item.hasMenu) menu.open() } // This is lazy, but it's not like tray menus on Windoes are consistent... // TODO: Figure out how to do cascading menus then use a custom menu QsMenuAnchor { id: menu menu: root.item.menu anchor { adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX item: root gravity: root.barAtBottom ? Edges.Top : Edges.Bottom edges: root.barAtBottom ? Edges.Top : Edges.Bottom } } BarToolTip { extraVisibleCondition: root.shouldShowTooltip && !root.Drag.active text: TrayService.getTooltipForItem(root.item) } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayOverflowMenu.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.bar BarPopup { id: root closeOnFocusLost: false onFocusCleared: { const hasMenuOpen = contentItem.children.some(c => (c.menuOpen)); if (!hasMenuOpen) root.close(); else root.grabFocus(); } contentItem: Item { id: contentItem anchors.centerIn: parent implicitWidth: contentGrid.implicitWidth implicitHeight: contentGrid.implicitHeight GridLayout { id: contentGrid anchors.centerIn: parent rows: Math.floor(Math.sqrt(TrayService.unpinnedItems.length)) columns: Math.ceil(TrayService.unpinnedItems.length / rows) columnSpacing: 0 rowSpacing: 0 Repeater { model: ScriptModel { values: TrayService.unpinnedItems onValuesChanged: { root.updateAnchor(); if (values.length === 0) { root.close(); } } } delegate: TrayButton { id: trayButton required property var modelData item: modelData topInset: 0 bottomInset: 0 implicitWidth: 40 implicitHeight: 40 colBackground: ColorUtils.transparentize(Looks.colors.bg2) colBackgroundHover: Looks.colors.bg2Hover colBackgroundActive: Looks.colors.bg2Active onMenuOpenChanged: { // The overflow menu should only be closed when the user clicks outside // However the focus grab refuses to reactivate, so we can't have that // But most of the time the user dismisses the menu by clicking outside anyway, // so this is acceptable. if (!menuOpen) { root.close(); } } property real initialX property real initialY Behavior on x { animation: Looks.transition.move.createObject(this) } Behavior on y { animation: Looks.transition.move.createObject(this) } MouseArea { id: dragArea anchors.fill: parent drag.target: parent drag.threshold: 2 onPressed: event => { trayButton.Drag.hotSpot.x = event.x; trayButton.Drag.hotSpot.y = event.y; trayButton.initialX = trayButton.x; trayButton.initialY = trayButton.y; trayButton.Drag.active = true; } onReleased: { if (!dragArea.drag.active) { trayButton.click(); } else { if (!unpinDropArea.containsDrag && unpinDropArea.willUnpin) { // Quickshell would crash if we don't hide this item first. Took me fucking 3 hours to figure out... trayButton.visible = false; TrayService.togglePin(trayButton.item.id); unpinDropArea.willUnpin = false; } else { trayButton.x = trayButton.initialX; trayButton.y = trayButton.initialY; } } trayButton.Drag.active = false; } } } } } DropArea { id: unpinDropArea anchors.fill: parent property bool willUnpin: false onEntered: willUnpin = false onExited: willUnpin = true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/lock/WaffleLock.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.common.panels.lock import qs.modules.waffle.looks import qs.modules.waffle.sessionScreen as SessionScreen LockScreen { id: root property bool passwordView: false lockSurface: Item { id: lockSurfaceItem Component.onCompleted: { root.passwordView = false; lockSurfaceItem.forceActiveFocus(); } Keys.onPressed: { interactables.switchToFocusedView(); } StyledImage { id: bg z: 0 width: parent.width height: parent.height onStatusChanged: { if (status === Image.Ready) { print("Lock wallpaper loaded"); print(lockSurfaceItem.height); y = -lockSurfaceItem.height; openAnim.restart(); } } source: Config.options.background.wallpaperPath fillMode: Image.PreserveAspectCrop PropertyAnimation { id: openAnim target: bg property: "y" to: 0 duration: 350 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } GaussianBlur { z: 1 anchors.fill: bg source: bg radius: 100 samples: radius * 2 + 1 scale: root.passwordView ? 1.1 : 1 opacity: root.passwordView ? 1 : 0 Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } Behavior on scale { NumberAnimation { duration: 400 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } } Interactables { id: interactables z: 2 anchors.fill: bg } } component Interactables: Rectangle { id: interactablesComponent color: ColorUtils.transparentize("#000000", 0.8) // Button { // onClicked: { // root.context.unlocked(LockContext.ActionEnum.Unlock); // GlobalStates.screenLocked = false; // } // text: "woah it doesnt work let me out pls uwu colon three" // } function switchToFocusedView() { switchToPasswordViewAnim.restart(); } SequentialAnimation { id: switchToPasswordViewAnim PropertyAnimation { target: unfocusedContent property: "y" from: 0 to: -height * 1.1 duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } ScriptAction { script: { root.passwordView = true; } } } Item { id: unfocusedContent width: parent.width height: parent.height visible: !root.passwordView ClockTextGroup { anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: interactablesComponent.height * 0.1 } } RowLayout { anchors { bottom: parent.bottom right: parent.right bottomMargin: 21 rightMargin: 31 } IconIndicator { baseIcon: "wifi-1" icon: WIcons.internetIcon } IconIndicator { baseIcon: WIcons.batteryIcon icon: WIcons.batteryLevelIcon } } } Item { id: focusedContent anchors.fill: parent visible: root.passwordView PasswordGroup { visible: root.passwordView anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } } RowLayout { visible: root.passwordView anchors { bottom: parent.bottom right: parent.right bottomMargin: 21 rightMargin: 31 } SessionScreen.PowerButton { id: powerButton } } } } component IconIndicator: Item { id: iconIndicator required property string baseIcon required property string icon default property alias indicatorData: iconWidget.data implicitWidth: 40 implicitHeight: 40 FluentIcon { id: iconWidget anchors.centerIn: parent icon: iconIndicator.baseIcon color: Looks.darkColors.inactiveIcon implicitSize: 20 FluentIcon { anchors.fill: parent icon: iconIndicator.icon } } } component ClockTextGroup: Column { id: clockTextGroup spacing: -3 WText { anchors.horizontalCenter: parent.horizontalCenter color: Looks.darkColors.fg font.pixelSize: 133 font.weight: Looks.font.weight.strong text: { // Don't take am/pm // Match groups of digits separated by non-digit chars (e.g., "12:34", "12.34", "12-34") let match = DateTime.time.match(/(\d{1,2})\D+(\d{2})/); return match ? `${match[1]}${DateTime.time.match(/\D+/)[0]}${match[2]}` : DateTime.time; } } WText { id: dateLabel color: Looks.darkColors.fg anchors.horizontalCenter: parent.horizontalCenter font.pixelSize: 28 font.weight: Looks.font.weight.strong text: DateTime.collapsedCalendarFormat } } component PasswordGroup: ColumnLayout { id: passwordGroup spacing: 15 WUserAvatar { Layout.alignment: Qt.AlignHCenter sourceSize: Qt.size(192, 192) } WText { Layout.alignment: Qt.AlignHCenter text: SystemInfo.username color: Looks.darkColors.fg font.pixelSize: 26 font.weight: Looks.font.weight.strong } Rectangle { id: passwordInputWrapper Layout.topMargin: 10 Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 132 color: "transparent" implicitWidth: 296 implicitHeight: 36 border.width: 2 border.color: Looks.applyContentTransparency(Looks.darkColors.bg1Border) radius: Looks.radius.medium Rectangle { id: passwordInputBackground anchors.fill: parent anchors.margins: 2 radius: Looks.radius.small + 1 color: passwordInput.focus ? Looks.applyBackgroundTransparency(Looks.darkColors.bg1Base) : Looks.applyContentTransparency(Looks.darkColors.bg1) RowLayout { anchors.fill: parent anchors.margins: 6 spacing: 3 WTextInput { id: passwordInput Layout.fillHeight: true Layout.fillWidth: true verticalAlignment: TextInput.AlignVCenter inputMethodHints: Qt.ImhSensitiveData echoMode: passwordVisibilityButton.pressed ? TextInput.Normal : TextInput.Password color: Looks.darkColors.fg font.pixelSize: 12 WText { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter visible: passwordInput.text.length === 0 text: Translation.tr("Password") font.pixelSize: Looks.font.pixelSize.large color: Looks.darkColors.fg opacity: 0.8 } onTextChanged: root.context.currentText = this.text onAccepted: { root.context.tryUnlock(); } Connections { target: root.context function onCurrentTextChanged() { passwordInput.text = root.context.currentText; } } Connections { target: root function onPasswordViewChanged() { passwordInput.forceActiveFocus(); } } Keys.onPressed: event => { root.context.resetClearTimer(); } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton cursorShape: Qt.IBeamCursor } } PasswordBoxButton { id: passwordVisibilityButton property bool passwordVisible: false visible: passwordInput.text.length > 0 onPressed: passwordVisible = true onReleased: passwordVisible = false icon.name: passwordVisible ? "eye-off" : "eye" } PasswordBoxButton { onClicked: { root.context.tryUnlock(); } icon.name: "arrow-right" } } } Rectangle { id: activeIndicatorLine anchors { left: parent.left right: parent.right bottom: parent.bottom } implicitHeight: 2 color: passwordInput.focus ? Looks.colors.accent : Looks.applyContentTransparency(Looks.darkColors.bg2Border) } layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: passwordInputWrapper.width height: passwordInputWrapper.height radius: passwordInputWrapper.radius } } } Item {} } component PasswordBoxButton: WButton { id: pwBoxBtn implicitWidth: 28 implicitHeight: 22 property color colBackground: ColorUtils.transparentize(Looks.darkColors.bg1) property color colBackgroundHover: ColorUtils.transparentize(Looks.darkColors.bg2Hover) property color colBackgroundActive: ColorUtils.transparentize(Looks.darkColors.bg2Active) fgColor: checked ? Looks.colors.accentFg : Looks.darkColors.fg checked: hovered contentItem: Item { FluentIcon { color: pwBoxBtn.fgColor anchors.centerIn: parent icon: pwBoxBtn.icon.name implicitSize: 16 } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/AcrylicButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks WButton { id: root colBackground: Looks.colors.bg1 colBackgroundHover: Looks.colors.bg1Hover colBackgroundActive: Looks.colors.bg1Active property color colBackgroundBorder property color color property alias border: background.border property alias shinyColor: background.borderColor colBackgroundBorder: ColorUtils.transparentize(color, (root.checked || root.hovered) ? Looks.backgroundTransparency : 0) color: { if (root.down) { return root.colBackgroundActive } else if ((root.hovered && !root.down) || root.checked) { return root.colBackgroundHover } else { return root.colBackground } } background: AcrylicRectangle { id: background shiny: ((root.hovered && !root.down) || root.checked) color: root.color radius: Looks.radius.medium border.width: 1 border.color: root.colBackgroundBorder Behavior on border.color { animation: Looks.transition.color.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/AcrylicRectangle.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Rectangle { id: root property bool shiny: true // Top border property color borderColor: ColorUtils.transparentize(Looks.colors.bg1Hover, 0.7) property color internalBorderColor: ColorUtils.transparentize(borderColor, shiny ? 0.0 : 1) color: Looks.colors.bg1Hover radius: Looks.radius.medium Behavior on color { animation: Looks.transition.color.createObject(this) } Behavior on internalBorderColor { animation: Looks.transition.color.createObject(this) } onInternalBorderColorChanged: { borderCanvas.requestPaint(); } // 1px border at the top or bottom Canvas { id: borderCanvas anchors.fill: parent // For dark mode we have a shiny top border, and for light mode we have sort of a shadow rotation: Looks.dark ? 0 : 180 onPaint: { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); var borderColor = root.internalBorderColor; var r = root.radius; var fadeLength = Math.max(1, r); var fadeLengthPercent = fadeLength / width; // Compute normalized stops var leftFadeStop = fadeLengthPercent; var rightFadeStop = 1 - fadeLengthPercent; var grad = ctx.createLinearGradient(0, 0, width, 0); grad.addColorStop(0, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0)); grad.addColorStop(leftFadeStop, borderColor); grad.addColorStop(rightFadeStop, borderColor); grad.addColorStop(1, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0)); ctx.strokeStyle = grad; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(r, 0.5); ctx.lineTo(width - r, 0.5); // Top-right curve ctx.arcTo(width, 0.5, width, r + 0.5, r); // Top-left curve ctx.moveTo(width - r, 0.5); ctx.arcTo(0, 0.5, 0, r + 0.5, r); ctx.stroke(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/BodyRectangle.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Rectangle { Layout.fillHeight: true Layout.fillWidth: true color: Looks.colors.bgPanelBody } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.bar import Quickshell Button { id: reusableCloseButton implicitHeight: 30 implicitWidth: 30 property alias radius: closeButtonBg.radius Rectangle { z: 0 color: "transparent" anchors.fill: closeButtonBg anchors.margins: -1 opacity: closeButtonBg.opacity border.width: 1 radius: closeButtonBg.radius + 1 border.color: Looks.colors.bg2Border } background: Rectangle { id: closeButtonBg z: 1 opacity: reusableCloseButton.hovered ? 1 : 0 color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } Behavior on color { animation: Looks.transition.color.createObject(this) } } contentItem: FluentIcon { z: 2 anchors.centerIn: parent icon: "dismiss" implicitSize: 10 } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/FluentIcon.qml ================================================ import QtQuick import org.kde.kirigami as Kirigami import qs.modules.common import qs.modules.waffle.looks Kirigami.Icon { id: root required property string icon property bool filled: false property alias monochrome: root.isMask // Should be 16, but it appears the icons have some padding, // Unlike the Windows-only Segoe UI icons, the open source FluentUI ones are hella small property int implicitSize: 20 implicitWidth: implicitSize implicitHeight: implicitSize source: icon === "" ? "" : `${Looks.iconsPath}/${root.icon}${filled ? "-filled" : ""}.svg` fallback: root.icon roundToIconSize: false color: Looks.colors.fg isMask: true animated: true } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/FooterRectangle.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Rectangle { Layout.fillHeight: false Layout.fillWidth: true color: "transparent" implicitWidth: 358 implicitHeight: 47 } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml ================================================ pragma ComponentBehavior: Bound pragma Singleton import QtQuick import Quickshell import qs.modules.common import qs.modules.common.functions Singleton { id: root property QtObject darkColors property QtObject lightColors property QtObject colors property QtObject radius property QtObject font property QtObject transition property string iconsPath: `${Directories.assetsPath}/icons/fluent` property bool dark: Appearance.m3colors.darkmode readonly property bool transparencyEnabled: Config.options.appearance.transparency.enable property real backgroundTransparency: transparencyEnabled ? 0.16 : 0 property real panelBackgroundTransparency: transparencyEnabled ? 0.14 : 0 property real panelLayerTransparency: root.dark ? 0.9 : 0.7 property real contentTransparency: root.dark ? 0.87 : 0.5 function applyBackgroundTransparency(col) { return ColorUtils.applyAlpha(col, 1 - root.backgroundTransparency) } function applyContentTransparency(col) { return ColorUtils.applyAlpha(col, 1 - root.contentTransparency) } lightColors: QtObject { id: lightColors property color bgPanelBody: "#F2F2F2" property color bgPanelSeparator: "#E0E0E0" property color bg0: "#EEEEEE" property color bg0Border: '#BEBEBE' property color bg1Base: "#F7F7F7" property color bg1: "#F7F7F7" property color bg1Hover: "#F7F7F7" property color bg1Active: '#EFEFEF' property color bg1Border: '#E9E9E9' property color bg2: "#FBFBFB" property color bg2Base: "#FBFBFB" property color bg2Hover: '#ffffff' property color bg2Active: '#eeeeee' property color bg2Border: '#E0E0E0' property color subfg: "#5C5C5C" property color fg: "#000000" property color fg1: "#626262" property color inactiveIcon: "#C4C4C4" property color controlBgInactive: '#555458' property color controlBg: '#807F85' property color controlBgHover: '#57575B' property color controlFg: "#FFFFFF" property color accentUnfocused: "#848484" property color link: "#235CCF" property color inputBg: ColorUtils.transparentize(bg0, 0.4) } darkColors: QtObject { id: darkColors property color bgPanelBody: '#242424' property color bgPanelSeparator: "#191919" property color bg0: "#1C1C1C" property color bg0Border: "#404040" property color bg1Base: '#2C2C2C' property color bg1: '#2C2C2C' property color bg1Hover: "#292929" property color bg1Active: '#252525' property color bg1Border: '#bebebe' property color bg2Base: "#313131" property color bg2: '#313131' property color bg2Hover: '#363636' property color bg2Active: '#2B2B2B' property color bg2Border: '#404040' property color subfg: "#CED1D7" property color fg: "#FFFFFF" property color fg1: "#D1D1D1" property color inactiveIcon: "#494949" property color controlBgInactive: "#CDCECF" property color controlBg: "#9B9B9B" property color controlBgHover: "#CFCED1" property color controlFg: "#454545" property color accentUnfocused: "#989898" property color link: "#A7C9FC" property color inputBg: ColorUtils.transparentize(darkColors.bg0, 0.5) } colors: QtObject { id: colors // Special property color shadow: ColorUtils.transparentize('#161616', 0.62) property color ambientShadow: ColorUtils.transparentize("#000000", 0.75) property color bgPanelFooterBase: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 property color bgPanelFooterBackground: ColorUtils.transparentize(root.dark ? root.darkColors.bg0 : root.lightColors.bg0, root.panelBackgroundTransparency) property color bgPanelFooter: ColorUtils.transparentize(bgPanelFooterBackground, root.panelLayerTransparency) property color bgPanelBodyBase: root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody property color bgPanelBody: ColorUtils.solveOverlayColor(bgPanelFooterBackground,bgPanelBodyBase, 1 - root.panelLayerTransparency) property color bgPanelSeparator: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, 1 - root.panelBackgroundTransparency) // Layer 0 property color bg0Base: root.dark ? root.darkColors.bg0 : root.lightColors.bg0 property color bg0: ColorUtils.transparentize(bg0Base, root.backgroundTransparency) property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency) // Layer 1 property color bg1Base: root.dark ? root.darkColors.bg1 : root.lightColors.bg1 property color bg1: ColorUtils.solveOverlayColor(bg0Base, bg1Base, 1 - root.contentTransparency) property color bg1Hover: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, 1 - root.contentTransparency) property color bg1Active: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, 1 - root.contentTransparency) property color bg1Border: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, 1 - root.contentTransparency) // Layer 2 property color bg2Base: root.dark ? root.darkColors.bg2 : root.lightColors.bg2 property color bg2: ColorUtils.solveOverlayColor(bgPanelBodyBase, bg2Base, 1 - root.contentTransparency) property color bg2Hover: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, 1 - root.contentTransparency) property color bg2Active: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, 1 - root.contentTransparency) property color bg2Border: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Border : root.lightColors.bg2Border, 1 - root.contentTransparency) // Foreground / Text property color subfg: root.dark ? root.darkColors.subfg : root.lightColors.subfg property color fg: root.dark ? root.darkColors.fg : root.lightColors.fg property color fg1: root.dark ? root.darkColors.fg1 : root.lightColors.fg1 property color inactiveIcon: root.dark ? root.darkColors.inactiveIcon : root.lightColors.inactiveIcon property color link: root.dark ? root.darkColors.link : root.lightColors.link // Controls property color controlBgInactive: root.dark ? root.darkColors.controlBgInactive : root.lightColors.controlBgInactive property color controlBg: root.dark ? root.darkColors.controlBg : root.lightColors.controlBg property color controlBgHover: root.dark ? root.darkColors.controlBgHover : root.lightColors.controlBgHover property color controlFg: root.dark ? root.darkColors.controlFg : root.lightColors.controlFg property color inputBg: root.dark ? root.darkColors.inputBg : root.lightColors.inputBg property color danger: "#C42B1C" property color dangerActive: "#B62D1F" property color warning: "#FF9900" // Accent property color accent: Appearance.colors.colPrimary property color accentHover: Appearance.colors.colPrimaryHover property color accentActive: Appearance.colors.colPrimaryActive property color accentUnfocused: root.dark ? root.darkColors.accentUnfocused : root.lightColors.accentUnfocused property color accentFg: ColorUtils.isDark(accent) ? "#FFFFFF" : "#000000" property color selection: Appearance.colors.colPrimaryContainer property color selectionFg: Appearance.colors.colOnPrimaryContainer } radius: QtObject { id: radius property int none: 0 property int small: 2 property int medium: 4 property int large: 8 property int xLarge: 12 } font: QtObject { id: font property QtObject family: QtObject { property string ui: "Noto Sans" } property QtObject weight: QtObject { // Noto is not Segoe, so we might use slightly different weights property int thin: Font.Normal property int regular: Font.Medium property int strong: Font.DemiBold property int stronger: (Font.DemiBold + 2*Font.Bold) / 3 property int strongest: Font.Bold } property QtObject pixelSize: QtObject { property real normal: 11 property real large: 13 property real larger: 15 property real xlarger: 17 } property QtObject variableAxes: QtObject { property var ui: ({ "wdth": 25 }) } } transition: QtObject { id: transition property int velocity: 850 property QtObject easing: QtObject { property QtObject bezierCurve: QtObject { readonly property list easeInOut: [0.42,0.00,0.58,1.00,1,1] readonly property list easeIn: [0,1,1,1,1,1] readonly property list easeOut: [1,0,1,1,1,1] } } property Component color: Component { ColorAnimation { duration: 80 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component opacity: Component { NumberAnimation { duration: 120 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component resize: Component { // TODO: better curve needed NumberAnimation { duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component enter: Component { NumberAnimation { duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component exit: Component { NumberAnimation { duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeOut } } property Component move: Component { NumberAnimation { duration: 170 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeInOut } } property Component rotate: Component { NumberAnimation { duration: 170 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeInOut } } property Component anchor: Component { AnchorAnimation { duration: 160 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component longMovement: Component { NumberAnimation { duration: 1000 easing.type: Easing.BezierSpline easing.bezierCurve: transition.easing.bezierCurve.easeIn } } property Component scroll: Component { NumberAnimation { duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: [0.0, 0.0, 0.25, 1.0, 1, 1] } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml ================================================ pragma ComponentBehavior: Bound import Qt.labs.synchronizer import QtQuick import QtQuick.Layouts import QtQuick.Controls import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks Column { id: root property bool showArrows: true property int currentIndex: 0 property int count: 1 signal clicked(int index) signal increasePage() signal decreasePage() visible: count > 1 spacing: 6 NavigationArrow { visible: root.showArrows down: false } Repeater { model: root.count delegate: MouseArea { id: pageIndicator required property int index hoverEnabled: true onClicked: root.clicked(index); anchors.horizontalCenter: parent.horizontalCenter implicitWidth: 6 implicitHeight: 6 Circle { anchors.centerIn: parent diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4 color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg } } } NavigationArrow { visible: root.showArrows down: true } component NavigationArrow: FluentIcon { id: navArrow required property bool down anchors.horizontalCenter: parent.horizontalCenter implicitHeight: 12 implicitWidth: 12 - (2 * upArea.containsPress) icon: down ? "caret-down" : "caret-up" color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg filled: true opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0 MouseArea { id: upArea anchors.fill: parent hoverEnabled: true onClicked: navArrow.down ? root.increasePage() : root.decreasePage(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Rectangle { id: root required property var target z: 0 anchors { fill: target margins: -border.width } border.color: Looks.colors.ambientShadow border.width: 1 color: "transparent" radius: target.radius + border.width } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml ================================================ import QtQuick import org.kde.kirigami as Kirigami import qs.services import qs.modules.common Kirigami.Icon { id: root required property string iconName property bool separateLightDark: false property bool tryCustomIcon: true property real implicitSize: 26 implicitWidth: implicitSize implicitHeight: implicitSize animated: true roundToIconSize: false fallback: root.iconName source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback color: Looks.colors.fg } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks Item { id: root signal closed required property Item contentItem property real visualMargin: 12 property int closeAnimDuration: 150 property bool revealFromSides: false property bool revealFromLeft: true function close() { closeAnim.start(); } readonly property bool barAtBottom: Config.options.waffles.bar.bottom implicitHeight: contentItem.implicitHeight + visualMargin * 2 implicitWidth: contentItem.implicitWidth + visualMargin * 2 focus: true Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { content.close(); } } Item { id: panelContent anchors { left: (root.revealFromSides && !root.revealFromLeft) ? undefined : parent.left right: (root.revealFromSides && root.revealFromLeft) ? undefined : parent.right top: (!root.revealFromSides && root.barAtBottom) ? undefined : parent.top bottom: (!root.revealFromSides && !root.barAtBottom) ? undefined : parent.bottom // Opening anim bottomMargin: (!root.revealFromSides && root.barAtBottom) ? sourceEdgeMargin : root.visualMargin topMargin: (!root.revealFromSides && !root.barAtBottom) ? sourceEdgeMargin : root.visualMargin leftMargin: (root.revealFromSides && root.revealFromLeft) ? sideEdgeMargin : root.visualMargin rightMargin: (root.revealFromSides && !root.revealFromLeft) ? sideEdgeMargin : root.visualMargin } Component.onCompleted: { openAnim.start(); } property real sourceEdgeMargin: -(implicitHeight + root.visualMargin) property real sideEdgeMargin: -(implicitWidth + root.visualMargin) OpenAnim { id: openAnim properties: "sourceEdgeMargin, sideEdgeMargin" } SequentialAnimation { id: closeAnim ParallelAnimation { CloseAnim { property: "sourceEdgeMargin" to: -(implicitHeight + root.visualMargin) } CloseAnim { property: "sideEdgeMargin" to: -(implicitWidth + root.visualMargin) } } ScriptAction { script: { root.closed(); } } } implicitWidth: root.contentItem.implicitWidth implicitHeight: root.contentItem.implicitHeight children: [root.contentItem] } component OpenAnim: PropertyAnimation { target: panelContent to: root.visualMargin duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } component CloseAnim: PropertyAnimation { target: panelContent duration: root.closeAnimDuration easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WBorderedButton.qml ================================================ import QtQuick import QtQuick.Controls import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks WButton { id: root colBackground: Looks.colors.bg2 colBackgroundHover: Looks.colors.bg2Hover colBackgroundActive: Looks.colors.bg2Active property color colBorder: Looks.colors.bg2Border property color colBorderToggled: Looks.colors.accent border.color: checked ? colBorderToggled : colBorder border.width: root.pressed ? 2 : 1 } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WBorderlessButton.qml ================================================ import QtQuick import QtQuick.Controls import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Button { id: root implicitHeight: 36 property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) property color colBackgroundHover: Looks.colors.bg1Hover property color colBackgroundActive: Looks.colors.bg1Active property color color property color colForeground: Looks.colors.fg color: { if (!root.enabled) return colBackground; if (root.down) { return root.colBackgroundActive } else if ((root.hovered && !root.down) || root.checked) { return root.colBackgroundHover } else { return root.colBackground } } property alias radius: background.radius background: Rectangle { id: background radius: Looks.radius.medium color: root.color } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks // Generic button with background Button { id: root property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) property color colBackgroundHover: Looks.colors.bg2Hover property color colBackgroundActive: Looks.colors.bg2Active property color colBackgroundToggled: Looks.colors.accent property color colBackgroundToggledHover: Looks.colors.accentHover property color colBackgroundToggledActive: Looks.colors.accentActive property color colForeground: Looks.colors.fg property color colForegroundToggled: Looks.colors.accentFg property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4) property alias backgroundOpacity: backgroundRect.opacity property color color: { if (!root.enabled) return colBackground; if (root.checked) { if (root.down) { return root.colBackgroundToggledActive; } else if (root.hovered) { return root.colBackgroundToggledHover; } else { return root.colBackgroundToggled; } } if (root.down) { return root.colBackgroundActive; } else if (root.hovered) { return root.colBackgroundHover; } else { return root.colBackground; } } property color fgColor: { if (!root.enabled) return root.colForegroundDisabled if (root.checked) return root.colForegroundToggled if (root.enabled) return root.colForeground return root.colForeground } property alias horizontalAlignment: buttonText.horizontalAlignment font { family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.large weight: Looks.font.weight.regular } // Hover stuff signal hoverTimedOut property bool shouldShowTooltip: false ToolTip.delay: 400 property Timer hoverTimer: Timer { id: hoverTimer running: root.hovered interval: root.ToolTip.delay onTriggered: { root.hoverTimedOut(); } } onHoverTimedOut: { root.shouldShowTooltip = true; } onHoveredChanged: { if (!root.hovered) { root.shouldShowTooltip = false; root.hoverTimer.stop(); } } property alias monochromeIcon: buttonIcon.monochrome property alias buttonSpacing: contentLayout.spacing property bool forceShowIcon: false property var altAction: () => {} property var middleClickAction: () => {} property real inset: 0 topInset: inset bottomInset: inset leftInset: inset rightInset: inset property alias radius: backgroundRect.radius property alias topLeftRadius: backgroundRect.topLeftRadius property alias topRightRadius: backgroundRect.topRightRadius property alias bottomLeftRadius: backgroundRect.bottomLeftRadius property alias bottomRightRadius: backgroundRect.bottomRightRadius property alias border: backgroundRect.border horizontalPadding: 10 verticalPadding: 6 implicitHeight: contentItem.implicitHeight + verticalPadding * 2 + topInset + bottomInset implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 + leftInset + rightInset background: Rectangle { id: backgroundRect radius: Looks.radius.medium color: root.color Behavior on color { animation: Looks.transition.color.createObject(this) } } MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.MiddleButton onClicked: event => { if (event.button === Qt.LeftButton) root.clicked(); if (event.button === Qt.RightButton) root.altAction(); if (event.button === Qt.MiddleButton) root.middleClickAction(); } } contentItem: Item { anchors { fill: parent margins: root.inset } implicitWidth: contentLayout.implicitWidth implicitHeight: contentLayout.implicitHeight RowLayout { id: contentLayout anchors { fill: parent leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding } spacing: 12 FluentIcon { id: buttonIcon monochrome: true implicitSize: 18 Layout.leftMargin: root.iconLeftMargin Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter icon: root.icon.name color: root.fgColor visible: root.icon.name !== "" } WText { id: buttonText Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: root.text horizontalAlignment: Text.AlignLeft font: root.font color: root.fgColor } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WButton { id: root property bool animateChoiceHighlight: true Layout.fillWidth: true implicitWidth: contentItem.implicitWidth horizontalPadding: 10 verticalPadding: 11 buttonSpacing: 8 color: { if (root.checked) { if (root.down) { return root.colBackgroundHover; } else if (root.hovered && !root.down) { return root.colBackgroundActive; } else { return root.colBackgroundHover; } } if (root.down) { return root.colBackgroundActive; } else if (root.hovered && !root.down) { return root.colBackgroundHover; } else { return root.colBackground; } } fgColor: colForeground background: Rectangle { id: backgroundRect radius: Looks.radius.medium color: root.color Behavior on color { enabled: root.animateChoiceHighlight animation: Looks.transition.color.createObject(this) } WFadeLoader { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter shown: root.checked sourceComponent: Rectangle { implicitWidth: 3 implicitHeight: 3 radius: width / 2 color: Looks.colors.accent property bool forceZeroHeight: true height: forceZeroHeight ? 0 : Math.max(16, root.background.height - 18 * 2) Component.onCompleted: { forceZeroHeight = false; } Behavior on height { enabled: root.animateChoiceHighlight animation: Looks.transition.opacity.createObject(this) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WFadeLoader.qml ================================================ import QtQuick import qs.modules.common // Yes, this is (mostly) a copy of FadeLoader. // The animation of a Behavior cannot be changed... I'd love to be proven wrong. Loader { id: root property bool shown: true property alias fade: opacityBehavior.enabled property alias animation: opacityBehavior.animation opacity: shown ? 1 : 0 visible: opacity > 0 active: opacity > 0 Behavior on opacity { id: opacityBehavior animation: Looks.transition.opacity.createObject(null) } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml ================================================ pragma Singleton import QtQuick import Quickshell import Quickshell.Services.UPower import qs.services Singleton { id: root function pathForName(iconName) { return Quickshell.shellPath(`assets/icons/fluent/${iconName}.svg`); } function wifiIconForStrength(strength) { if (strength > 75) return "wifi-1"; if (strength > 50) return "wifi-2"; if (strength > 25) return "wifi-3"; return "wifi-4"; } property string internetIcon: { if (Network.ethernet) return "ethernet"; if (Network.wifiEnabled) { const strength = Network.networkStrength; return wifiIconForStrength(strength); } if (Network.wifiStatus === "connecting") return "wifi-4"; if (Network.wifiStatus === "disconnected") return "wifi-off"; if (Network.wifiStatus === "disabled") return "wifi-off"; return "wifi-warning"; } property string batteryIcon: { if (Battery.isCharging) return "battery-charge"; if (Battery.isCriticalAndNotCharging) return "battery-warning"; if (Battery.percentage >= 0.9) return "battery-full"; return `battery-0`; } property string batteryLevelIcon: { const discreteLevel = Math.ceil(Battery.percentage * 10); return `battery-${discreteLevel > 9 ? "full" : discreteLevel}`; } property string volumeIcon: { const muted = Audio.sink?.audio.muted ?? false; const volume = Audio.sink?.audio.volume ?? 0; if (muted) return "speaker-mute"; if (volume == 0) return "speaker-none"; if (volume < 0.5) return "speaker-1"; return "speaker"; } property string micIcon: { const muted = Audio.source?.audio.muted ?? false; return muted ? "mic-off" : "mic"; } property string bluetoothIcon: BluetoothStatus.connected ? "bluetooth-connected" : BluetoothStatus.enabled ? "bluetooth" : "bluetooth-disabled" property string nightLightIcon: Hyprsunset.temperatureActive ? "weather-moon" : "weather-moon-off" property string notificationsIcon: Notifications.silent ? "alert-snooze" : "alert" property string powerProfileIcon: { switch (PowerProfiles.profile) { case PowerProfile.PowerSaver: return "leaf-two"; case PowerProfile.Balanced: return "flash-on"; case PowerProfile.Performance: return "fire"; } } function audioDeviceIcon(node) { if (!node.isSink) return "mic-on"; const monitor = /monitor|hdmi/i; const headphones = /headset|headphone|bluez|wireless/i; const speakers = /speaker|output/i; if (monitor.test(node.nickname) || monitor.test(node.description) || monitor.test(node.name)) { return "desktop-speaker"; } if (headphones.test(node.nickname) || headphones.test(node.description) || headphones.test(node.name)) { return "headphones"; } if (speakers.test(node.nickname) || speakers.test(node.description) || speakers.test(node.name)) { return "speaker"; } return "speaker"; } function audioAppIcon(node) { let icon; icon = AppSearch.guessIcon(node?.properties["application.icon-name"] ?? ""); if (AppSearch.iconExists(icon)) return icon; icon = AppSearch.guessIcon(node?.properties["node.name"] ?? ""); return icon; } function bluetoothDeviceIcon(device) { const systemIconName = device?.icon || ""; if (systemIconName.includes("headset") || systemIconName.includes("headphones")) return "headphones"; if (systemIconName.includes("audio")) return "speaker"; if (systemIconName.includes("phone")) return "phone"; if (systemIconName.includes("mouse")) return "bluetooth"; if (systemIconName.includes("keyboard")) return "keyboard"; return "bluetooth"; } function fluentFromMaterial(icon) { switch (icon) { case "calculate": return "calculator"; case "keyboard_return": return "arrow-enter-left"; case "open_in_new": return "open"; case "settings_suggest": return "wand"; case "terminal": return "app-generic"; case "travel_explore": return "globe-search"; case "keep": return "pin"; case "keep_off": return "pin-off"; default: return "apps"; } } function guessIconForName(name) { const lowerName = name.toLowerCase(); if (lowerName.includes("app") || lowerName.includes("desktop")) return "apps"; if (lowerName.includes("news")) return "news"; if (lowerName.includes("new") || lowerName.includes("create") || lowerName.includes("add")) return "add"; if (lowerName.includes("open")) return "open"; if (lowerName.includes("friends") || lowerName.includes("contact") || lowerName.includes("family")) return "people"; if (lowerName.includes("community")) return "people-team"; if (lowerName.includes("library")) return "library"; if (lowerName.includes("setting")) return "settings"; if (lowerName.includes("gallery")) return "image-copy"; if (lowerName.includes("server")) return "server"; if (lowerName.includes("picture") || lowerName.includes("photo") || lowerName.includes("image")) return "image"; if (lowerName.includes("store") || lowerName.includes("shop")) return "store-microsoft"; if (lowerName.includes("record") || lowerName.includes("capture")) return "record"; if (lowerName.includes("screen") || lowerName.includes("display") || lowerName.includes("monitor") || lowerName.includes("desktop")) return "desktop"; return "apps"; } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WIndeterminateProgressBar.qml ================================================ import QtQuick import Qt5Compat.GraphicalEffects import qs import qs.services import qs.services.network import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks StyledIndeterminateProgressBar { id: progressBar implicitHeight: 3 background: null layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: progressBar.width height: progressBar.height radius: progressBar.height / 2 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml ================================================ import qs.modules.common import qs.modules.common.widgets import QtQuick import QtQuick.Controls ListView { id: root boundsBehavior: Flickable.DragOverBounds ScrollBar.vertical: WScrollBar {} displaced: Transition { animations: [Looks.transition.enter.createObject(this, { property: "y" })] } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks Menu { id: root property bool downDirection: false property bool hasIcons: false // TODO: implement property color color: Looks.colors.bg1Base property alias backgroundPane: bgPane implicitWidth: background.implicitWidth + margins * 2 implicitHeight: background.implicitHeight + margins * 2 margins: 10 padding: 3 property real sourceEdgeMargin: -implicitHeight clip: true enter: Transition { NumberAnimation { property: "sourceEdgeMargin" from: -root.implicitHeight to: root.margins duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } exit: Transition { NumberAnimation { property: "sourceEdgeMargin" from: root.margins to: -root.implicitHeight duration: 150 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } } background: Item { id: bgItem implicitWidth: bgPane.implicitWidth implicitHeight: bgPane.implicitHeight WPane { id: bgPane anchors { left: parent.left right: parent.right top: root.downDirection ? parent.top : undefined bottom: root.downDirection ? undefined : parent.bottom margins: root.margins topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin } contentItem: Rectangle { color: root.color implicitWidth: menuListView.implicitWidth + root.padding * 2 implicitHeight: root.contentItem.implicitHeight + root.padding * 2 } } } Component.onCompleted: { menuListView.itemAtIndex(0)?.forceActiveFocus(); } contentItem: Item { implicitWidth: menuListView.implicitWidth implicitHeight: menuListView.implicitHeight WListView { id: menuListView interactive: contentHeight > height anchors { left: parent.left right: parent.right top: root.downDirection ? parent.top : undefined bottom: root.downDirection ? undefined : parent.bottom margins: root.margins // ???? topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin } clip: true implicitHeight: contentHeight implicitWidth: Array.from({ length: count }, (_, i) => itemAtIndex(i)?.implicitWidth ?? 0).reduce((a, b) => a > b ? a : b) model: root.contentModel } } delegate: WMenuItem { id: menuItemDelegate } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks MenuItem { id: root property color colBackground: ColorUtils.transparentize(Looks.colors.bg1) property color colBackgroundHover: Looks.colors.bg2Hover property color colBackgroundActive: Looks.colors.bg2Active property color colBackgroundToggled: Looks.colors.bg2Hover property color colBackgroundToggledHover: Looks.colors.bg2Active property color colBackgroundToggledActive: Looks.colors.bg2Hover property color colForeground: Looks.colors.fg property color colForegroundToggled: Looks.colors.fg property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4) property color color: { if (!root.enabled) return colBackground; if (root.checked) { if (root.down) { return root.colBackgroundToggledActive; } else if (root.hovered) { return root.colBackgroundToggledHover; } else { return root.colBackgroundToggled; } } if (root.down) { return root.colBackgroundActive; } else if (root.hovered) { return root.colBackgroundHover; } else { return root.colBackground; } } property color fgColor: { if (root.checked) return root.colForegroundToggled; if (root.enabled) return root.colForeground; return root.colForegroundDisabled; } property real inset: 2 topInset: inset bottomInset: inset leftInset: inset rightInset: inset horizontalPadding: 11 width: ListView.view?.width height: visible ? implicitHeight : 0 background: Rectangle { id: backgroundRect radius: Looks.radius.medium color: root.color Behavior on color { animation: Looks.transition.color.createObject(this) } } implicitHeight: Math.max(28, contentItem.implicitHeight) + topInset + bottomInset implicitWidth: contentItem.implicitWidth + leftInset + rightInset + leftPadding + rightPadding contentItem: Item { implicitWidth: contentLayout.implicitWidth implicitHeight: contentLayout.implicitHeight RowLayout { id: contentLayout anchors.fill: parent spacing: 12 FluentIcon { id: buttonIcon monochrome: true implicitSize: 20 Layout.fillWidth: false Layout.alignment: Qt.AlignVCenter color: root.fgColor visible: root.icon.name !== "" icon: root.icon.name } WText { id: buttonText Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft text: root.text horizontalAlignment: Text.AlignLeft font.pixelSize: Looks.font.pixelSize.large color: root.fgColor } } WFadeLoader { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: -root.leftPadding + width } shown: root.checked sourceComponent: Rectangle { implicitWidth: 3 implicitHeight: 3 radius: width / 2 color: Looks.colors.accent property bool forceZeroHeight: true height: forceZeroHeight ? 0 : Math.max(root.down ? 10 : 16, root.background.height - 18 * 2) Component.onCompleted: { forceZeroHeight = false; } Behavior on height { animation: Looks.transition.resize.createObject(this) } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WMouseAreaButton.qml ================================================ import QtQuick import qs import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks MouseArea { id: root property real radius: Looks.radius.medium hoverEnabled: true property color colBackground: ColorUtils.transparentize(Looks.colors.bg2) property color colBackgroundHover: Looks.colors.bg2Hover property color colBackgroundActive: Looks.colors.bg2Active property color colBorder: ColorUtils.transparentize(Looks.colors.bg2Border) property color colBorderHover: Looks.colors.bg2Border property color color: { if (containsMouse) { return pressed ? colBackgroundActive : colBackgroundHover; } else { return colBackground; } } property color borderColor: { if (containsMouse) { return colBorderHover; } else { return colBorder; } } property Item background: Rectangle { id: bgRect parent: root anchors.fill: parent color: root.color radius: root.radius border.color: root.borderColor border.width: 1 } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WPane.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks Item { id: root property Item contentItem property real radius: Looks.radius.large property alias color: contentRect.color property alias border: borderRect property alias borderColor: borderRect.border.color property alias borderWidth: borderRect.border.width implicitWidth: borderRect.implicitWidth implicitHeight: borderRect.implicitHeight WRectangularShadow { target: borderRect } Rectangle { id: borderRect z: 1 color: "transparent" radius: root.radius border.color: Looks.colors.bg2Border border.width: 1 implicitWidth: contentItem.implicitWidth + border.width * 2 implicitHeight: contentItem.implicitHeight + border.width * 2 anchors.fill: contentRect anchors.margins: -border.width } Rectangle { id: contentRect anchors.centerIn: parent z: 0 color: Looks.colors.bgPanelFooterBackground implicitWidth: contentItem.implicitWidth implicitHeight: contentItem.implicitHeight layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { id: contentAreaMask width: contentRect.width height: contentRect.height radius: root.radius - borderRect.border.width } } children: [root.contentItem] } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml ================================================ import QtQuick import Quickshell import qs import qs.services import qs.modules.common import qs.modules.waffle.looks import qs.modules.waffle.bar WButton { id: root property alias iconName: iconContent.icon property alias iconSize: iconContent.implicitSize property alias monochrome: iconContent.monochrome implicitWidth: 40 implicitHeight: 40 contentItem: Item { FluentIcon { id: iconContent anchors.centerIn: parent implicitSize: 18 icon: root.iconName } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WPanelPageColumn.qml ================================================ import QtQuick import QtQuick.Layouts ColumnLayout { spacing: 0 } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WPanelSeparator.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.waffle.looks Rectangle { Layout.fillHeight: false Layout.fillWidth: true color: Looks.colors.bgPanelSeparator implicitHeight: 1 } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks PopupToolTip { id: root required property Item realContentItem realContentItem: WText { text: root.text anchors.centerIn: parent } property real visualMargin: 11 verticalPadding: 8 horizontalPadding: 10 verticalMargin: visualMargin horizontalMargin: visualMargin contentItem: WToolTipContent { id: tooltipContent realContentItem: root.realContentItem horizontalPadding: root.horizontalPadding verticalPadding: root.verticalPadding } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WProgressBar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Widgets import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks ProgressBar { id: root Behavior on value { SmoothedAnimation { velocity: Looks.transition.velocity } } implicitHeight: 4 background: null contentItem: Item { id: background Rectangle { id: trackTrough anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } radius: root.implicitHeight / 2 color: Looks.colors.controlBg implicitHeight: root.implicitHeight } Rectangle { id: trackHighlight anchors { left: parent.left verticalCenter: parent.verticalCenter } radius: root.implicitHeight / 2 color: Looks.colors.accent implicitHeight: root.implicitHeight width: background.width * root.value } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadow.qml ================================================ import QtQuick import QtQuick.Effects import qs.modules.common import qs.modules.common.widgets StyledRectangularShadow { blur: 10 spread: 2 offset: Qt.vector2d(0.0, 4) color: Looks.colors.shadow } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadowThis.qml ================================================ import QtQuick import QtQuick.Effects import qs.modules.common import qs.modules.common.widgets Item { default property Item contentItem property Item shadow: WRectangularShadow { target: contentItem } implicitWidth: contentItem.implicitWidth implicitHeight: contentItem.implicitHeight children: [shadow, contentItem] } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions ScrollBar { id: root policy: ScrollBar.AsNeeded active: hovered || pressed property color color: Looks.colors.controlBg contentItem: Rectangle { implicitWidth: root.active ? 4 : 2 implicitHeight: root.visualSize radius: 9999 color: root.color opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0 Behavior on opacity { animation: Looks.transition.opacity.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell.Widgets import qs.modules.common import qs.modules.common.widgets Slider { id: root property real trackWidth: 4 property string tooltipContent: `${Math.round(value * 100)}` property bool scrollable: false stepSize: 0.02 leftPadding: 0 rightPadding: 0 implicitHeight: handle.implicitHeight Behavior on value { // This makes the adjusted value (like volume) shift smoothly SmoothedAnimation { velocity: Looks.transition.velocity } } background: MouseArea { id: background anchors.fill: parent onWheel: (event) => { if (!root.scrollable) { event.accepted = false; return; } if (event.angleDelta.y > 0) { root.value = Math.min(root.value + root.stepSize, 1) root.moved() } else { root.value = Math.max(root.value - root.stepSize, 0) root.moved() } } Rectangle { id: trackHighlight anchors { left: parent.left verticalCenter: parent.verticalCenter } topLeftRadius: root.trackWidth / 2 bottomLeftRadius: root.trackWidth / 2 color: Looks.colors.accent implicitHeight: root.trackWidth width: background.width * root.visualPosition } Rectangle { id: trackTrough anchors { right: parent.right verticalCenter: parent.verticalCenter } topRightRadius: root.trackWidth / 2 bottomRightRadius: root.trackWidth / 2 color: Looks.colors.controlBg implicitHeight: root.trackWidth width: background.width * (1 - root.visualPosition) } } handle: Circle { id: handle anchors.verticalCenter: parent.verticalCenter x: (diameter / 2) + root.visualPosition * (root.width - diameter) - (diameter / 2) diameter: 20 color: Looks.colors.controlFg MouseArea { id: handleMouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.NoButton } Circle { anchors.centerIn: parent diameter: root.pressed ? 10 : handleMouseArea.containsMouse ? 14 : 12 color: Looks.colors.accent Behavior on diameter { animation: Looks.transition.enter.createObject(this) } } WToolTip { id: tooltip extraVisibleCondition: root.pressed text: root.tooltipContent font.pixelSize: Looks.font.pixelSize.larger verticalPadding: 3 horizontalPadding: 8 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WStackView.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.waffle.looks StackView { id: root property real moveDistance: 30 property int pushDuration: 200 property int fadeDuration: 80 property list bezierCurve: Looks.transition.easing.bezierCurve.easeIn property list fadeBezierCurve: Looks.transition.easing.bezierCurve.easeInOut clip: true background: null pushEnter: Transition { XAnimator { from: -root.moveDistance to: 0 duration: root.pushDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.bezierCurve } NumberAnimation { properties: "opacity" from: 0 to: 1 duration: root.fadeDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.fadeBezierCurve } } pushExit: Transition { XAnimator { from: 0 to: root.moveDistance duration: root.pushDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.bezierCurve } NumberAnimation { properties: "opacity" from: 1 to: 0 duration: root.fadeDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.fadeBezierCurve } } popEnter: Transition { XAnimator { from: root.moveDistance to: 0 duration: root.pushDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.bezierCurve } NumberAnimation { properties: "opacity" from: 0 to: 1 duration: root.fadeDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.fadeBezierCurve } } popExit: Transition { XAnimator { from: 0 to: -root.moveDistance duration: root.pushDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.bezierCurve } NumberAnimation { properties: "opacity" from: 1 to: 0 duration: root.fadeDuration easing.type: Easing.BezierSpline easing.bezierCurve: root.fadeBezierCurve } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WSwitch.qml ================================================ import QtQuick import QtQuick.Layouts import QtQuick.Controls import qs.modules.common import qs.modules.waffle.looks Switch { id: root implicitWidth: 40 implicitHeight: 20 property real indicatorHeight: 12 property real indicatorPressedHeight: 14 property real indicatorPressedWidth: 17 property color checkedColor: Looks.colors.accent property color uncheckedColor: Looks.colors.bg1 property color borderColor: Looks.colors.controlBgInactive readonly property real indicatorPressedWidthDiff: indicatorPressedWidth - indicatorHeight background: Rectangle { width: parent.width height: parent.height radius: height / 2 color: root.checked ? root.checkedColor : root.uncheckedColor border.width: 1 border.color: root.checked ? root.checkedColor : root.borderColor Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } Behavior on border.color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } // Custom thumb styling indicator: Rectangle { implicitWidth: (root.pressed || root.down) ? root.indicatorPressedWidth : root.indicatorHeight implicitHeight: (root.pressed || root.down) ? root.indicatorPressedHeight : root.indicatorHeight radius: height / 2 color: root.checked ? Looks.colors.accentFg : root.borderColor anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: { if (root.checked) { return 24 - (root.pressed || root.down ? root.indicatorPressedWidthDiff : 0); } else { return (root.pressed || root.down) ? 3 : (Config.options.waffles.tweaks.switchHandlePositionFix ? 4 : 3); } } Behavior on anchors.leftMargin { animation: Looks.transition.enter.createObject(this) } Behavior on implicitWidth { animation: Looks.transition.resize.createObject(this) } Behavior on implicitHeight { animation: Looks.transition.resize.createObject(this) } Behavior on color { animation: Looks.transition.color.createObject(this) } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WText.qml ================================================ import QtQuick Text { id: root renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter color: Looks.colors.fg font { hintingPreference: Font.PreferDefaultHinting family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.normal weight: Looks.font.weight.regular variableAxes: Looks.font.variableAxes.ui } linkColor: Looks.colors.link } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WTextButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs.modules.waffle.looks WButton { id: root implicitHeight: 40 implicitWidth: contentItem.implicitWidth + 30 color: "transparent" contentItem: Item { id: contentItem anchors.centerIn: parent implicitWidth: buttonText.implicitWidth WText { id: buttonText anchors.centerIn: parent color: root.pressed ? Looks.colors.fg : Looks.colors.fg1 text: root.text } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml ================================================ import qs.modules.common import QtQuick import QtQuick.Controls.FluentWinUI3 import QtQuick.Controls TextField { id: root clip: true renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter color: Looks.colors.fg palette { active: Looks.colors.accent } font { hintingPreference: Font.PreferDefaultHinting family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.normal weight: Looks.font.weight.regular } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: true cursorShape: Qt.IBeamCursor } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WTextInput.qml ================================================ import QtQuick import QtQuick.Controls TextInput { id: root renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter color: Looks.colors.fg font { hintingPreference: Font.PreferFullHinting family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.large weight: Looks.font.weight.regular } selectionColor: Looks.colors.selection selectedTextColor: Looks.colors.selectionFg } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WTextWithFixedWidth.qml ================================================ import QtQuick Item { id: root property string longestText property alias text: textItem.text property alias font: textItem.font property alias horizontalAlignment: textItem.horizontalAlignment property alias verticalAlignment: textItem.verticalAlignment property alias color: textItem.color implicitWidth: longestTextMetrics.width implicitHeight: longestTextMetrics.height TextMetrics { id: longestTextMetrics text: root.longestText font { family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.large weight: Looks.font.weight.regular } } WText { id: textItem anchors.fill: parent font.pixelSize: Looks.font.pixelSize.large } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolTip.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks StyledToolTip { id: root required property Item realContentItem font { family: Looks.font.family.ui pixelSize: Looks.font.pixelSize.normal weight: Looks.font.weight.regular } realContentItem: WText { text: root.text font: root.font anchors.centerIn: parent } verticalPadding: 8 horizontalPadding: 10 delay: 400 contentItem: WToolTipContent { id: tooltipContent realContentItem: root.realContentItem horizontalPadding: root.horizontalPadding verticalPadding: root.verticalPadding } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolTipContent.qml ================================================ import QtQuick import Quickshell import qs.modules.waffle.looks Item { id: root anchors.centerIn: parent required property Item realContentItem property alias radius: realContent.radius property real verticalPadding: 8 property real horizontalPadding: 10 implicitWidth: realContent.implicitWidth + 2 * 2 implicitHeight: realContent.implicitHeight + 2 * 2 WAmbientShadow { target: realContent } Rectangle { id: realContent z: 1 anchors.centerIn: parent implicitWidth: root.realContentItem.implicitWidth + root.horizontalPadding * 2 implicitHeight: root.realContentItem.implicitHeight + root.verticalPadding * 2 color: Looks.colors.bg1Base radius: Looks.radius.medium children: [root.realContentItem] } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbar.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets Item { id: root property real padding: 9 property alias colBackground: background.color property alias spacing: toolbarLayout.spacing property alias radius: background.radius default property alias toolbarData: toolbarLayout.data implicitWidth: background.implicitWidth implicitHeight: background.implicitHeight Rectangle { id: background anchors.fill: parent implicitHeight: 50 implicitWidth: toolbarLayout.implicitWidth + root.padding * 2 radius: Looks.radius.large color: Looks.colors.bg0Base border.width: 1 border.color: Looks.colors.bg1Border RowLayout { id: toolbarLayout spacing: 4 anchors { fill: parent margins: root.padding } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbarButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common WButton { implicitHeight: 32 radius: Looks.radius.medium } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconButton.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common WToolbarButton { id: root implicitWidth: height contentItem: Item { FluentIcon { anchors.centerIn: parent icon: root.icon.name implicitSize: 18 color: root.fgColor } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconTabButton.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.common TabButton { id: root implicitWidth: 38 implicitHeight: 32 padding: 0 background: null contentItem: Item { FluentIcon { anchors.centerIn: parent icon: root.icon.name color: root.icon.color implicitSize: 18 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbarSeparator.qml ================================================ import QtQuick import QtQuick.Layouts import qs.modules.common Rectangle { Layout.leftMargin: 4 Layout.rightMargin: 4 implicitHeight: 24 implicitWidth: 1 color: Looks.colors.bg0Border } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WToolbarTabBar.qml ================================================ import QtQuick import QtQuick.Controls import qs.modules.common import qs.modules.common.functions TabBar { id: root implicitHeight: 32 background: Rectangle { radius: Looks.radius.medium color: Looks.colors.bgPanelFooter border.color: ColorUtils.transparentize(Looks.colors.bg0Border, 0.7) border.width: 1 // Indicator Rectangle { anchors { top: parent.top bottom: parent.bottom left: parent.left leftMargin: root.currentIndex * (root.width / root.count) Behavior on leftMargin { animation: Looks.transition.resize.createObject(this) } } radius: Looks.radius.medium color: Looks.colors.bg2Base border.color: Looks.colors.bg0Border border.width: 1 width: root.width / root.count Rectangle { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 1 } implicitWidth: pressDetector.containsPress ? 16 : 12 implicitHeight: 3 radius: height / 2 color: Looks.colors.accent } } } MouseArea { id: pressDetector z: 9999 anchors.fill: parent acceptedButtons: Qt.LeftButton } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/looks/WUserAvatar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks StyledImage { id: avatar Layout.alignment: Qt.AlignTop sourceSize: Qt.size(32, 32) source: Directories.userAvatarPathAccountsService fallbacks: [Directories.userAvatarPathRicersAndWeirdSystems, Directories.userAvatarPathRicersAndWeirdSystems2] layer.enabled: true layer.effect: OpacityMask { maskSource: Circle { diameter: avatar.height } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarWidget.qml ================================================ pragma ComponentBehavior: Bound import QtQml import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks BodyRectangle { id: root // State property bool collapsed // Locale property var locale: Qt.locale(Config.options.calendar.locale) implicitHeight: collapsed ? 0 : contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth Behavior on implicitHeight { animation: Looks.transition.enter.createObject(this) } clip: true ColumnLayout { id: contentColumn spacing: 12 CalendarHeader { Layout.topMargin: 10 Layout.fillWidth: true } ColumnLayout { Layout.fillWidth: true Layout.leftMargin: 5 Layout.rightMargin: 5 spacing: 1 DayOfWeekRow { Layout.fillWidth: true locale: root.locale spacing: calendarView.buttonSpacing implicitHeight: calendarView.buttonSize delegate: Item { id: dayOfWeekItem required property var model implicitHeight: calendarView.buttonSize implicitWidth: calendarView.buttonSize WText { anchors.centerIn: parent text: { var result = dayOfWeekItem.model.shortName; if (Config.options.waffles.calendar.force2CharDayOfWeek) result = result.substring(0,2); return result; } color: Looks.colors.fg font.pixelSize: Looks.font.pixelSize.large } } } CalendarView { id: calendarView locale: root.locale verticalPadding: 2 buttonSize: 41 // ??? buttonSpacing: 6 buttonVerticalSpacing: 1 Layout.fillWidth: true delegate: DayButton {} } } } component DayButton: WButton { id: dayButton required property var model checked: model.today enabled: hovered || checked || model.month === calendarView.focusedMonth implicitWidth: calendarView.buttonSize implicitHeight: calendarView.buttonSize radius: height / 2 required property int index contentItem: Item { WText { anchors.centerIn: parent text: dayButton.model.day color: dayButton.fgColor font.pixelSize: Looks.font.pixelSize.larger } } } component CalendarHeader: RowLayout { Layout.leftMargin: 8 Layout.rightMargin: 8 spacing: 8 WBorderlessButton { Layout.fillWidth: true implicitHeight: 34 contentItem: Item { WText { anchors.fill: parent horizontalAlignment: Text.AlignLeft text: Qt.locale().toString(calendarView.focusedDate, "MMMM yyyy") font.pixelSize: Looks.font.pixelSize.large font.weight: Looks.font.weight.strong } } } ScrollMonthButton { scrollDown: false } ScrollMonthButton { scrollDown: true } } component ScrollMonthButton: WBorderlessButton { id: scrollMonthButton required property bool scrollDown Layout.alignment: Qt.AlignVCenter onClicked: { calendarView.scrollMonthsAndSnap(scrollDown ? 1 : -1); } implicitWidth: 32 implicitHeight: 34 contentItem: FluentIcon { filled: true implicitSize: 12 icon: scrollMonthButton.scrollDown ? "caret-down" : "caret-up" } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/DateHeader.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks FooterRectangle { id: root implicitWidth: 0 property bool collapsed color: ColorUtils.transparentize(Looks.colors.bgPanelBody, collapsed ? 0 : 1) Behavior on color { animation: Looks.transition.color.createObject(this) } RowLayout { anchors { fill: parent leftMargin: 16 rightMargin: 16 topMargin: 12 bottomMargin: 12 } WText { Layout.fillWidth: true font.pixelSize: Looks.font.pixelSize.large text: DateTime.collapsedCalendarFormat } WBorderedButton { implicitWidth: 24 implicitHeight: 24 padding: 0 onClicked: root.collapsed = !root.collapsed contentItem: Item { FluentIcon { anchors.centerIn: parent implicitSize: 12 icon: "chevron-down" rotation: root.collapsed ? 180 : 0 Behavior on rotation { animation: Looks.transition.rotate.createObject(this) } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/FocusFooter.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks FooterRectangle { Layout.fillWidth: true implicitWidth: 0 color: Looks.colors.bgPanelBody RowLayout { anchors { fill: parent leftMargin: 16 rightMargin: 16 topMargin: 12 bottomMargin: 12 } spacing: 0 SmallBorderedIconButton { visible: !TimerService.pomodoroRunning icon.name: "subtract" onClicked: Config.options.time.pomodoro.focus -= 300 // 5 mins } WTextWithFixedWidth { visible: !TimerService.pomodoroRunning implicitWidth: 81 horizontalAlignment: Text.AlignHCenter color: Looks.colors.subfg text: Translation.tr("%1 mins").arg(`${TimerService.focusTime / 60}`) } SmallBorderedIconButton { visible: !TimerService.pomodoroRunning icon.name: "add" onClicked: Config.options.time.pomodoro.focus += 300 // 5 mins } WText { visible: TimerService.pomodoroRunning font.pixelSize: Looks.font.pixelSize.large text: Translation.tr("Focusing") } Item { Layout.fillWidth: true } SmallBorderedIconAndTextButton { iconName: TimerService.pomodoroRunning ? "stop" : "play" text: TimerService.pomodoroRunning ? Translation.tr("End session") : Translation.tr("Focus") onClicked: { if (TimerService.pomodoroRunning) { TimerService.togglePomodoro(); TimerService.resetPomodoro(); } else { TimerService.togglePomodoro(); Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "sidebarRight", "toggle"]); } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationCenterContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Qt.labs.synchronizer import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks WBarAttachedPanelContent { id: root readonly property bool barAtBottom: Config.options.waffles.bar.bottom revealFromSides: true revealFromLeft: false property bool collapsed: false contentItem: ColumnLayout { id: contentLayout anchors { horizontalCenter: parent.horizontalCenter top: parent.top bottom: parent.bottom } spacing: 12 Item { id: notificationArea Layout.fillHeight: true implicitWidth: notificationPane.implicitWidth WPane { id: notificationPane anchors { bottom: parent.bottom left: parent.left right: parent.right } contentItem: NotificationPaneContent { implicitWidth: calendarColumnLayout.implicitWidth implicitHeight: { if (Notifications.list.length > 0) { return ((contentLayout.height - calendarPane.height - contentLayout.spacing) - notificationPane.borderWidth * 2) } return 230; } Timer { id: enableTimer interval: Config.options.hacks.arbitraryRaceConditionDelay onTriggered: heightBehavior.enabled = true; } Behavior on implicitHeight { id: heightBehavior enabled: false Component.onCompleted: { enableTimer.restart(); } animation: Looks.transition.enter.createObject(this) } } } } WPane { id: calendarPane contentItem: WPanelPageColumn { id: calendarColumnLayout DateHeader { Layout.fillWidth: true Synchronizer on collapsed { property alias source: root.collapsed } } WPanelSeparator { visible: !root.collapsed } CalendarWidget { Layout.fillWidth: true Synchronizer on collapsed { property alias source: root.collapsed } } WPanelSeparator {} FocusFooter { Layout.fillWidth: true } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationHeaderButton.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks WBorderlessButton { id: root Layout.fillWidth: false property real implicitSize: 16 implicitWidth: implicitSize implicitHeight: implicitSize color: "transparent" colForeground: root.hovered && !root.pressed ? Looks.colors.fg : Looks.colors.fg1 Behavior on colForeground { animation: Looks.transition.color.createObject(this) } contentItem: Item { FluentIcon { anchors.centerIn: parent implicitSize: root.implicitSize icon: root.icon.name color: root.colForeground } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationPaneContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks FooterRectangle { id: root anchors.fill: parent implicitHeight: 230 ColumnLayout { id: contentLayout anchors.fill: parent anchors.margins: 4 spacing: 12 RowLayout { Layout.fillWidth: true Layout.leftMargin: 12 Layout.rightMargin: 12 Layout.topMargin: 8 spacing: 8 WText { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: Translation.tr("Notifications") font.pixelSize: Looks.font.pixelSize.large } SmallBorderedIconButton { icon.name: "alert-snooze" checked: Notifications.silent onClicked: { Notifications.silent = !Notifications.silent; } } SmallBorderedIconAndTextButton { visible: Notifications.list.length > 0 iconVisible: false text: Translation.tr("Clear all") onClicked: { Notifications.discardAllNotifications(); } } } WListView { Layout.fillWidth: true Layout.fillHeight: true clip: true model: Notifications.appNameList delegate: WNotificationGroup { required property int index required property var modelData width: ListView.view.width notificationGroup: Notifications.groupsByAppName[modelData] } EmptyPlaceholder { visible: Notifications.list.length === 0 anchors.centerIn: parent } } } component EmptyPlaceholder: WText { horizontalAlignment: Text.AlignHCenter text: Translation.tr("No new notifications") } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/SmallBorderedIconAndTextButton.qml ================================================ import QtQuick import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks AcrylicButton { id: root property bool iconVisible: true property string iconName: "" property bool iconFilled: true colBackground: Looks.colors.bg2 colBackgroundHover: Looks.colors.bg2Hover colBackgroundActive: Looks.colors.bg2Active property color colBorder: Looks.colors.bg2Border property color colBorderToggled: Looks.colors.accent border.color: checked ? colBorderToggled : colBorder leftPadding: 12 rightPadding: 12 implicitWidth: focusButtonContent.implicitWidth + leftPadding + rightPadding implicitHeight: 24 contentItem: Row { id: focusButtonContent spacing: 4 FluentIcon { visible: root.iconVisible icon: root.iconName filled: root.iconFilled implicitSize: 14 anchors.verticalCenter: parent.verticalCenter } WText { anchors.verticalCenter: parent.verticalCenter text: root.text } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/SmallBorderedIconButton.qml ================================================ import QtQuick import qs import qs.services import qs.modules.common import qs.modules.waffle.looks WBorderedButton { id: root implicitWidth: 24 implicitHeight: 24 contentItem: Item { FluentIcon { anchors.centerIn: parent implicitSize: 12 icon: root.icon.name color: root.fgColor } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationAppIcon.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import org.kde.kirigami as Kirigami import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks Item { id: root property string icon: "" property real implicitSize: 16 implicitWidth: implicitSize implicitHeight: implicitSize Kirigami.Icon { anchors.fill: parent implicitWidth: root.implicitSize implicitHeight: root.implicitSize source: root.icon || fallback fallback: `${Looks.iconsPath}/apps.svg` roundToIconSize: false isMask: !root.icon color: Looks.colors.fg } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationDismissAnim.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks SequentialAnimation { id: root required property var target PropertyAction { target: root.target property: "ListView.delayRemove" value: true } NumberAnimation { target: root.target property: "x" to: root.target.width duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } PropertyAction { target: root.target property: "ListView.delayRemove" value: false } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationGroup.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks // TODO: Swipe to dismiss MouseArea { id: root required property var notificationGroup readonly property var notifications: notificationGroup?.notifications ?? [] property bool expanded: false implicitWidth: contentLayout.implicitWidth implicitHeight: contentLayout.implicitHeight function dismissAll() { root.notifications.forEach(notif => { Qt.callLater(() => { Notifications.discardNotification(notif.notificationId); }); }); removeAnimation.start(); } WNotificationDismissAnim { id: removeAnimation target: root } property real dragDismissThreshold: 100 drag { axis: Drag.XAxis target: contentLayout minimumX: 0 onActiveChanged: { if (drag.active) return; if (contentLayout.x > root.dragDismissThreshold) { root.dismissAll(); } else { contentLayout.x = 0; } } } ColumnLayout { id: contentLayout spacing: 4 width: root.width Behavior on x { animation: Looks.transition.enter.createObject(this) } GroupHeader { id: notifHeader Layout.fillWidth: true Layout.margins: 11 } WListView { Layout.leftMargin: -Math.min(35, contentLayout.x) Layout.rightMargin: -Layout.leftMargin Layout.fillWidth: true implicitWidth: notifHeader.implicitWidth implicitHeight: contentHeight interactive: false spacing: 4 model: ScriptModel { values: root.expanded ? root.notifications.slice().reverse() : root.notifications.slice(-1) objectProp: "notificationId" } delegate: WSingleNotification { id: singleNotif required property int index required property var modelData width: ListView.view.width notification: modelData groupExpandControlMessage: { if (root.notifications.length <= 1) return ""; if (!root.expanded) return Translation.tr("+%1 notifications").arg(root.notifications.length - 1); if (index === root.notifications.length - 1) return Translation.tr("See fewer"); return ""; } onGroupExpandToggle: { root.expanded = !root.expanded; } } } } component GroupHeader: MouseArea { id: headerMouseArea hoverEnabled: true acceptedButtons: Qt.NoButton implicitWidth: appHeader.implicitWidth implicitHeight: appHeader.implicitHeight RowLayout { id: appHeader anchors.fill: parent spacing: 7 WNotificationAppIcon { Layout.alignment: Qt.AlignVCenter icon: root.notificationGroup?.appIcon ?? "" } WText { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: root.notificationGroup?.appName ?? "" } // NotificationHeaderButton { // TODO: More notification functionality needed so we can have this button // visible: headerMouseArea.containsMouse // Layout.leftMargin: 25 // Layout.rightMargin: 25 // icon.name: "more-horizontal" // } NotificationHeaderButton { visible: headerMouseArea.containsMouse Layout.rightMargin: 3 icon.name: "dismiss" onClicked: { root.dismissAll(); } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/WSingleNotification.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.Notifications import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks MouseArea { id: root required property var notification property bool expanded: notification.actions.length > 0 property string groupExpandControlMessage: "" readonly property bool isPopup: notification?.popup ?? false signal groupExpandToggle hoverEnabled: true function dismiss() { Qt.callLater(() => { Notifications.discardNotification(root.notification?.notificationId); }); removeAnimation.start(); } WNotificationDismissAnim { id: removeAnimation target: root } implicitHeight: contentItem.implicitHeight implicitWidth: contentItem.implicitWidth Behavior on implicitHeight { animation: Looks.transition.enter.createObject(this) } property real dragDismissThreshold: 100 drag { axis: Drag.XAxis target: contentItem minimumX: 0 onActiveChanged: { if (drag.active) return; if (contentItem.x > root.dragDismissThreshold) { root.dismiss(); } else { contentItem.x = 0; } } } Rectangle { id: contentItem width: parent.width color: root.isPopup ? Looks.colors.bg0 : Looks.colors.bgPanelBody radius: root.isPopup ? Looks.radius.large : Looks.radius.medium property real padding: 12 implicitHeight: notificationContent.implicitHeight + padding * 2 implicitWidth: notificationContent.implicitWidth + padding * 2 border.width: 1 border.color: root.isPopup ? Looks.colors.bg2Border : Looks.colors.bgPanelSeparator Behavior on x { animation: Looks.transition.enter.createObject(this) } ColumnLayout { id: notificationContent anchors.fill: parent anchors.margins: contentItem.padding spacing: 19 // Header SingleNotificationHeader { Layout.fillWidth: true } // Content Item { id: actualContent Layout.fillWidth: true Layout.fillHeight: true property real spacing: 16 implicitHeight: Math.max(contentColumn.implicitHeight, imageLoader.height) implicitWidth: contentColumn.implicitWidth Loader { id: imageLoader anchors { top: parent.top left: parent.left } active: root.notification.image != "" sourceComponent: StyledImage { readonly property int size: 48 width: size height: size source: root.notification.image fillMode: Image.PreserveAspectFit } } ColumnLayout { id: contentColumn anchors { top: parent.top left: parent.left right: parent.right } spacing: 3 SummaryText { id: summaryText Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0 } BodyText { Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0 // onLineLaidOut: (line) => { // if (!imageLoader.active) return; // const dodgeDistance = imageLoader.width + actualContent.spacing; // // print(line.y, dodgeDistance) // if (summaryText.height + line.y > dodgeDistance) { // line.x -= dodgeDistance; // line.width += dodgeDistance; // } // } } } } // Actions ActionsRow { Layout.fillWidth: true } // "+1 notifications" button GroupExpandButton { Layout.bottomMargin: 2 } } } component SingleNotificationHeader: RowLayout { ExpandButton { Layout.topMargin: -2 } Item { Layout.fillWidth: true } NotificationHeaderButton { Layout.rightMargin: 4 opacity: (root.containsMouse || root.isPopup) ? 1 : 0 icon.name: "dismiss" implicitSize: 14 onClicked: root.dismiss() } } component ActionsRow: RowLayout { visible: root.expanded && root.notification.actions.length > 0 uniformCellSizes: true Repeater { id: actionRepeater model: root.notification.actions delegate: WBorderedButton { id: actionButton Layout.fillHeight: true required property var modelData Layout.fillWidth: true verticalPadding: 16 horizontalPadding: 12 text: modelData.text implicitHeight: actionButtonText.implicitHeight + verticalPadding * 2 contentItem: WText { id: actionButtonText text: actionButton.text font.pixelSize: Looks.font.pixelSize.large horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap } } } } component SummaryText: WText { Layout.fillWidth: true elide: Text.ElideRight text: root.notification?.summary font.pixelSize: Looks.font.pixelSize.large } component BodyText: WText { Layout.fillWidth: true Layout.fillHeight: true elide: Text.ElideRight verticalAlignment: Text.AlignTop wrapMode: Text.Wrap maximumLineCount: root.expanded ? 100 : 1 text: { if (root.expanded) return `` + `${NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "
")}`; return NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "
"); } color: Looks.colors.subfg textFormat: root.expanded ? Text.RichText : Text.StyledText onLinkActivated: link => { Qt.openUrlExternally(link); GlobalStates.sidebarRightOpen = false; } } component ExpandButton: NotificationHeaderButton { id: expandButton implicitWidth: expandButtonContent.implicitWidth onClicked: root.expanded = !root.expanded contentItem: Item { id: expandButtonContent implicitWidth: expandButtonRow.implicitWidth implicitHeight: expandButtonRow.implicitHeight RowLayout { id: expandButtonRow anchors.centerIn: parent spacing: 8 WText { color: expandButton.colForeground text: NotificationUtils.getFriendlyNotifTimeString(root.notification?.time) } FluentIcon { Layout.rightMargin: 12 icon: "chevron-down" implicitSize: 18 rotation: root.expanded ? -180 : 0 color: expandButton.colForeground Behavior on rotation { animation: Looks.transition.rotate.createObject(this) } } } } } component GroupExpandButton: AcrylicButton { id: groupExpandButton visible: root.groupExpandControlMessage !== "" horizontalPadding: 10 implicitHeight: 24 implicitWidth: expandButtonText.implicitWidth + horizontalPadding * 2 onClicked: root.groupExpandToggle() contentItem: Item { WText { id: expandButtonText anchors.centerIn: parent text: root.groupExpandControlMessage } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationCenter/WaffleNotificationCenter.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: root Connections { target: GlobalStates function onSidebarRightOpenChanged() { if (GlobalStates.sidebarRightOpen) panelLoader.active = true; } } Loader { id: panelLoader active: GlobalStates.sidebarRightOpen sourceComponent: PanelWindow { id: panelWindow exclusiveZone: 0 WlrLayershell.namespace: "quickshell:wNotificationCenter" WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { bottom: true top: true right: true } implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight HyprlandFocusGrab { id: focusGrab active: true windows: [panelWindow] onCleared: content.close(); } Connections { target: GlobalStates function onSidebarRightOpenChanged() { if (!GlobalStates.sidebarRightOpen) content.close(); } } NotificationCenterContent { id: content anchors.fill: parent onClosed: { GlobalStates.sidebarRightOpen = false; panelLoader.active = false; } } } } function toggleOpen() { GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; } IpcHandler { target: "sidebarRight" function toggle() { root.toggleOpen(); } } GlobalShortcut { name: "sidebarRightToggle" description: "Toggles notification center on press" onPressed: root.toggleOpen(); } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/notificationPopup/WaffleNotificationPopup.qml ================================================ import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks import qs.modules.waffle.notificationCenter Scope { id: notificationPopup PanelWindow { id: root visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null WlrLayershell.namespace: "quickshell:notificationPopup" WlrLayershell.layer: WlrLayer.Overlay exclusiveZone: 0 anchors { top: true right: true bottom: true } mask: Region { item: listview.contentItem } color: "transparent" implicitWidth: listview.implicitWidth WListView { id: listview anchors { bottom: parent.bottom right: parent.right left: parent.left } leftMargin: 16 rightMargin: 16 topMargin: 16 bottomMargin: 16 height: Math.min(contentItem.height + topMargin + bottomMargin, parent.height) width: parent.width - Appearance.sizes.elevationMargin * 2 implicitWidth: 396 spacing:12 model: ScriptModel { values: Notifications.popupList } delegate: WSingleNotification { required property var modelData notification: modelData width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/BrightnessOSD.qml ================================================ import QtQuick import Quickshell import Quickshell.Hyprland import qs.services import qs.modules.waffle.looks OSDValue { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) iconName: "weather-sunny" value: brightnessMonitor?.brightness ?? 0 showNumber: false Connections { target: Brightness function onBrightnessChanged() { root.timer.restart(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/OSDValue.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks WBarAttachedPanelContent { id: root required property string iconName property real value property bool showNumber: true property Timer timer: Timer { id: autoCloseTimer running: true interval: Config.options.osd.timeout repeat: false onTriggered: { root.close(); } } contentItem: WPane { anchors.centerIn: parent borderColor: Looks.colors.ambientShadow contentItem: Item { // color: Looks.colors.bg1Base // radius: Looks.radius.medium implicitWidth: root.showNumber ? 192 : 170 implicitHeight: 46 RowLayout { id: contentRow anchors.fill: parent anchors.margins: 12 spacing: 12 FluentIcon { Layout.alignment: Qt.AlignVCenter icon: root.iconName implicitSize: 18 } WProgressBar { id: progressBar value: root.value Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter Layout.rightMargin: root.showNumber ? 0 : 3 } WTextWithFixedWidth { visible: root.showNumber text: Math.round(root.value * 100) // longestText: "100" implicitWidth: 16 horizontalAlignment: Text.AlignHCenter } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/VolumeOSD.qml ================================================ import QtQuick import qs.services import qs.modules.waffle.looks OSDValue { id: root iconName: WIcons.volumeIcon value: Audio.sink?.audio.volume ?? 0 Connections { // Listen to volume changes target: Audio.sink?.audio ?? null function onVolumeChanged() { if (Audio.ready) root.timer.restart(); } function onMutedChanged() { if (Audio.ready) root.timer.restart(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/WaffleOSD.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.waffle.looks Scope { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) property string currentIndicator: "volume" property var indicators: [ { id: "volume", sourceUrl: "VolumeOSD.qml", globalStateValue: "osdVolumeOpen" }, { id: "brightness", sourceUrl: "BrightnessOSD.qml", globalStateValue: "osdBrightnessOpen" }, ] function triggerBrightnessOsd() { root.currentIndicator = "brightness"; GlobalStates.osdBrightnessOpen = true; } function triggerVolumeOSD() { root.currentIndicator = "volume"; GlobalStates.osdVolumeOpen = true; } // Listen to brightness changes Connections { target: Brightness function onBrightnessChanged() { root.triggerBrightnessOsd(); } } // Listen to volume changes Connections { target: Audio.sink?.audio ?? null function onVolumeChanged() { if (Audio.ready) root.triggerVolumeOSD(); } function onMutedChanged() { if (Audio.ready) root.triggerVolumeOSD(); } } // Open when global state changes Connections { target: GlobalStates function onOsdBrightnessOpenChanged() { if (GlobalStates.osdBrightnessOpen) panelLoader.active = true; } function onOsdVolumeOpenChanged() { if (GlobalStates.osdVolumeOpen) panelLoader.active = true; } } // The actual thing Loader { id: panelLoader active: false onActiveChanged: { if (active) return; root.indicators.forEach(i => { GlobalStates[i.globalStateValue] = false; }); } sourceComponent: PanelWindow { id: panelWindow Connections { target: root function onFocusedScreenChanged() { osdRoot.screen = root.focusedScreen; } } color: "transparent" exclusiveZone: 0 WlrLayershell.namespace: "quickshell:wOnScreenDisplay" WlrLayershell.layer: WlrLayer.Overlay anchors { top: !Config.options.waffles.bar.bottom bottom: Config.options.waffles.bar.bottom } mask: Region { item: osdIndicatorLoader } implicitWidth: osdIndicatorLoader.implicitWidth implicitHeight: osdIndicatorLoader.implicitHeight Loader { id: osdIndicatorLoader anchors.fill: parent source: root.indicators.find(i => i.id === root.currentIndicator)?.sourceUrl Connections { target: osdIndicatorLoader.item function onClosed() { panelLoader.active = false; GlobalStates[root.indicators.find(i => i.id === root.currentIndicator)?.globalStateValue] = false; } } Behavior on source { id: switchBehavior SequentialAnimation { id: switchAnim // Animate close of current indicator ScriptAction { script: { osdIndicatorLoader.item.close(); } } // Wait for close anim PauseAnimation { duration: osdIndicatorLoader.item.closeAnimDuration } PropertyAction {} // The source change happens here } } } } } IpcHandler { target: "osd" function trigger() { root.trigger(); } } GlobalShortcut { name: "osdTrigger" description: "Triggers OSD display" onPressed: root.trigger() } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks Rectangle { id: root color: "#000000" readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { PolkitService.cancel(); } } StyledImage { anchors.fill: parent source: Config.options.background.wallpaperPath fillMode: Image.PreserveAspectCrop Rectangle { anchors.fill: parent color: ColorUtils.transparentize("#000000", 0.31) PolkitDialog { id: dialog DragHandler { target: null property real startX: dialog.x property real startY: dialog.y onActiveChanged: { if (!active) return; startX = dialog.x; startY = dialog.y; } xAxis.onActiveValueChanged: { dialog.x = Math.round(startX + xAxis.activeValue); } yAxis.onActiveValueChanged: { dialog.y = Math.round(startY + yAxis.activeValue); } } x: Math.round((parent.width - width) / 2) y: Math.round((parent.height - height) / 2) } } } component PolkitDialog: WPane { borderColor: Looks.colors.ambientShadow contentItem: WPanelPageColumn { PolkitDialogHeader { Layout.fillWidth: true } BodyRectangle { id: dialogBody implicitHeight: bodyContent.implicitHeight + 48 implicitWidth: 434 color: Looks.colors.bg1Base ColumnLayout { id: bodyContent anchors.fill: parent anchors.margins: 24 spacing: 20 RowLayout { Layout.fillWidth: true spacing: 15 WAppIcon { iconName: PolkitService.flow?.iconName ?? "window-shield" fallback: PolkitService.flow?.iconName == "" ? `${Looks.iconsPath}/window-shield` : PolkitService.flow.iconName isMask: PolkitService.flow?.iconName === "" tryCustomIcon: false } WText { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft font.pixelSize: Looks.font.pixelSize.larger font.weight: Looks.font.weight.strongest text: { const iconName = PolkitService.flow?.iconName ?? ""; if (iconName === "") return Translation.tr("Command-line-invoked Action"); const desktopEntry = DesktopEntries.applications.values.find(entry => { return entry.icon == iconName; }); return desktopEntry ? desktopEntry.name : Translation.tr("Unknown Application"); } } } WText { Layout.fillWidth: true wrapMode: Text.Wrap horizontalAlignment: Text.AlignLeft text: PolkitService.cleanMessage } WTextField { id: inputField Layout.fillWidth: true focus: true enabled: PolkitService.interactionAvailable placeholderText: PolkitService.cleanPrompt echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal onAccepted: PolkitService.submit(inputField.text) Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { PolkitService.cancel(); } } Component.onCompleted: forceActiveFocus() Connections { target: PolkitService function onInteractionAvailableChanged() { if (!PolkitService.interactionAvailable) return; inputField.text = ""; inputField.forceActiveFocus(); } } } } } BodyRectangle { implicitHeight: 80 color: Looks.colors.bgPanelFooterBackground RowLayout { anchors.fill: parent anchors.margins: 24 spacing: 8 uniformCellSizes: true WButton { Layout.fillWidth: true implicitHeight: 32 colBackground: Looks.colors.bg1 horizontalAlignment: Text.AlignHCenter text: Translation.tr("Yes") onClicked: PolkitService.submit(inputField.text) } WButton { Layout.fillWidth: true implicitHeight: 32 horizontalAlignment: Text.AlignHCenter checked: true text: Translation.tr("No") onClicked: PolkitService.cancel() } } } } } component PolkitDialogHeader: BodyRectangle { implicitHeight: headerContent.implicitHeight color: Looks.colors.bg2Base CloseButton { anchors { top: parent.top right: parent.right } radius: 0 implicitWidth: 32 implicitHeight: 32 onClicked: { PolkitService.cancel(); } } ColumnLayout { id: headerContent anchors.fill: parent anchors.leftMargin: 24 anchors.rightMargin: 24 spacing: 18 WText { Layout.topMargin: 20 Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: Translation.tr("Polkit") } WText { Layout.fillWidth: true Layout.bottomMargin: 12 horizontalAlignment: Text.AlignLeft wrapMode: Text.Wrap text: Translation.tr("Do you want to allow this app to make changes to your device?") font.pixelSize: Looks.font.pixelSize.xlarger font.weight: Looks.font.weight.strongest } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Wayland FullscreenPolkitWindow { id: root contentComponent: Component { WPolkitContent {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/screenSnip/WRectangularSelection.qml ================================================ import QtQuick import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks Item { id: root required property int regionX required property int regionY required property int regionWidth required property int regionHeight property bool dashed: true property color borderColor: "#ffffff" property color overlayColor: ColorUtils.transparentize("#000000", 1) Component.onCompleted: overlayColor = ColorUtils.transparentize("#000000", 0.4) Behavior on overlayColor { ColorAnimation { duration: 150 easing.type: Easing.InOutQuad } } // Overlay to darken screen // Base dark overlay around region Rectangle { id: darkenOverlay z: 1 anchors { left: parent.left top: parent.top leftMargin: root.regionX - darkenOverlay.border.width topMargin: root.regionY - darkenOverlay.border.width } width: root.regionWidth + darkenOverlay.border.width * 2 height: root.regionHeight + darkenOverlay.border.width * 2 color: "transparent" border.color: root.overlayColor border.width: Math.max(root.width, root.height) } // Selection border DashedBorder { id: border z: 2 visible: root.regionWidth > 0 && root.regionHeight > 0 anchors { left: parent.left top: parent.top leftMargin: Math.round(root.regionX - borderWidth) topMargin: Math.round(root.regionY - borderWidth) } width: Math.round(root.regionWidth + borderWidth * 2) height: Math.round(root.regionHeight + borderWidth * 2) color: root.borderColor dashLength: 4 gapLength: root.dashed ? 3 : 0 borderWidth: 1 } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/screenSnip/WRegionSelectionPanel.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt.labs.synchronizer import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.utils import qs.modules.common.widgets import qs.modules.waffle.looks PanelWindow { id: root enum MediaType { Image, Video } enum ImageAction { Copy, Menu, CharRecognition, Search } enum VideoAction { Record, RecordWithSound } enum SelectionMode { Rect, Window } function close() { root.closed(); } property var mediaType: WRegionSelectionPanel.MediaType.Image property var imageAction: WRegionSelectionPanel.ImageAction.Copy property var selectionMode: WRegionSelectionPanel.SelectionMode.Rect visible: false color: "transparent" WlrLayershell.namespace: "quickshell:regionSelector" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand exclusionMode: ExclusionMode.Ignore anchors { left: true right: true top: true bottom: true } // Hyprland stuff readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen) readonly property real monitorScale: hyprlandMonitor.scale readonly property var windows: [...HyprlandData.windowList].sort((a, b) => { // Sort floating=true windows before others if (a.floating === b.floating) return 0; return a.floating ? -1 : 1; }) property string screenshotDir: Directories.screenshotTemp property string screenshotPath: `${root.screenshotDir}/image-${screen.name}` TempScreenshotProcess { id: screenshotProc running: true screen: root.screen screenshotDir: root.screenshotDir screenshotPath: root.screenshotPath onExited: (exitCode, exitStatus) => { root.preparationDone = true; } } property bool preparationDone: false onPreparationDoneChanged: { if (!preparationDone) return; root.visible = true; } function getScreenshotAction() { switch (root.mediaType) { case WRegionSelectionPanel.MediaType.Image: switch (root.imageAction) { case WRegionSelectionPanel.ImageAction.Copy: return ScreenshotAction.Action.Copy; case WRegionSelectionPanel.ImageAction.Menu: return ScreenshotAction.Action.Edit; case WRegionSelectionPanel.ImageAction.CharRecognition: return ScreenshotAction.Action.CharRecognition; case WRegionSelectionPanel.ImageAction.Search: return ScreenshotAction.Action.Search; default: return ScreenshotAction.Action.Copy; } break; case WRegionSelectionPanel.MediaType.Video: switch (root.videoAction) { case WRegionSelectionPanel.VideoAction.Record: return ScreenshotAction.Action.Record; case WRegionSelectionPanel.VideoAction.RecordWithSound: return ScreenshotAction.Action.RecordWithSound; } } } Process { id: snipProc } ScreencopyView { id: screencopyView anchors.fill: parent live: false captureSource: root.screen focus: root.visible Keys.onPressed: event => { // Esc to close if (event.key === Qt.Key_Escape) { root.close(); } else if (event.key === Qt.Key_E && event.modifiers & Qt.ControlModifier) { if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) { root.imageAction = WRegionSelectionPanel.ImageAction.Copy; } else { root.imageAction = WRegionSelectionPanel.ImageAction.Menu; } } } DragManager { id: dragArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton cursorShape: Qt.CrossCursor property bool isWindowSelection: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window property var hoveredWindow: root.windows.find(w => { const inCurrentWorkspace = w.workspace.id === HyprlandData.activeWorkspace.id; const withinXRange = w.at[0] <= dragArea.mouseX && dragArea.mouseX <= w.at[0] + w.size[0]; const withinYRange = w.at[1] <= dragArea.mouseY && dragArea.mouseY <= w.at[1] + w.size[1]; return inCurrentWorkspace && withinXRange && withinYRange; }) property int winPadding: 1 property int selectionX: isWindowSelection ? ((hoveredWindow?.at[0] ?? 0) - winPadding) : regionTopLeftX property int selectionY: isWindowSelection ? ((hoveredWindow?.at[1] ?? 0) - winPadding) : regionTopLeftY property int selectionWidth: isWindowSelection ? ((hoveredWindow?.size[0] ?? 0) + winPadding * 2) : regionWidth property int selectionHeight: isWindowSelection ? ((hoveredWindow?.size[1] ?? 0) + winPadding * 2) : regionHeight onDragReleased: (diffX, diffY) => { if (selectionWidth === 0 || selectionHeight === 0) { return; } const screenshotDir = Config.options.screenSnip.savePath !== "" ? Config.options.screenSnip.savePath : ""; const screenshotAction = root.getScreenshotAction(); const command = ScreenshotAction.getCommand(dragArea.selectionX * root.monitorScale // , dragArea.selectionY * root.monitorScale // , dragArea.selectionWidth * root.monitorScale// , dragArea.selectionHeight * root.monitorScale // , root.screenshotPath // , screenshotAction // , screenshotDir); // yo wtf is this formatting qmlls do be funnie snipProc.command = command; // Image post-processing snipProc.startDetached(); root.close(); } WRectangularSelection { id: rectangularSelection anchors.fill: parent regionX: dragArea.selectionX regionY: dragArea.selectionY regionWidth: dragArea.selectionWidth regionHeight: dragArea.selectionHeight dashed: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect } RegionSelectionOptionsToolbar { anchors { horizontalCenter: parent.horizontalCenter top: parent.top topMargin: 12 } } } } component RegionSelectionOptionsToolbar: WToolbar { // Image/video WToolbarTabBar { currentIndex: switch (root.mediaType) { case WRegionSelectionPanel.MediaType.Image: return 0; case WRegionSelectionPanel.MediaType.Video: return 1; default: return 0; } WToolbarIconTabButton { icon.name: "camera" icon.color: Looks.colors.fg } WToolbarIconTabButton { icon.name: "video" icon.color: Looks.colors.fg } onCurrentIndexChanged: { switch (currentIndex) { case 0: root.mediaType = WRegionSelectionPanel.MediaType.Image; break; case 1: root.mediaType = WRegionSelectionPanel.MediaType.Video; break; } } WToolTip { text: Translation.tr("Snip") } } // Selection type WToolbarButton { id: selectionTypeBtn implicitWidth: selectionTypeBtnRow.implicitWidth + 11 * 2 leftPadding: 11 rightPadding: 11 onClicked: { selectionTypeMenu.visible = !selectionTypeMenu.visible; } contentItem: Row { id: selectionTypeBtnRow spacing: 4 FluentIcon { anchors.verticalCenter: parent.verticalCenter icon: switch (root.selectionMode) { case WRegionSelectionPanel.SelectionMode.Rect: return "crop"; case WRegionSelectionPanel.SelectionMode.Window: return "calendar-add"; default: return "crop"; } implicitSize: 18 } FluentIcon { anchors { top: parent.top topMargin: (parent.height - height) / 2 + (selectionTypeBtn.down ? 2 : 0) Behavior on topMargin { animation: Looks.transition.enter.createObject(this) } } icon: "chevron-down" implicitSize: 12 } } WMenu { id: selectionTypeMenu onClosed: screencopyView.focus = true x: -margins y: -margins - (selectionTypeBtn.parent.height - selectionTypeBtn.height) - 16 topMargin: -6 height: implicitHeight + sourceEdgeMargin color: Looks.colors.bg1Base Action { icon.name: "crop" text: Translation.tr("Rectangle") checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect onTriggered: { root.selectionMode = WRegionSelectionPanel.SelectionMode.Rect; } } Action { icon.name: "calendar-add" text: Translation.tr("Window") checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window onTriggered: { root.selectionMode = WRegionSelectionPanel.SelectionMode.Window; } } } WToolTip { text: Translation.tr("Snipping area") } } // Markup WToolbarIconButton { icon.name: "image-edit" enabled: root.mediaType === WRegionSelectionPanel.MediaType.Image checked: root.imageAction === WRegionSelectionPanel.ImageAction.Menu onClicked: { if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) { root.imageAction = WRegionSelectionPanel.ImageAction.Copy; } else { root.imageAction = WRegionSelectionPanel.ImageAction.Menu; } } WToolTip { text: Translation.tr("Quick markup (Ctrl+E)") } } WToolbarSeparator {} // Tools WToolbarIconButton { icon.name: "search-visual" checked: root.imageAction === WRegionSelectionPanel.ImageAction.Search onClicked: { if (root.imageAction === WRegionSelectionPanel.ImageAction.Search && root.mediaType === WRegionSelectionPanel.MediaType.Image) { root.imageAction = WRegionSelectionPanel.ImageAction.Copy; } else { root.mediaType = WRegionSelectionPanel.MediaType.Image; root.imageAction = WRegionSelectionPanel.ImageAction.Search; } } WToolTip { text: Translation.tr("Image search") } } WToolbarIconButton { icon.name: "eyedropper" onClicked: { Quickshell.execDetached(["bash", "-c", "sleep 0.2; hyprpicker -a"]); root.closed(); } WToolTip { text: Translation.tr("Color picker") } } WToolbarIconButton { icon.name: "scan-text" checked: root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition onClicked: { if (root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition && root.mediaType === WRegionSelectionPanel.MediaType.Image) { root.imageAction = WRegionSelectionPanel.ImageAction.Copy; } else { root.mediaType = WRegionSelectionPanel.MediaType.Image; root.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition; } } WToolTip { text: Translation.tr("Text extractor") } } WToolbarSeparator {} WToolbarIconButton { icon.name: "dismiss" onClicked: root.close() WToolTip { text: Translation.tr("Close (Esc)") } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/screenSnip/WScreenSnip.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.services import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Widgets import Quickshell.Hyprland Scope { id: root function dismiss() { GlobalStates.regionSelectorOpen = false; } Loader { id: regionSelectorLoader active: GlobalStates.regionSelectorOpen sourceComponent: WRegionSelectionPanel { onClosed: root.dismiss() } } function screenshot() { GlobalStates.regionSelectorOpen = true; } function ocr() { GlobalStates.regionSelectorOpen = true; regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image; regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition; } function record() { GlobalStates.regionSelectorOpen = true; regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video; regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.Record; } function recordWithSound() { GlobalStates.regionSelectorOpen = true; regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video; regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.RecordWithSound; } function search() { GlobalStates.regionSelectorOpen = true; regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image; regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.Search; } IpcHandler { target: "region" function screenshot() { root.screenshot(); } function ocr() { root.ocr(); } function record() { root.record(); } function recordWithSound() { root.recordWithSound(); } function search() { root.search(); } } GlobalShortcut { name: "regionScreenshot" description: "Takes a screenshot of the selected region" onPressed: root.screenshot() } GlobalShortcut { name: "regionSearch" description: "Searches the selected region" onPressed: root.search() } GlobalShortcut { name: "regionOcr" description: "Recognizes text in the selected region" onPressed: root.ocr() } GlobalShortcut { name: "regionRecord" description: "Records the selected region" onPressed: root.record() } GlobalShortcut { name: "regionRecordWithSound" description: "Records the selected region with sound" onPressed: root.recordWithSound() } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/sessionScreen/PowerButton.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io WSessionScreenTextButton { id: root implicitWidth: 40 implicitHeight: 40 focusRingRadius: Looks.radius.large colBackground: ColorUtils.transparentize(Looks.darkColors.bg2) colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover) colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active) property color color: { if (root.down) { return root.colBackgroundActive; } else if (root.hovered) { return root.colBackgroundHover; } else { return root.colBackground; } } background: Rectangle { id: background radius: Looks.radius.medium color: root.color } contentItem: Item { FluentIcon { anchors.centerIn: parent implicitSize: 20 icon: "power" color: root.fgColor } } onClicked: { powerMenu.visible = !powerMenu.visible; } WMenu { id: powerMenu x: -powerMenu.implicitWidth / 2 + root.implicitWidth / 2 y: -powerMenu.implicitHeight color: Looks.darkColors.bg1Base Component.onCompleted: { powerMenu.backgroundPane.borderColor = Looks.applyContentTransparency(Looks.darkColors.bg2Border); } delegate: WMenuItem { id: menuItemDelegate colBackground: ColorUtils.transparentize(Looks.darkColors.bg1Base) colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover) colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active) colForeground: Looks.darkColors.fg } Action { icon.name: "power" text: Translation.tr("Shut down") onTriggered: Session.poweroff() } Action { icon.name: "arrow-counterclockwise" text: Translation.tr("Restart") onTriggered: Session.reboot() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/sessionScreen/SessionScreenContent.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import qs.modules.waffle.looks import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell Item { id: root Component.onCompleted: { lockButton.forceActiveFocus(); } ColumnLayout { anchors.centerIn: parent spacing: 4 WSessionScreenTextButton { id: lockButton focus: true text: Translation.tr("Lock") onClicked: { GlobalStates.sessionOpen = false; Session.lock(); } KeyNavigation.up: powerButton KeyNavigation.down: signOutButton } WSessionScreenTextButton { id: signOutButton focus: true text: Translation.tr("Sign out") onClicked: { GlobalStates.sessionOpen = false; Session.logout(); } KeyNavigation.up: lockButton KeyNavigation.down: changePasswordButton } WSessionScreenTextButton { id: changePasswordButton focus: true text: Translation.tr("Change password") onClicked: { GlobalStates.sessionOpen = false; Session.changePassword(); } KeyNavigation.up: signOutButton KeyNavigation.down: taskManagerButton } WSessionScreenTextButton { id: taskManagerButton focus: true text: Translation.tr("Task Manager") onClicked: { GlobalStates.sessionOpen = false; Session.launchTaskManager(); } KeyNavigation.up: signOutButton KeyNavigation.down: cancelButton } CancelButton { id: cancelButton Layout.fillWidth: true Layout.leftMargin: 5 Layout.rightMargin: 5 Layout.topMargin: 38 onClicked: GlobalStates.sessionOpen = false KeyNavigation.up: taskManagerButton KeyNavigation.down: powerButton } } RowLayout { anchors { bottom: parent.bottom right: parent.right bottomMargin: 21 rightMargin: 31 } PowerButton { id: powerButton KeyNavigation.up: cancelButton KeyNavigation.down: lockButton } } component CancelButton: WBorderlessButton { id: root implicitHeight: 32 colBackground: Looks.darkColors.bg1Base colBackgroundHover: Qt.lighter(Looks.darkColors.bg1Base, 1.2) colBackgroundActive: Qt.lighter(Looks.darkColors.bg1Base, 1.1) colForeground: Looks.darkColors.fg property bool keyboardDown: false Keys.onPressed: event => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = true; event.accepted = true; } } Keys.onReleased: event => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = false; root.clicked(); event.accepted = true; } } contentItem: WText { text: Translation.tr("Cancel") horizontalAlignment: Text.AlignHCenter font.pixelSize: Looks.font.pixelSize.large color: root.colForeground } Rectangle { visible: cancelButton.focus anchors { fill: parent margins: -3 } radius: cancelButton.background.radius + 4 color: "transparent" border.width: 2 border.color: "#ffffff" } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/sessionScreen/WSessionScreenTextButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import qs import qs.modules.waffle.looks WTextButton { id: root implicitWidth: 135 implicitHeight: 40 horizontalPadding: 5 property bool keyboardDown: false property alias focusRingRadius: focusRing.radius fgColor: (root.pressed || root.keyboardDown) ? Looks.darkColors.fg1 : Looks.darkColors.fg Keys.onPressed: event => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = true; event.accepted = true; } } Keys.onReleased: event => { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { keyboardDown = false; root.clicked(); event.accepted = true; } } contentItem: Item { id: contentItem implicitWidth: buttonText.implicitWidth WText { id: buttonText anchors.fill: parent color: root.fgColor text: root.text font.pixelSize: Looks.font.pixelSize.large } } Rectangle { id: focusRing visible: root.focus anchors { fill: parent margins: -4 } color: "transparent" border.width: 2 border.color: "#ffffff" } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/sessionScreen/WaffleSessionScreen.qml ================================================ import qs import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: root property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) Loader { id: sessionLoader active: GlobalStates.sessionOpen onActiveChanged: { if (sessionLoader.active) SessionWarnings.refresh(); } Connections { target: GlobalStates function onScreenLockedChanged() { if (GlobalStates.screenLocked) { GlobalStates.sessionOpen = false; } } } sourceComponent: PanelWindow { // Session menu id: sessionRoot visible: sessionLoader.active property string subtitle function hide() { GlobalStates.sessionOpen = false; } exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:session" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive // This is a big surface so we needa carefully choose the transparency, // or we'll get a large scary rgb blob color: "#000000" anchors { top: true left: true right: true bottom: true } Item { anchors.fill: parent Keys.onPressed: (event) => { if (event.key === Qt.Key_Escape) { sessionRoot.hide(); } } SessionScreenContent { anchors.fill: parent } } } } IpcHandler { target: "session" function toggle(): void { GlobalStates.sessionOpen = !GlobalStates.sessionOpen; } function close(): void { GlobalStates.sessionOpen = false } function open(): void { GlobalStates.sessionOpen = true } } GlobalShortcut { name: "sessionToggle" description: "Toggles session screen on press" onPressed: { GlobalStates.sessionOpen = !GlobalStates.sessionOpen; } } GlobalShortcut { name: "sessionOpen" description: "Opens session screen on press" onPressed: { GlobalStates.sessionOpen = true } } GlobalShortcut { name: "sessionClose" description: "Closes session screen on press" onPressed: { GlobalStates.sessionOpen = false } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks FooterRectangle { id: root property real horizontalPadding: 32 property real verticalPadding: 16 property bool searching: text.length > 0 property alias searchInput: searchInput property alias text: searchInput.text implicitHeight: outline.implicitHeight + verticalPadding * 2 signal accepted() Component.onCompleted: forceFocus() function forceFocus() { searchInput.forceActiveFocus(); } focus: true color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter Behavior on horizontalPadding { enabled: Config.options.waffles.tweaks.smootherSearchBar animation: Looks.transition.move.createObject(this) } Behavior on verticalPadding { enabled: Config.options.waffles.tweaks.smootherSearchBar animation: Looks.transition.move.createObject(this) } Rectangle { id: outline anchors { left: parent.left right: parent.right leftMargin: root.horizontalPadding rightMargin: root.horizontalPadding verticalCenter: parent.verticalCenter } implicitHeight: 32 color: "transparent" radius: height / 2 border.width: 1 border.color: Looks.colors.bg2Border } Rectangle { id: searchInputBg anchors.fill: outline anchors.margins: 1 radius: height / 2 color: Looks.colors.inputBg RowLayout { anchors.fill: parent spacing: 11 WAppIcon { Layout.leftMargin: 14 iconName: "system-search-checked" separateLightDark: true implicitSize: 18 } WTextInput { id: searchInput focus: true Layout.fillWidth: true WText { anchors { left: parent.left verticalCenter: parent.verticalCenter } color: Looks.colors.accentUnfocused text: Translation.tr("Search for apps") // should also have "", settings, and documents" but we don't have those visible: searchInput.text.length === 0 font.pixelSize: Looks.font.pixelSize.large } onAccepted: { root.accepted(); } } } } MouseArea { anchors.fill: parent hoverEnabled: true cursorShape: Qt.IBeamCursor acceptedButtons: Qt.NoButton } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml ================================================ pragma ComponentBehavior: Bound import Qt.labs.synchronizer import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.startMenu.startPage import qs.modules.waffle.startMenu.searchPage WBarAttachedPanelContent { id: root property bool searching: false property string searchText: LauncherSearch.query StartMenuContext { id: context } Keys.onPressed: event => { // Prevent Esc and Backspace from registering if (event.key === Qt.Key_Escape) return; // Handle Backspace: focus and delete character if not focused if (event.key === Qt.Key_Backspace) { searchBar.forceFocus(); if (event.modifiers & Qt.ControlModifier) { // Delete word before cursor let text = searchBar.text; let pos = searchBar.searchInput.cursorPosition; if (pos > 0) { // Find the start of the previous word let left = text.slice(0, pos); let match = left.match(/(\s*\S+)\s*$/); let deleteLen = match ? match[0].length : 1; searchBar.text = text.slice(0, pos - deleteLen) + text.slice(pos); searchBar.searchInput.cursorPosition = pos - deleteLen; } } else { // Delete character before cursor if any if (searchBar.searchInput.cursorPosition > 0) { searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.text.slice(searchBar.searchInput.cursorPosition); searchBar.searchInput.cursorPosition -= 1; } } // Always move cursor to end after programmatic edit searchBar.searchInput.cursorPosition = searchBar.text.length; event.accepted = true; // If already focused, let TextField handle it return; } // Only handle visible printable characters (ignore control chars, arrows, etc.) if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc. { if (!searchBar.searchInput.activeFocus) { searchBar.forceFocus(); // Insert the character at the cursor position searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.text.slice(searchBar.searchInput.cursorPosition); searchBar.searchInput.cursorPosition += 1; event.accepted = true; context.setCurrentIndex(0); } } // Arrow keys for item navigation if (event.key === Qt.Key_Down) { let maxIndex = Math.max(0, LauncherSearch.results.length - 1); context.setCurrentIndex(Math.min(context.currentIndex + 1, maxIndex)); event.accepted = true; } else if (event.key === Qt.Key_Up) { context.setCurrentIndex(Math.max(context.currentIndex - 1, 0)); event.accepted = true; } } contentItem: WPane { contentItem: WPanelPageColumn { SearchBar { id: searchBar Layout.fillWidth: true implicitWidth: 832 // TODO: Make sizes naturally inferred horizontalPadding: 32 // verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel Synchronizer on searching { property alias target: root.searching } focus: true text: root.searchText onTextChanged: { LauncherSearch.query = text; } onAccepted: { context.accepted(); } } Item { implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred Layout.fillWidth: true Loader { id: pageContentLoader anchors.fill: parent sourceComponent: root.searching ? searchPageComp : startPageComp } } } } Component { id: searchPageComp SearchPageContent { context: context } } Component { id: startPageComp StartPageContent {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import qs import qs.modules.common import qs.services Scope { id: root signal accepted property int currentIndex: 0 function setCurrentIndex(index) { if (index == currentIndex) return; currentIndex = index; } function selectCategory(category) { for (let i = 0; i < root.categories.length; i++) { const thisCategoryName = root.categories[i].name; if (thisCategoryName.startsWith(category) || category.startsWith(thisCategoryName)) { LauncherSearch.ensurePrefix(root.categories[i].prefix); return; } } } property list categories: [ { name: Translation.tr("All"), prefix: "" }, { name: Translation.tr("Apps"), prefix: Config.options.search.prefix.app }, { name: Translation.tr("Actions"), prefix: Config.options.search.prefix.action }, { name: Translation.tr("Clipboard"), prefix: Config.options.search.prefix.clipboard }, { name: Translation.tr("Emojis"), prefix: Config.options.search.prefix.emojis }, { name: Translation.tr("Math"), prefix: Config.options.search.prefix.math }, { name: Translation.tr("Commands"), prefix: Config.options.search.prefix.shellCommand }, { name: Translation.tr("Web"), prefix: Config.options.search.prefix.webSearch }, ] } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml ================================================ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.widgets Scope { id: root Connections { target: GlobalStates function onSearchOpenChanged() { if (GlobalStates.searchOpen) { LauncherSearch.query = ""; panelLoader.active = true; } } } Loader { id: panelLoader active: GlobalStates.searchOpen sourceComponent: PanelWindow { id: panelWindow exclusiveZone: 0 WlrLayershell.namespace: "quickshell:wStartMenu" WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { bottom: Config.options.waffles.bar.bottom top: !Config.options.waffles.bar.bottom left: Config.options.waffles.bar.leftAlignApps } implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight HyprlandFocusGrab { id: focusGrab active: true windows: [panelWindow] onCleared: content.close() } Connections { target: GlobalStates function onSearchOpenChanged() { if (!GlobalStates.searchOpen) content.close(); } } StartMenuContent { id: content anchors.fill: parent focus: true onClosed: { GlobalStates.searchOpen = false; panelLoader.active = false; LauncherSearch.query = ""; } } } } function toggleClipboard() { if (LauncherSearch.query.startsWith(Config.options.search.prefix.clipboard) || !GlobalStates.searchOpen) { GlobalStates.searchOpen = !GlobalStates.searchOpen; } LauncherSearch.ensurePrefix(Config.options.search.prefix.clipboard); } function toggleEmojis() { if (LauncherSearch.query.startsWith(Config.options.search.prefix.emojis) || !GlobalStates.searchOpen) { GlobalStates.searchOpen = !GlobalStates.searchOpen; } LauncherSearch.ensurePrefix(Config.options.search.prefix.emojis); } IpcHandler { target: "search" function toggle() { GlobalStates.searchOpen = !GlobalStates.searchOpen; } function close() { GlobalStates.searchOpen = false; } function open() { GlobalStates.searchOpen = true; } function toggleReleaseInterrupt() { GlobalStates.superReleaseMightTrigger = false; } } GlobalShortcut { name: "searchToggle" description: "Toggles search on press" onPressed: { GlobalStates.searchOpen = !GlobalStates.searchOpen; } } GlobalShortcut { name: "searchToggleRelease" description: "Toggles search on release" onPressed: { GlobalStates.superReleaseMightTrigger = true; } onReleased: { if (!GlobalStates.superReleaseMightTrigger) { GlobalStates.superReleaseMightTrigger = true; return; } GlobalStates.searchOpen = !GlobalStates.searchOpen; } } GlobalShortcut { name: "searchToggleReleaseInterrupt" description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." onPressed: { GlobalStates.superReleaseMightTrigger = false; } } GlobalShortcut { name: "overviewClipboardToggle" description: "Toggle clipboard query on overview widget" onPressed: { root.toggleClipboard(); } } GlobalShortcut { name: "overviewEmojiToggle" description: "Toggle emoji query on overview widget" onPressed: { root.toggleEmojis(); } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchEntryIcon.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.models import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks Item { id: root required property LauncherSearchResult entry property int iconSize: 24 implicitWidth: Math.max(iconSize, textIconLoader.implicitWidth) implicitHeight: iconSize Loader { anchors.centerIn: parent active: root.entry.iconType === LauncherSearchResult.IconType.System && root.entry.iconName !== "" sourceComponent: WAppIcon { implicitSize: root.iconSize iconName: root.entry.iconName tryCustomIcon: false animated: false } } Loader { id: textIconLoader anchors.centerIn: parent active: root.entry.iconType === LauncherSearchResult.IconType.Text sourceComponent: WText { text: root.entry.iconName font.pixelSize: root.iconSize horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } Loader { anchors.centerIn: parent active: root.entry.iconType === LauncherSearchResult.IconType.Material || root.entry.iconType === LauncherSearchResult.IconType.None || root.entry.iconName === "" sourceComponent: FluentIcon { icon: root.entry.iconName ? WIcons.fluentFromMaterial(root.entry.iconName) : WIcons.guessIconForName(root.entry.name) implicitSize: root.iconSize animated: false } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchPageContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks BodyRectangle { id: root property alias context: searchResults.context property string searchText: LauncherSearch.query property alias currentIndex: searchResults.currentIndex ColumnLayout { anchors { fill: parent topMargin: 2 leftMargin: 24 rightMargin: 24 } spacing: 12 TagStrip { context: root.context Layout.fillWidth: true Layout.fillHeight: false } SearchResults { id: searchResults Layout.fillWidth: true Layout.fillHeight: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResultButton.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.models import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WChoiceButton { id: root required property LauncherSearchResult entry property bool firstEntry: false signal requestFocus() checked: focus animateChoiceHighlight: false implicitWidth: contentLayout.implicitWidth + leftPadding + rightPadding implicitHeight: contentLayout.implicitHeight + topPadding + bottomPadding onClicked: { execute(); } function execute() { GlobalStates.searchOpen = false; root.entry.execute(); } horizontalPadding: 0 verticalPadding: 0 contentItem: RowLayout { id: contentLayout spacing: 0 WButton { id: launchButton Layout.fillWidth: true Layout.fillHeight: true horizontalPadding: 10 verticalPadding: 11 implicitHeight: Math.max(root.firstEntry ? 62 : 36, entryContentRow.implicitHeight + 8 * 2) implicitWidth: entryContentRow.implicitWidth + leftPadding + rightPadding topRightRadius: 0 bottomRightRadius: 0 onClicked: root.click() contentItem: Item { RowLayout { id: entryContentRow anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } spacing: 8 SearchEntryIcon { entry: root.entry iconSize: 24 } EntryNameColumn { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } } } } Rectangle { id: separator opacity: (root.hovered && !root.checked) ? 1 : 0 Layout.fillHeight: true implicitWidth: 1 color: ColorUtils.transparentize(Looks.colors.fg, 0.75) } WButton { visible: !root.checked Layout.fillHeight: true implicitWidth: 47 topLeftRadius: 0 bottomLeftRadius: 0 onClicked: root.requestFocus() contentItem: Item { FluentIcon { anchors.centerIn: parent icon: "chevron-right" implicitSize: 14 } } } } component EntryNameColumn: ColumnLayout { spacing: 4 WText { Layout.fillWidth: true wrapMode: Text.Wrap text: root.entry.name font.pixelSize: Looks.font.pixelSize.large maximumLineCount: 2 elide: Text.ElideRight } WText { Layout.fillWidth: true visible: root.firstEntry text: root.entry.type color: Looks.colors.accentUnfocused elide: Text.ElideRight } } MouseArea { anchors.fill: parent // hoverEnabled: true acceptedButtons: Qt.NoButton cursorShape: Qt.PointingHandCursor } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResults.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.waffle.looks import qs.modules.common.functions import qs.modules.common.models import qs.modules.waffle.startMenu import Quickshell import QtQuick.Layouts import QtQuick.Controls import QtQuick RowLayout { id: root property int maxResultsPerCategory: 4 property int resultLimit: 20 property StartMenuContext context property int currentIndex: context.currentIndex onCurrentIndexChanged: { forceCurrentIndex(currentIndex); } function focusFirstItem() { forceCurrentIndex(0); } function forceCurrentIndex(index) { context.currentIndex = index; // Somehow this hack is needed if (index === 0) { resultList.incrementCurrentIndex(); resultList.decrementCurrentIndex(); } else { resultList.decrementCurrentIndex(); resultList.incrementCurrentIndex(); } } Connections { target: context function onAccepted() { resultList.currentItem?.execute(); } } ResultList { id: resultList Layout.fillHeight: true Layout.fillWidth: true } ResultPreview { Layout.preferredWidth: 386 Layout.leftMargin: 1 Layout.rightMargin: 1 entry: resultList.model[resultList.currentIndex] ?? searchResultComp.createObject() } component ResultList: WListView { id: resultListView section { criteria: ViewSection.FullString property: "category" // This is "type" with tweaks to make it match more closely labelPositioning: ViewSection.InlineLabels delegate: Item { id: sectionButton required property string section implicitHeight: sectionChoiceButton.implicitHeight + resultListView.spacing width: ListView.view?.width WChoiceButton { id: sectionChoiceButton anchors { left: parent.left right: parent.right top: parent.top } implicitHeight: 38 contentItem: WText { text: sectionButton.section font.pixelSize: Looks.font.pixelSize.large font.weight: Looks.font.weight.strong } onClicked: { root.context.selectCategory(sectionButton.section); } } } } clip: true spacing: 4 currentIndex: root.currentIndex // We can't use a ScriptModel here because it would mess up sections model: { const allResults = LauncherSearch.results; // Find categories var categories = new Set(); for (let i = 0; i < allResults.length; i++) { categories.add(allResults[i].type); } // Collect max 4 per category var categorizedResults = []; let categoriesArray = Array.from(categories); let totalCount = 0; for (let c = 0; c < categoriesArray.length; c++) { let category = categoriesArray[c]; let count = 0; for (let i = 0; i < allResults.length; i++) { if (allResults[i].type === category) { if (totalCount >= root.resultLimit) { break; } const entry = allResults[i]; const tweakedEntry = searchResultComp.createObject(null, Object.assign({}, entry)); tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr("Best match") : entry.type; categorizedResults.push(tweakedEntry); // Section header count++; totalCount++; if (count >= root.maxResultsPerCategory) { break; } } } if (totalCount >= root.resultLimit) { break; } } // print(JSON.stringify(categorizedResults, null, 2)); return categorizedResults; } onModelChanged: { root.focusFirstItem(); } delegate: SearchResultButton { required property int index required property var modelData entry: modelData firstEntry: index === 0 width: ListView.view?.width checked: resultListView.currentIndex === index onRequestFocus: { root.forceCurrentIndex(index); } } } component ResultPreview: Rectangle { id: resultPreview property LauncherSearchResult entry // LauncherSearchResult Layout.fillHeight: true color: Looks.colors.bg1 radius: Looks.radius.large ColumnLayout { anchors.fill: parent anchors.margins: 22 spacing: 13 ColumnLayout { id: mainInfoColumn Layout.alignment: Qt.AlignHCenter SearchEntryIcon { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 10 Layout.bottomMargin: 12 entry: resultPreview.entry iconSize: 64 } WText { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight wrapMode: Text.Wrap maximumLineCount: 2 text: resultPreview.entry?.name || "" font.pixelSize: Looks.font.pixelSize.xlarger } WText { Layout.alignment: Qt.AlignHCenter text: resultPreview.entry?.type || "" color: Looks.colors.accentUnfocused font.pixelSize: Looks.font.pixelSize.normal } } Rectangle { id: resultSeparator implicitHeight: 2 Layout.topMargin: 16 Layout.fillWidth: true color: Looks.colors.bg2Hover } WListView { id: actionsColumn Layout.fillHeight: true Layout.fillWidth: true clip: true spacing: 2 model: { const isAppEntry = resultPreview.entry.type === Translation.tr("App"); const appId = isAppEntry ? resultPreview.entry.id : ""; const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false; const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false; var result = [ searchResultComp.createObject(null, { name: resultPreview.entry.verb, iconName: isAppEntry ? "open_in_new" : "keyboard_return", iconType: LauncherSearchResult.IconType.Material, execute: () => { resultPreview.entry.execute(); } }), ...(isAppEntry ? [ searchResultComp.createObject(null, { name: startPinned ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start"), iconName: startPinned ? "keep_off" : "keep", iconType: LauncherSearchResult.IconType.Material, execute: () => { LauncherApps.togglePin(appId); } }) ] : []), ...(isAppEntry ? [ searchResultComp.createObject(null, { name: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"), iconName: pinned ? "keep_off" : "keep", iconType: LauncherSearchResult.IconType.Material, execute: () => { TaskbarApps.togglePin(appId); } }) ] : []), ]; result = result.concat(resultPreview.entry.actions); return result; } delegate: WButton { id: actionButton required property var modelData width: ListView.view?.width icon.name: modelData.iconName text: modelData.name onClicked: modelData.execute(); contentItem: RowLayout { spacing: 11 SearchEntryIcon { entry: actionButton.modelData iconSize: 16 } WText { Layout.fillWidth: true horizontalAlignment: Text.AlignLeft text: actionButton.text } } } } } } Component { id: searchResultComp LauncherSearchResult {} } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/TagStrip.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.waffle.looks import qs.modules.waffle.startMenu RowLayout { id: root property StartMenuContext context WPanelIconButton { implicitWidth: 36 implicitHeight: 36 iconSize: 24 iconName: "arrow-left" onClicked: LauncherSearch.query = "" } ListView { id: tagListView Layout.fillWidth: true Layout.fillHeight: true orientation: Qt.Horizontal spacing: 4 model: root.context.categories clip: true delegate: WBorderedButton { id: tagButton required property var modelData border.width: 1 radius: height / 2 implicitWidth: tagButtonText.implicitWidth + 12 * 2 implicitHeight: 32 checked: { if (modelData.prefix != "") { return LauncherSearch.query.startsWith(modelData.prefix); } else { return !tagListView.model.some(i => (i.prefix != "" && LauncherSearch.query.startsWith(i.prefix))); } } contentItem: Item { WText { id: tagButtonText anchors.centerIn: parent color: tagButton.fgColor text: tagButton.modelData.name font.pixelSize: Looks.font.pixelSize.large } } onClicked: LauncherSearch.ensurePrefix(tagButton.modelData.prefix) } } WPanelIconButton { id: optionsButton implicitWidth: 36 implicitHeight: 36 iconSize: 24 iconName: "more-horizontal" onClicked: accountsMenu.open() WMenu { id: accountsMenu x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10 y: optionsButton.height downDirection: true Action { icon.name: "people-settings" text: Translation.tr("Manage accounts") onTriggered: { Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) GlobalStates.searchOpen = false; } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AggregatedAppCategoryModel.qml ================================================ import QtQuick import qs.services QtObject { property string name property list categories } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AllAppsGrid.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks GridLayout { id: root columns: 4 Component { id: aggAppCatComp AggregatedAppCategoryModel {} } property list aggregatedCategories: [ aggAppCatComp.createObject(null, { name: Translation.tr("Productivity"), categories: ["Development", "Education", "Network", "Office"] }), aggAppCatComp.createObject(null, { name: Translation.tr("Utilities & Tools"), categories: ["Utility", "Science"] }), aggAppCatComp.createObject(null, { name: Translation.tr("Creativity"), categories: ["AudioVideo", "Graphics"] }), aggAppCatComp.createObject(null, { name: Translation.tr("System"), categories: ["Settings", "System"] }), aggAppCatComp.createObject(null, { name: Translation.tr("Other"), categories: ["Game"] }), ] Repeater { model: root.aggregatedCategories delegate: AppCategory { required property var modelData aggregatedCategory: modelData } } columnSpacing: 27 rowSpacing: 12 component AppCategory: Item { id: categoryItem property AggregatedAppCategoryModel aggregatedCategory implicitWidth: categoryLayout.implicitWidth implicitHeight: categoryLayout.implicitHeight ColumnLayout { id: categoryLayout anchors.fill: parent spacing: 4 AppCategoryGrid { id: categoryGrid Layout.fillWidth: true aggregatedCategory: categoryItem.aggregatedCategory } WButton { id: categoryButton Layout.fillWidth: true implicitHeight: 32 contentItem: WText { id: categoryButtonText Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight text: categoryItem.aggregatedCategory.name } onClicked: { categoryGrid.openCategoryFolder(); } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AppCategoryGrid.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks Rectangle { id: root property AggregatedAppCategoryModel aggregatedCategory property list desktopEntries: [...DesktopEntries.applications.values.filter(app => { const appCategories = app.categories; const gridCategories = root.aggregatedCategory.categories; return appCategories.some(cat => gridCategories.indexOf(cat) !== -1); })].sort((a, b) => a.name.localeCompare(b.name)); property Item windowRootItem: { var item = root; // print("FINDING ROOT") while (item.parent != null) { if (item.parent.toString().includes("ProxyWindow")) break; item = item.parent; } // print(item.width, item.height) return item; } function openCategoryFolder() { categoryFolderPopup.open(); } radius: Looks.radius.large color: Looks.colors.bg1 border.width: 1 border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7) implicitWidth: 156 implicitHeight: 156 GridLayout { id: categoryAppsGrid anchors.fill: parent anchors.margins: 10 columns: 2 rows: 2 columnSpacing: 0 rowSpacing: 0 uniformCellHeights: true uniformCellWidths: true Repeater { model: ScriptModel { values: root.desktopEntries.slice(0, 3) } delegate: SmallGridAppButton { required property DesktopEntry modelData desktopEntry: modelData } } Loader { id: categoryOpenButtonLoader // It's like this on the real thing - you get an invisible button if there's not enough items opacity: root.desktopEntries.length > 3 ? 1 : 0 active: true sourceComponent: CategoryOpenButton { aggregatedCategory: root.aggregatedCategory } } } Popup { id: categoryFolderPopup // I don't even know what the fuck is going on at this point // I hate point mapping property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2) property point windowCenterPoint: { const rootContentItem = root.windowRootItem; const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2); const sectionItem = root.parent.parent.parent; const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y); const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y); return Qt.point(canvasPosInRoot.x, targetY); } enter: Transition { NumberAnimation { target: categoryFolderPopup property: "x" from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2 to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2 duration: 300 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } NumberAnimation { target: categoryFolderPopup property: "y" from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2 to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2 duration: 300 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } NumberAnimation { target: categoryFolderPopup property: "scale" from: 0 to: 1 duration: 300 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } exit: Transition { NumberAnimation { target: categoryFolderPopup property: "x" to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2 duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } NumberAnimation { target: categoryFolderPopup property: "y" to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2 duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } NumberAnimation { target: categoryFolderPopup property: "scale" from: 1 to: 0 duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut } } background: null Loader { id: folderContentLoader active: categoryFolderPopup.visible sourceComponent: WRectangularShadowThis { CategoryFolderContent { title: root.aggregatedCategory.name desktopEntries: root.desktopEntries } } } } component CategoryFolderContent: WToolTipContent { id: categoryFolderContent property string title property list desktopEntries: root.desktopEntries horizontalPadding: 0 verticalPadding: 0 radius: Looks.radius.large realContentItem: Item { implicitWidth: 448 implicitHeight: 376 ColumnLayout { anchors { fill: parent leftMargin: 32 rightMargin: 32 topMargin: 40 bottomMargin: 32 } spacing: 28 WText { Layout.fillWidth: true text: categoryFolderContent.title font.pixelSize: Looks.font.pixelSize.xlarger font.weight: Looks.font.weight.stronger elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter } Item { Layout.fillWidth: true Layout.fillHeight: true SwipeView { id: categoryFolderSwipeView anchors.fill: parent orientation: Qt.Vertical clip: true Repeater { model: Math.ceil(root.desktopEntries.length / 12) delegate: Item { id: folderPage required property int index width: SwipeView.view.width height: SwipeView.view.height BigAppGrid { anchors { top: parent.top left: parent.left } columns: 4 rows: 3 desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12) } } } } VerticalPageIndicator { anchors.verticalCenter: parent.verticalCenter anchors.right: categoryFolderSwipeView.right anchors.rightMargin: -19 showArrows: false currentIndex: categoryFolderSwipeView.currentIndex count: Math.ceil(root.desktopEntries.length / 12) onClicked: index => categoryFolderSwipeView.currentIndex = index } } } FocusedScrollMouseArea { z: 999 anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: false onScrollUp: categoryFolderSwipeView.decrementCurrentIndex() onScrollDown: categoryFolderSwipeView.incrementCurrentIndex() } } } component CategoryOpenButton: SmallGridButton { id: categoryOpenButton property AggregatedAppCategoryModel aggregatedCategory onClicked: root.openCategoryFolder() contentItem: Item { Behavior on scale { NumberAnimation { id: scaleAnim easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } GridLayout { anchors.centerIn: parent rows: 2 columns: 2 rowSpacing: 2 columnSpacing: 2 Repeater { model: root.desktopEntries.slice(3, 7) delegate: WAppIcon { required property DesktopEntry modelData tryCustomIcon: false iconName: modelData.icon implicitSize: 16 } } } } } component SmallGridAppButton: SmallGridButton { id: smallGridAppButton property DesktopEntry desktopEntry property bool pinnedStart: LauncherApps.isPinned(smallGridAppButton.desktopEntry.id); property bool pinnedTaskbar: TaskbarApps.isPinned(smallGridAppButton.desktopEntry.id); onClicked: { GlobalStates.searchOpen = false; desktopEntry.execute(); } contentItem: Item { Behavior on scale { NumberAnimation { id: scaleAnim easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } } WAppIcon { anchors.centerIn: parent tryCustomIcon: false iconName: smallGridAppButton.desktopEntry.icon implicitSize: 34 } } WToolTip { text: smallGridAppButton.desktopEntry.name } altAction: () => { appMenu.popup(); } WMenu { id: appMenu downDirection: true WMenuItem { icon.name: smallGridAppButton.pinnedStart ? "pin-off" : "pin" text: smallGridAppButton.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start") onTriggered: { LauncherApps.togglePin(smallGridAppButton.desktopEntry.id); } } WMenuItem { icon.name: smallGridAppButton.pinnedTaskbar ? "pin-off" : "pin" text: smallGridAppButton.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar") onTriggered: { TaskbarApps.togglePin(smallGridAppButton.desktopEntry.id); } } } } component SmallGridButton: WButton { id: root implicitWidth: 68 implicitHeight: 68 property real pressedScale: 5 / 6 onDownChanged: { contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/BigAppGrid.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks GridLayout { id: root property list desktopEntries: [] columnSpacing: 0 rowSpacing: 0 uniformCellHeights: true uniformCellWidths: true Repeater { model: root.desktopEntries delegate: StartAppButton { id: pinnedAppButton required property var modelData desktopEntry: modelData onClicked: { GlobalStates.searchOpen = false; desktopEntry.execute(); } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartAppButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WButton { id: root required property DesktopEntry desktopEntry property bool pinnedStart: LauncherApps.isPinned(root.desktopEntry.id); property bool pinnedTaskbar: TaskbarApps.isPinned(root.desktopEntry.id); implicitWidth: 96 implicitHeight: 84 horizontalPadding: 0 verticalPadding: 0 contentItem: ColumnLayout { spacing: 3 WAppIcon { Layout.topMargin: 12 Layout.alignment: Qt.AlignHCenter iconName: root.desktopEntry.icon implicitSize: 34 tryCustomIcon: false } WText { Layout.fillHeight: true Layout.fillWidth: true Layout.leftMargin: 8 Layout.rightMargin: 8 text: root.desktopEntry.name wrapMode: Text.Wrap elide: Text.ElideRight maximumLineCount: 2 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignTop } } WToolTip { text: root.desktopEntry.name } altAction: () => { appMenu.popup() } WMenu { id: appMenu downDirection: true WMenuItem { visible: root.pinnedStart icon.name: "arrow-up-left" text: Translation.tr("Move to front") onTriggered: { LauncherApps.moveToFront(root.desktopEntry.id); } } WMenuItem { visible: root.pinnedStart icon.name: "arrow-left" text: Translation.tr("Move left") onTriggered: { LauncherApps.moveLeft(root.desktopEntry.id); } } WMenuItem { visible: root.pinnedStart icon.name: "arrow-right" text: Translation.tr("Move right") onTriggered: { LauncherApps.moveRight(root.desktopEntry.id); } } WMenuItem { icon.name: root.pinnedStart ? "pin-off" : "pin" text: root.pinnedStart ? Translation.tr("Unpin from Start") : Translation.tr("Pin to Start") onTriggered: { LauncherApps.togglePin(root.desktopEntry.id); } } WMenuItem { icon.name: root.pinnedTaskbar ? "pin-off" : "pin" text: root.pinnedTaskbar ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar") onTriggered: { TaskbarApps.togglePin(root.desktopEntry.id); } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageApps.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks BodyRectangle { id: root ColumnLayout { anchors { fill: parent leftMargin: 32 rightMargin: 32 topMargin: 25 bottomMargin: 30 } spacing: 26 PinnedApps { Layout.fillWidth: true } AllApps { implicitHeight: 300 // for now } } component PinnedApps: PageSection { title: Translation.tr("Pinned") BigAppGrid { Layout.fillWidth: true columns: 8 desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId)) } } component AllApps: PageSection { title: Translation.tr("All") // TODO: Do we wanna also implement list view and grid view? // (instead of only category view) AllAppsGrid { Layout.fillWidth: true Layout.fillHeight: true Layout.leftMargin: 32 Layout.rightMargin: 32 } } component PageSection: ColumnLayout { id: pageSection required property string title default property alias pageData: pageSectionContentArea.data spacing: 16 WText { Layout.leftMargin: 32 text: pageSection.title font.pixelSize: Looks.font.pixelSize.large font.weight: Looks.font.weight.stronger } ColumnLayout { id: pageSectionContentArea Layout.fillWidth: true } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageContent.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WPanelPageColumn { id: root WPanelSeparator {} StartPageApps { Layout.fillHeight: true } WPanelSeparator {} StartFooter { Layout.fillWidth: true } component StartFooter: FooterRectangle { implicitHeight: 63 StartUserButton { anchors { left: parent.left leftMargin: 52 bottom: parent.bottom bottomMargin: 12 } } PowerButton { anchors { right: parent.right rightMargin: 52 bottom: parent.bottom bottomMargin: 12 } } } component PowerButton: WBorderlessButton { id: powerButton implicitWidth: 40 implicitHeight: 40 contentItem: Item { FluentIcon { anchors.centerIn: parent icon: "power" implicitSize: 20 } } WToolTip { extraVisibleCondition: !powerMenu.visible text: qsTr("Power") } onClicked: { powerMenu.open() } WMenu { id: powerMenu x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2 y: -powerMenu.implicitHeight - 4 Action { icon.name: "lock-closed" text: Translation.tr("Lock") onTriggered: Session.lock() } Action { icon.name: "weather-moon" text: Translation.tr("Sleep") onTriggered: Session.suspend() } Action { icon.name: "power" text: Translation.tr("Shut down") onTriggered: Session.poweroff() } Action { icon.name: "arrow-counterclockwise" text: Translation.tr("Restart") onTriggered: Session.reboot() } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartUserButton.qml ================================================ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WBorderlessButton { id: userButton implicitWidth: userButtonRow.implicitWidth + 12 * 2 implicitHeight: 40 contentItem: Item { RowLayout { id: userButtonRow anchors.centerIn: parent spacing: 12 WUserAvatar { sourceSize: Qt.size(32, 32) } WText { Layout.alignment: Qt.AlignVCenter text: SystemInfo.username } } } onClicked: { userMenu.open(); } WToolTip { text: SystemInfo.username } Popup { id: userMenu x: -51 y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10 background: null WToolTipContent { id: popupContent horizontalPadding: 10 verticalPadding: 7 radius: Looks.radius.large realContentItem: Item { implicitWidth: userMenuContentLayout.implicitWidth implicitHeight: userMenuContentLayout.implicitHeight ColumnLayout { id: userMenuContentLayout anchors { fill: parent leftMargin: popupContent.horizontalPadding rightMargin: popupContent.horizontalPadding topMargin: popupContent.verticalPadding bottomMargin: popupContent.verticalPadding } spacing: 5 RowLayout { Layout.fillWidth: true Layout.leftMargin: 6 FluentIcon { Layout.alignment: Qt.AlignVCenter implicitSize: 22 icon: "corporation" monochrome: false } WText { Layout.alignment: Qt.AlignVCenter text: "Megahard" font.pixelSize: Looks.font.pixelSize.large font.weight: Looks.font.weight.strong } Item { Layout.fillWidth: true } WBorderlessButton { Layout.alignment: Qt.AlignVCenter implicitHeight: 36 implicitWidth: textItem.implicitWidth + 10 * 2 contentItem: WText { id: textItem text: Translation.tr("Sign out") font.pixelSize: Looks.font.pixelSize.large } onClicked: Session.logout() } } Item { // Force min width 360 (using min on the item somehow doesn't work) implicitWidth: 334 } RowLayout { Layout.fillWidth: true Layout.bottomMargin: 7 Layout.leftMargin: 6 spacing: 12 WUserAvatar { sourceSize: Qt.size(58, 58) } ColumnLayout { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter spacing: 2 WText { text: SystemInfo.username font.pixelSize: Looks.font.pixelSize.larger font.weight: Looks.font.weight.strong } WText { color: Looks.colors.fg1 text: Translation.tr("Local account") } WText { color: Looks.colors.accent text: Translation.tr("Manage my account") MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: { Quickshell.execDetached(["bash", "-c", Config.options.apps.manageUser]) GlobalStates.searchOpen = false; } } } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewContent.qml ================================================ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.models import qs.modules.common.widgets import qs.modules.waffle.looks import "window-layout.js" as WindowLayout Rectangle { id: root color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5) property bool draggingWindow: false property real openProgress: 0 property Item hoveredWorkspace: null signal closed Component.onCompleted: { openAnim.start(); } function close() { closeAnim.start(); } PropertyAnimation { id: openAnim target: root property: "openProgress" to: 1 duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } SequentialAnimation { id: closeAnim PropertyAnimation { target: root property: "openProgress" to: 0 duration: 250 easing.type: Easing.BezierSpline easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn } ScriptAction { script: { root.closed(); } } } // Windows property real maxWindowHeight: 290 property real maxWindowWidth: 738 property real padding: 52 property real spacing: 25 readonly property list toplevels: ToplevelManager.toplevels.values.filter(t => { const client = HyprlandData.clientForToplevel(t); return client && client.workspace.id === HyprlandData.activeWorkspace?.id; }) readonly property list arrangedToplevels: { const maxRowWidth = width - padding * 2; const count = toplevels.length; const resultLayout = []; var i = 0; while (i < count) { var row = []; var rowWidth = 0; var j = i; while (j < count) { const toplevel = toplevels[j]; const client = HyprlandData.clientForToplevel(toplevel); const scaledSize = WindowLayout.scaleWindow(client, maxWindowWidth, maxWindowHeight); if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) { row.push(toplevel); rowWidth += scaledSize.width; j++; } else { break; } } resultLayout.push(row); i = j; } return resultLayout; } MouseArea { z: 0 anchors.fill: parent onClicked: { GlobalStates.overviewOpen = false; } } // Windows WListView { id: windowListView z: root.openProgress == 1 ? 2 : 1 anchors { left: parent.left right: parent.right top: parent.top topMargin: (root.height - (wsBorder.height + 16) - height) / 2 } spacing: root.spacing topMargin: root.padding bottomMargin: root.padding leftMargin: root.padding rightMargin: root.padding height: Math.min(contentHeight + topMargin + bottomMargin, root.height - (wsBorder.height + 16)) interactive: (height < contentHeight) && !root.draggingWindow clip: root.openProgress > 0.99 && !root.draggingWindow model: ScriptModel { values: root.arrangedToplevels } delegate: RowLayout { id: clientRow required property var modelData spacing: root.spacing anchors.horizontalCenter: parent?.horizontalCenter ?? undefined Repeater { model: ScriptModel { values: clientRow.modelData } delegate: Item { id: clientGridArea required property int index required property var modelData implicitWidth: windowItem.openedSize.width implicitHeight: windowItem.openedSize.height + windowItem.titleBarImplicitHeight TaskViewWindow { id: windowItem z: Drag.active ? 2 : 1 opacity: openAnim.running ? root.openProgress : 1 property int mappedX: { // print("AAAWAWAAWAWWA: ", -(clientRow.x + clientGridArea.x + root.padding)); var rootPosToThis = -(clientRow.x + clientGridArea.x + root.padding); return rootPosToThis + hyprlandClient.at[0]; } property int mappedY: { // print("AAAWAWAAWAWWA YYYY YUIUSDFOIU: ", clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight) var rootPosToThis = -(clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight); return rootPosToThis + hyprlandClient.at[1]; } property int openedX: 0 property int openedY: 0 // property int openedX: Drag.active ? (dragHandler.xAxis.activeValue) : 0 // property int openedY: Drag.active ? (dragHandler.yAxis.activeValue) : 0 scaleSize: (root.openProgress > 0 && !closeAnim.running) x: mappedX + (openedX - mappedX) * root.openProgress y: mappedY + (openedY - mappedY) * root.openProgress droppable: root.hoveredWorkspace !== null Drag.active: dragHandler.active Drag.hotSpot.x: mouseX Drag.hotSpot.y: mouseY DragHandler { id: dragHandler target: null xAxis.onActiveValueChanged: { windowItem.openedX = dragHandler.xAxis.activeValue; } yAxis.onActiveValueChanged: { windowItem.openedY = dragHandler.yAxis.activeValue; } onActiveChanged: { if (active) { root.draggingWindow = true; } else { root.draggingWindow = false; if (root.hoveredWorkspace !== null && root.hoveredWorkspace.workspace !== windowItem.hyprlandClient.workspace.id) { Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${root.hoveredWorkspace.workspace}, follow = false, window = "address:${windowItem.hyprlandClient.address}" })`) } else { windowItem.openedX = 0; windowItem.openedY = 0; } } } } Layout.alignment: Qt.AlignTop maxHeight: root.maxWindowHeight maxWidth: root.maxWindowWidth toplevel: clientGridArea.modelData } } } } } // Workspaces Rectangle { id: wsBorder z: root.openProgress == 1 ? 1 : 2 property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16) anchors { left: parent.left right: parent.right bottom: parent.bottom leftMargin: 8 rightMargin: 8 topMargin: sourceEdgeMargin bottomMargin: sourceEdgeMargin } border.color: Looks.colors.bg2Border border.width: 1 radius: Looks.radius.large color: "transparent" implicitHeight: wsBg.implicitHeight + border.width * 2 Rectangle { id: wsBg anchors.fill: parent anchors.margins: wsBorder.border.width radius: wsBorder.radius - wsBorder.border.width color: Looks.colors.bgPanelFooterBackground implicitHeight: 174 WListView { id: workspaceListView anchors { top: parent.top bottom: parent.bottom horizontalCenter: parent.horizontalCenter topMargin: 5 bottomMargin: 5 } flickableDirection: Flickable.HorizontalFlick orientation: ListView.Horizontal interactive: width == parent.width width: Math.min(contentWidth + leftMargin + rightMargin, parent.width) leftMargin: 5 rightMargin: 5 clip: true spacing: 4 function reposition() { positionViewAtIndex(HyprlandData.activeWorkspace.id - 1, ListView.Contain); } Connections { target: HyprlandData function onActiveWorkspaceChanged() { workspaceListView.reposition(); } } model: IndexModel { id: workspaceIndexModel count: { const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id)); return Math.max(maxWorkspaceId, 1) + 1; } } delegate: TaskViewWorkspace { id: workspaceItem required property int index workspace: index + 1 newWorkspace: index == workspaceIndexModel.count - 1 droppable: root.hoveredWorkspace === workspaceItem DropArea { anchors.fill: parent onEntered: drag => { root.hoveredWorkspace = workspaceItem; } onExited: { if (root.hoveredWorkspace === workspaceItem) { root.hoveredWorkspace = null; } } } onClicked: { GlobalStates.overviewOpen = false; root.closed(); // Close immediately to avoid weird animations Hyprland.dispatch(`hl.dsp.focus({workspace = ${workspaceItem.workspace}})`); } } } } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewWindow.qml ================================================ import QtQuick import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks import "window-layout.js" as WindowLayout WMouseAreaButton { id: root required property var toplevel required property int maxHeight required property int maxWidth property var hyprlandClient: HyprlandData.clientForToplevel(root.toplevel) property string address: hyprlandClient?.address property string iconName: AppSearch.guessIcon(hyprlandClient?.class) color: drag.active ? ColorUtils.transparentize(Looks.colors.bg1Base) : (containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBackground) borderColor: ColorUtils.transparentize(Looks.colors.bg2Border, drag.active ? 1 : 0) radius: Looks.radius.xLarge property real titleBarImplicitHeight: titleBar.implicitHeight property bool scaleSize: true property size openedSize: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight); property size fullSize: Qt.size(hyprlandClient?.size[0] ?? maxWidth, hyprlandClient?.size[1] ?? maxHeight) property size size: scaleSize ? openedSize : fullSize implicitWidth: Math.max(Math.round(contentItem.implicitWidth), 138) implicitHeight: Math.round(contentItem.implicitHeight) layer.enabled: true layer.effect: OpacityMask { maskSource: Item { width: root.background.width height: root.background.height Rectangle { radius: root.background.radius anchors { fill: parent topMargin: root.drag.active ? root.titleBarImplicitHeight : 0 } } } } property bool droppable: false scale: (root.pressedButtons & Qt.LeftButton || root.Drag.active) ? (droppable ? 0.4 : 0.95) : 1 Behavior on scale { NumberAnimation { id: scaleAnim duration: 200 easing.type: Easing.OutExpo } } function closeWindow() { Hyprland.dispatch(`hl.dsp.window.close({window = "address:${root.hyprlandClient?.address}"})`) } acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton onClicked: event => { if (event.button === Qt.LeftButton) { GlobalStates.overviewOpen = false; Hyprland.dispatch(`hl.dsp.focus({window = "address:${root.hyprlandClient?.address}"})`) GlobalStates.overviewOpen = false; } else if (event.button === Qt.MiddleButton) { root.closeWindow(); event.accepted = true; } else if (event.button === Qt.RightButton) { if (!windowMenu.visible) windowMenu.popup(); else windowMenu.close(); } } ColumnLayout { id: contentItem z: 2 anchors.fill: parent anchors.margins: 1 spacing: 0 RowLayout { id: titleBar opacity: root.drag.active ? 0 : 1 spacing: 8 WAppIcon { Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter iconName: root.iconName implicitSize: 16 tryCustomIcon: false } WText { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter elide: Text.ElideRight text: root.hyprlandClient?.title ?? "" } CloseButton { implicitWidth: 38 implicitHeight: 38 padding: 8 onClicked: root.closeWindow() } } ScreencopyView { Layout.fillHeight: true Layout.alignment: Qt.AlignHCenter implicitWidth: Math.round(root.size.width) implicitHeight: Math.round(root.size.height) constraintSize: Qt.size(Math.round(root.size.width), Math.round(root.size.height)) Behavior on implicitWidth { animation: Looks.transition.enter.createObject(this) } Behavior on implicitHeight { animation: Looks.transition.enter.createObject(this) } captureSource: root.toplevel ?? null live: true } } WMenu { id: windowMenu downDirection: true Action { enabled: root.hyprlandClient?.floating property bool isPinned: root.hyprlandClient?.pinned icon.name: isPinned ? "checkmark" : "empty" text: Translation.tr("Show this window on all desktops") onTriggered: { Hyprland.dispatch(`hl.dsp.window.pin({window = "address:${root.hyprlandClient?.address}"})`); } } Action { icon.name: "empty" text: Translation.tr("Close") onTriggered: root.closeWindow() } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewWorkspace.qml ================================================ import QtQuick import QtQuick.Layouts import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import qs import qs.services import qs.modules.common import qs.modules.common.functions import qs.modules.common.widgets import qs.modules.waffle.looks WMouseAreaButton { id: root required property int workspace property bool newWorkspace: false property bool droppable: false readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace readonly property real screenWidth: QsWindow.window?.width ?? 0 readonly property real screenHeight: QsWindow.window?.height ?? 0 readonly property real screenAspectRatio: screenWidth / screenHeight readonly property real windowScale: wallpaperHeight / screenHeight property real wallpaperHeight: 124 height: ListView.view?.height ?? 100 implicitWidth: 244 // for now colBackground: ColorUtils.transparentize(Looks.colors.bg2, (isActiveWorkspace || droppable) ? 0 : 1) Behavior on color { animation: Looks.transition.color.createObject(this) } scale: root.containsPress ? 0.95 : 1 Behavior on scale { NumberAnimation { id: scaleAnim duration: 300 easing.type: Easing.OutExpo } } // Content ColumnLayout { id: contentItem anchors { fill: parent leftMargin: 12 rightMargin: 12 topMargin: 9 bottomMargin: 8 } spacing: 8 WText { Layout.fillWidth: true Layout.fillHeight: false horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: root.newWorkspace ? Translation.tr("New desktop") : Translation.tr("Desktop %1").arg(root.workspace) } Rectangle { id: wsBg height: root.wallpaperHeight Layout.fillHeight: true Layout.fillWidth: true color: Looks.colors.bg1 layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: wsBg.width height: wsBg.height radius: Looks.radius.medium } } // Workspace content Loader { anchors.fill: parent active: !root.newWorkspace sourceComponent: StyledImage { cache: true source: Config.options.background.wallpaperPath fillMode: Image.PreserveAspectCrop Repeater { model: ScriptModel { values: HyprlandData.toplevelsForWorkspace(root.workspace) } delegate: ScreencopyView { required property var modelData readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`] captureSource: modelData live: true width: hyprlandWindowData?.size[0] * root.windowScale height: hyprlandWindowData?.size[1] * root.windowScale x: hyprlandWindowData?.at[0] * root.windowScale y: hyprlandWindowData?.at[1] * root.windowScale } } } } // New plus icon Loader { anchors.centerIn: parent active: root.newWorkspace sourceComponent: FluentIcon { icon: "add" } } Rectangle { z: 2 visible: root.droppable && !root.newWorkspace anchors.fill: parent color: Looks.colors.accent opacity: 0.2 } } } // Active indicator WFadeLoader { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } shown: root.isActiveWorkspace sourceComponent: Rectangle { id: activeIndicator implicitWidth: 32 implicitHeight: 3 color: Looks.colors.accent radius: height / 2 } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/taskView/WaffleTaskView.qml ================================================ pragma ComponentBehavior: Bound import qs import qs.services import qs.modules.common import qs.modules.common.widgets import Qt.labs.synchronizer import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland Scope { id: overviewScope property bool dontAutoCancelSearch: false Variants { id: overviewVariants model: Quickshell.screens Loader { id: panelLoader required property var modelData active: false Connections { target: GlobalStates function onOverviewOpenChanged() { if (GlobalStates.overviewOpen) panelLoader.active = true; } } sourceComponent: PanelWindow { id: root property string searchingText: "" readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) screen: panelLoader.modelData WlrLayershell.namespace: "quickshell:wTaskView" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { top: true bottom: true left: true right: true } TaskViewContent { id: taskViewContent anchors.fill: parent Component.onCompleted: { taskViewContent.forceActiveFocus(); } Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { GlobalStates.overviewOpen = false; } } Connections { target: GlobalStates function onOverviewOpenChanged() { if (!GlobalStates.overviewOpen) taskViewContent.close(); } } onClosed: panelLoader.active = false } } } } IpcHandler { target: "search" function toggle() { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } function workspacesToggle() { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } function close() { GlobalStates.overviewOpen = false; } function open() { GlobalStates.overviewOpen = true; } function toggleReleaseInterrupt() { GlobalStates.superReleaseMightTrigger = false; } function clipboardToggle() { overviewScope.toggleClipboard(); } } GlobalShortcut { name: "overviewWorkspacesToggle" description: "Toggles overview on press" onPressed: { GlobalStates.overviewOpen = !GlobalStates.overviewOpen; } } } ================================================ FILE: dots/.config/quickshell/ii/modules/waffle/taskView/window-layout.js ================================================ function scaleWindow(hyprlandClient, maxWindowWidth, maxWindowHeight) { const [width, height] = hyprlandClient.size; const [xScale, yScale] = [maxWindowWidth / width, maxWindowHeight / height]; const scale = Math.min(xScale, yScale); return Qt.size(width * scale, height * scale) } function arrangedClients(hyprlandClients, maxRowWidth, maxWindowWidth, maxWindowHeight) { const count = hyprlandClients.length; const resultLayout = []; var i = 0; while (i < count) { var row = []; var rowWidth = 0; var j = i; while (j < count) { const client = hyprlandClients[j]; const scaledSize = scaleWindow(client, maxWindowWidth, maxWindowHeight); if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) { row.push(client); rowWidth += scaledSize.width; j++; } else { break; } } resultLayout.push(row); i = j; } return resultLayout; } ================================================ FILE: dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.ii.background import qs.modules.ii.bar import qs.modules.ii.cheatsheet import qs.modules.ii.dock import qs.modules.ii.lock import qs.modules.ii.mediaControls import qs.modules.ii.notificationPopup import qs.modules.ii.onScreenDisplay import qs.modules.ii.onScreenKeyboard import qs.modules.ii.overview import qs.modules.ii.polkit import qs.modules.ii.regionSelector import qs.modules.ii.screenCorners import qs.modules.ii.screenTranslator import qs.modules.ii.sessionScreen import qs.modules.ii.sidebarLeft import qs.modules.ii.sidebarRight import qs.modules.ii.overlay import qs.modules.ii.verticalBar import qs.modules.ii.wallpaperSelector Scope { PanelLoader { extraCondition: !Config.options.bar.vertical; component: Bar {} } PanelLoader { component: Background {} } PanelLoader { component: Cheatsheet {} } PanelLoader { extraCondition: Config.options.dock.enable; component: Dock {} } PanelLoader { component: Lock {} } PanelLoader { component: MediaControls {} } PanelLoader { component: NotificationPopup {} } PanelLoader { component: OnScreenDisplay {} } PanelLoader { component: OnScreenKeyboard {} } PanelLoader { component: Overlay {} } PanelLoader { component: Overview {} } PanelLoader { component: Polkit {} } PanelLoader { component: RegionSelector {} } PanelLoader { component: ScreenCorners {} } PanelLoader { component: ScreenTranslator {} } PanelLoader { component: SessionScreen {} } PanelLoader { component: SidebarLeft {} } PanelLoader { component: SidebarRight {} } PanelLoader { extraCondition: Config.options.bar.vertical; component: VerticalBar {} } PanelLoader { component: WallpaperSelector {} } } ================================================ FILE: dots/.config/quickshell/ii/panelFamilies/PanelLoader.qml ================================================ import QtQuick import Quickshell import qs.modules.common LazyLoader { property bool extraCondition: true active: Config.ready && extraCondition } ================================================ FILE: dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml ================================================ import QtQuick import Quickshell import qs.modules.common import qs.modules.waffle.actionCenter import qs.modules.waffle.background import qs.modules.waffle.bar import qs.modules.waffle.lock import qs.modules.waffle.notificationCenter import qs.modules.waffle.notificationPopup import qs.modules.waffle.onScreenDisplay // import qs.modules.waffle.overlay import qs.modules.waffle.polkit import qs.modules.waffle.screenSnip import qs.modules.waffle.startMenu import qs.modules.waffle.sessionScreen import qs.modules.waffle.taskView // Fallbacks import qs.modules.ii.cheatsheet import qs.modules.ii.onScreenKeyboard import qs.modules.ii.overlay import qs.modules.ii.screenTranslator import qs.modules.ii.wallpaperSelector Scope { PanelLoader { component: WaffleActionCenter {} } PanelLoader { component: WaffleBar {} } PanelLoader { component: WaffleBackground {} } PanelLoader { component: WaffleLock {} } PanelLoader { component: WaffleNotificationCenter {} } PanelLoader { component: WaffleNotificationPopup {} } PanelLoader { component: WaffleOSD {} } // PanelLoader { component: WaffleOverlay {} } PanelLoader { component: WafflePolkit {} } PanelLoader { component: WScreenSnip {} } PanelLoader { component: WaffleStartMenu {} } PanelLoader { component: WaffleSessionScreen {} } PanelLoader { component: WaffleTaskView {} } PanelLoader { component: Cheatsheet {} } PanelLoader { component: OnScreenKeyboard {} } PanelLoader { component: Overlay {} } PanelLoader { component: ScreenTranslator {} } PanelLoader { component: WallpaperSelector {} } } ================================================ FILE: dots/.config/quickshell/ii/scripts/ai/gemini-categorize-wallpaper.sh ================================================ #!/usr/bin/env bash if [[ -z "$1" ]]; then echo "Usage: $0 [model] [prompt]" echo "Tip: set GEMINI_WALLPAPER_MODEL and/or GEMINI_WALLPAPER_PROMPT to provide defaults." exit 1 fi # Variables SOURCE_IMG_PATH="$1" MODEL="${2:-${GEMINI_WALLPAPER_MODEL:-gemini-2.5-flash}}" # We use the flash variant so it's fast WALLPAPER_NAME="$(basename "$SOURCE_IMG_PATH")" PROMPT="${3:-${GEMINI_WALLPAPER_PROMPT:-Categorize the wallpaper. Its file name is $WALLPAPER_NAME}}" RESIZED_IMG_PATH="/tmp/quickshell/ai/wallpaper.jpg" # Resize image for speed mkdir -p "$(dirname "$RESIZED_IMG_PATH")" magick "$SOURCE_IMG_PATH" -resize 200x -quality 50 "$RESIZED_IMG_PATH" # Get API key API_KEY=$(secret-tool lookup 'application' 'illogical-impulse' | jq -r '.apiKeys.gemini') # Encode image to base64 if [[ "$(base64 --version 2>&1)" = *"FreeBSD"* ]]; then B64FLAGS="--input" else B64FLAGS="-w0" fi B64DATA="$(base64 $B64FLAGS $RESIZED_IMG_PATH)" # echo $B64DATA # Prepare request data payload='{ "contents": [{ "parts":[ { "inline_data": { "mime_type":"image/jpeg", "data": "'"$B64DATA"'" } }, {"text": "'"$PROMPT"'"} ] }], "generationConfig": { "responseMimeType": "text/x.enum", "responseSchema": { "type": "string", "enum": [ "abstract", "anime", "city", "minimalist", "landscape", "plants", "person", "space" ] }, "temperature": 0 } }' # echo "$payload" | jq # Make the request response=$(curl "https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent" \ -H "x-goog-api-key: $API_KEY" \ -H 'Content-Type: application/json' \ -X POST \ -d "$payload" 2> /dev/null) # echo "$response" | jq # Write the result echo "$response" | jq -r '.candidates[0].content.parts[0].text' ================================================ FILE: dots/.config/quickshell/ii/scripts/ai/gemini-translate.sh ================================================ #!/usr/bin/env bash if [[ -z "$1" ]]; then echo "Usage: $0 [model]" exit 1 fi # Variables SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" SHELL_CONFIG_DIR="$XDG_CONFIG_HOME/illogical-impulse" SHELL_CONFIG_FILE="${SHELL_CONFIG_DIR}/config.json" TRANSLATIONS_DIR="${SCRIPT_DIR}/../../translations" TRANSLATIONS_TARGET_DIR="${SHELL_CONFIG_DIR}/translations" SOURCE_LOCALE="en_US" NOTIFICATION_APP_NAME="Shell" TARGET_LOCALE="$1" MODEL="${2:-${GEMINI_MODEL:-gemini-2.5-flash}}" # Update the source keys for translation "${TRANSLATIONS_DIR}/tools/manage-translations.sh" update -l "$SOURCE_LOCALE" --yes mkdir -p "$TRANSLATIONS_TARGET_DIR" # Construct the prompt string instruction='You are to translate the user interface of a **desktop shell**. Given a JSON object of key-value pairs, return a JSON with the same structure, with keys unchanged and values translated to '"$TARGET_LOCALE"'. Be as **concise** as possible to save screen space, and make sure terminology is relevant (e.g. "discharging" refers to the battery status).' content=$(cat "${TRANSLATIONS_DIR}/en_US.json") prompt_json=$(jq -n --arg prompt_text "$instruction" --arg content "$content" '$prompt_text + "\n```\n" + $content + "\n```\n"') # Prepare request data using jq payload=$(jq -n \ --arg prompt "$prompt_json" \ --arg temperature "0" \ --arg model "$MODEL" \ '{ contents: [{ parts: [ {text: $prompt} ] }], generationConfig: { temperature: ($temperature | tonumber), "responseMimeType": "application/json", } }' ) # echo "$payload" | jq # Get API key API_KEY=$(secret-tool lookup 'application' 'illogical-impulse' | jq -r '.apiKeys.gemini') # Notify start notify-send "Translation started" "Will take 2 minutes, and you'll be notified when it's done, so feel free to do something else in the meantime." -a "$NOTIFICATION_APP_NAME" # Make the request response=$(curl "https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent" \ -H "x-goog-api-key: $API_KEY" \ -H 'Content-Type: application/json' \ -X POST \ -d "$payload" 2> /dev/null) # echo "$response" | jq # Write the result echo "$response" | jq -r '.candidates[0].content.parts[0].text' > "${TRANSLATIONS_TARGET_DIR}/${TARGET_LOCALE}.json" jq --arg locale "$TARGET_LOCALE" '.language.ui = $locale' "$SHELL_CONFIG_FILE" > "${SHELL_CONFIG_FILE}.tmp" && mv "${SHELL_CONFIG_FILE}.tmp" "$SHELL_CONFIG_FILE" notify-send "Translation complete" "Enjoy! In case you wanna refine it, the file is in ${TRANSLATIONS_TARGET_DIR}/${TARGET_LOCALE}.json" -a "$NOTIFICATION_APP_NAME" ================================================ FILE: dots/.config/quickshell/ii/scripts/ai/show-installed-ollama-models.sh ================================================ #!/usr/bin/env bash # Get the list, skip the header, and extract the first column (model names) model_names=$(ollama list | tail -n +2 | awk '{print $1}') # Build a JSON array json_array="[" for name in $model_names; do json_array+="\"$name\"," done # Remove trailing comma and close the array json_array="${json_array%,}]" # Output the JSON array echo "$json_array" ================================================ FILE: dots/.config/quickshell/ii/scripts/cava/raw_output_config.txt ================================================ [general] mode = waves framerate = 60 autosens = 1 bars = 50 [output] method = raw raw_target = /dev/stdout data_format = ascii channels = mono mono_option = average [smoothing] noise_reduction = 20 ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/applycolor.sh ================================================ #!/usr/bin/env bash QUICKSHELL_CONFIG_NAME="ii" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" term_alpha=100 #Set this to < 100 make all your terminals transparent # sleep 0 # idk i wanted some delay or colors dont get applied properly if [ ! -d "$STATE_DIR"/user/generated ]; then mkdir -p "$STATE_DIR"/user/generated fi cd "$CONFIG_DIR" || exit colornames='' colorstrings='' colorlist=() colorvalues=() colornames=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f1) colorstrings=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d ";" -f1) IFS=$'\n' colorlist=($colornames) # Array of color names colorvalues=($colorstrings) # Array of color values apply_kitty() { # Check if terminal escape sequence template exists if [ ! -f "$SCRIPT_DIR/terminal/kitty-theme.conf" ]; then echo "Template file not found for Kitty theme. Skipping that." return fi # Copy template mkdir -p "$STATE_DIR"/user/generated/terminal cp "$SCRIPT_DIR/terminal/kitty-theme.conf" "$STATE_DIR"/user/generated/terminal/kitty-theme.conf # Apply colors for i in "${!colorlist[@]}"; do sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$STATE_DIR"/user/generated/terminal/kitty-theme.conf done # Reload if ! pgrep -f kitty >/dev/null; then return fi kill -SIGUSR1 $(pidof kitty) } apply_anyterm() { # Check if terminal escape sequence template exists if [ ! -f "$SCRIPT_DIR/terminal/sequences.txt" ]; then echo "Template file not found for Terminal. Skipping that." return fi # Copy template mkdir -p "$STATE_DIR"/user/generated/terminal cp "$SCRIPT_DIR/terminal/sequences.txt" "$STATE_DIR"/user/generated/terminal/sequences.txt # Apply colors for i in "${!colorlist[@]}"; do sed -i "s/${colorlist[$i]} #/${colorvalues[$i]#\#}/g" "$STATE_DIR"/user/generated/terminal/sequences.txt done sed -i "s/\$alpha/$term_alpha/g" "$STATE_DIR/user/generated/terminal/sequences.txt" for file in /dev/pts/*; do if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then { cat "$STATE_DIR"/user/generated/terminal/sequences.txt >"$file" } & disown || true fi done } apply_term() { apply_anyterm & apply_kitty & } # Check if terminal theming is enabled in config CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json" if [ -f "$CONFIG_FILE" ]; then enable_terminal=$(jq -r '.appearance.wallpaperTheming.enableTerminal' "$CONFIG_FILE") if [ "$enable_terminal" = "true" ]; then apply_term & fi else echo "Config file not found at $CONFIG_FILE. Applying terminal theming by default." apply_term & fi # apply_qt & # Qt theming is already handled by kde-material-colors ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/code/material-code-set-color.sh ================================================ #!/usr/bin/env bash COLOR_FILE_PATH="${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/user/generated/color.txt" # Define an array of possible VSCode settings file paths for various forks settings_paths=( "${XDG_CONFIG_HOME:-$HOME/.config}/Code/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/Code - OSS/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/Code - Insiders/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/Cursor/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/Antigravity/User/settings.json" "${XDG_CONFIG_HOME:-$HOME/.config}/Windsurf/User/settings.json" # Add more paths as needed for other forks ) new_color=$(cat "$COLOR_FILE_PATH") # Loop through each settings file path for CODE_SETTINGS_PATH in "${settings_paths[@]}"; do if [[ -f "$CODE_SETTINGS_PATH" ]]; then # Try to update the key if it exists if grep -q '"material-code.primaryColor"' "$CODE_SETTINGS_PATH"; then sed -i -E \ "s/(\"material-code.primaryColor\"\s*:\s*\")[^\"]*(\")/\1${new_color}\2/" \ "$CODE_SETTINGS_PATH" else # If the key is not already there, add it sed -i '$ s/}/,\n "material-code.primaryColor": "'${new_color}'"\n}/' "$CODE_SETTINGS_PATH" sed -i '$ s/,\n,/,/' "$CODE_SETTINGS_PATH" fi fi done ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/generate_colors_material.py ================================================ #!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" import argparse import math import json from PIL import Image from materialyoucolor.quantize import QuantizeCelebi from materialyoucolor.score.score import Score from materialyoucolor.hct import Hct from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors from materialyoucolor.utils.color_utils import (rgba_from_argb, argb_from_rgb, argb_from_rgba) from materialyoucolor.utils.math_utils import (sanitize_degrees_double, difference_degrees, rotation_direction) parser = argparse.ArgumentParser(description='Color generation script') parser.add_argument('--path', type=str, default=None, help='generate colorscheme from image') parser.add_argument('--size', type=int , default=128 , help='bitmap image size') parser.add_argument('--color', type=str, default=None, help='generate colorscheme from color') parser.add_argument('--mode', type=str, choices=['dark', 'light'], default='dark', help='dark or light mode') parser.add_argument('--scheme', type=str, default='vibrant', help='material scheme to use') parser.add_argument('--smart', action='store_true', default=False, help='decide scheme type based on image color') parser.add_argument('--transparency', type=str, choices=['opaque', 'transparent'], default='opaque', help='enable transparency') parser.add_argument('--termscheme', type=str, default=None, help='JSON file containg the terminal scheme for generating term colors') parser.add_argument('--harmony', type=float , default=0.8, help='(0-1) Color hue shift towards accent') parser.add_argument('--harmonize_threshold', type=float , default=100, help='(0-180) Max threshold angle to limit color hue shift') parser.add_argument('--term_fg_boost', type=float , default=0.35, help='Make terminal foreground more different from the background') parser.add_argument('--blend_bg_fg', action='store_true', default=False, help='Shift terminal background or foreground towards accent') parser.add_argument('--cache', type=str, default=None, help='file path to store the generated color') parser.add_argument('--debug', action='store_true', default=False, help='debug mode') args = parser.parse_args() rgba_to_hex = lambda rgba: "#{:02X}{:02X}{:02X}".format(rgba[0], rgba[1], rgba[2]) argb_to_hex = lambda argb: "#{:02X}{:02X}{:02X}".format(*map(round, rgba_from_argb(argb))) hex_to_argb = lambda hex_code: argb_from_rgb(int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:], 16)) display_color = lambda rgba : "\x1B[38;2;{};{};{}m{}\x1B[0m".format(rgba[0], rgba[1], rgba[2], "\x1b[7m \x1b[7m") def calculate_optimal_size (width: int, height: int, bitmap_size: int) -> (int, int): image_area = width * height; bitmap_area = bitmap_size ** 2 scale = math.sqrt(bitmap_area/image_area) if image_area > bitmap_area else 1 new_width = round(width * scale) new_height = round(height * scale) if new_width == 0: new_width = 1 if new_height == 0: new_height = 1 return new_width, new_height def harmonize (design_color: int, source_color: int, threshold: float = 35, harmony: float = 0.5) -> int: from_hct = Hct.from_int(design_color) to_hct = Hct.from_int(source_color) difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue) rotation_degrees = min(difference_degrees_ * harmony, threshold) output_hue = sanitize_degrees_double( from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue) ) return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int() def boost_chroma_tone (argb: int, chroma: float = 1, tone: float = 1) -> int: hct = Hct.from_int(argb) return Hct.from_hct(hct.hue, hct.chroma * chroma, hct.tone * tone).to_int() darkmode = (args.mode == 'dark') transparent = (args.transparency == 'transparent') if args.path is not None: image = Image.open(args.path) if image.format == "GIF": image.seek(1) if image.mode in ["L", "P"]: image = image.convert('RGB') wsize, hsize = image.size wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, args.size) if wsize_new < wsize or hsize_new < hsize: image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC) colors = QuantizeCelebi(list(image.getdata()), 128) argb = Score.score(colors)[0] if args.cache is not None: with open(args.cache, 'w') as file: file.write(argb_to_hex(argb)) hct = Hct.from_int(argb) if(args.smart): if(hct.chroma < 20): args.scheme = 'neutral' elif args.color is not None: argb = hex_to_argb(args.color) hct = Hct.from_int(argb) if args.scheme == 'scheme-fruit-salad': from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme elif args.scheme == 'scheme-expressive': from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme elif args.scheme == 'scheme-monochrome': from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme elif args.scheme == 'scheme-rainbow': from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme elif args.scheme == 'scheme-tonal-spot': from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme elif args.scheme == 'scheme-neutral': from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme elif args.scheme == 'scheme-fidelity': from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme elif args.scheme == 'scheme-content': from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme elif args.scheme == 'scheme-vibrant': from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme else: from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme # Generate scheme = Scheme(hct, darkmode, 0.0) material_colors = {} term_colors = {} for color in vars(MaterialDynamicColors).keys(): color_name = getattr(MaterialDynamicColors, color) if hasattr(color_name, "get_hct"): rgba = color_name.get_hct(scheme).to_rgba() material_colors[color] = rgba_to_hex(rgba) # Extended material if darkmode == True: material_colors['success'] = '#B5CCBA' material_colors['onSuccess'] = '#213528' material_colors['successContainer'] = '#374B3E' material_colors['onSuccessContainer'] = '#D1E9D6' else: material_colors['success'] = '#4F6354' material_colors['onSuccess'] = '#FFFFFF' material_colors['successContainer'] = '#D1E8D5' material_colors['onSuccessContainer'] = '#0C1F13' # Terminal Colors if args.termscheme is not None: with open(args.termscheme, 'r') as f: json_termscheme = f.read() term_source_colors = json.loads(json_termscheme)['dark' if darkmode else 'light'] primary_color_argb = hex_to_argb(material_colors['primary_paletteKeyColor']) for color, val in term_source_colors.items(): if(args.scheme == 'monochrome') : term_colors[color] = val continue if args.blend_bg_fg and color == "term0": harmonized = boost_chroma_tone(hex_to_argb(material_colors['surfaceContainerLow']), 1.2, 0.95) elif args.blend_bg_fg and color == "term15": harmonized = boost_chroma_tone(hex_to_argb(material_colors['onSurface']), 3, 1) else: harmonized = harmonize(hex_to_argb(val), primary_color_argb, args.harmonize_threshold, args.harmony) harmonized = boost_chroma_tone(harmonized, 1, 1 + (args.term_fg_boost * (1 if darkmode else -1))) term_colors[color] = argb_to_hex(harmonized) if args.debug == False: print(f"$darkmode: {darkmode};") print(f"$transparent: {transparent};") for color, code in material_colors.items(): print(f"${color}: {code};") for color, code in term_colors.items(): print(f"${color}: {code};") else: if args.path is not None: print('\n--------------Image properties-----------------') print(f"Image size: {wsize} x {hsize}") print(f"Resized image: {wsize_new} x {hsize_new}") print('\n---------------Selected color------------------') print(f"Dark mode: {darkmode}") print(f"Scheme: {args.scheme}") print(f"Accent color: {display_color(rgba_from_argb(argb))} {argb_to_hex(argb)}") print(f"HCT: {hct.hue:.2f} {hct.chroma:.2f} {hct.tone:.2f}") print('\n---------------Material colors-----------------') for color, code in material_colors.items(): rgba = rgba_from_argb(hex_to_argb(code)) print(f"{color.ljust(32)} : {display_color(rgba)} {code}") print('\n----------Harmonize terminal colors------------') for color, code in term_colors.items(): rgba = rgba_from_argb(hex_to_argb(code)) code_source = term_source_colors[color] rgba_source = rgba_from_argb(hex_to_argb(code_source)) print(f"{color.ljust(6)} : {display_color(rgba_source)} {code_source} --> {display_color(rgba)} {code}") print('-----------------------------------------------') ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/random/random_konachan_wall.sh ================================================ #!/usr/bin/env bash get_pictures_dir() { if command -v xdg-user-dir &> /dev/null; then xdg-user-dir PICTURES return fi local config_file="${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" if [ -f "$config_file" ]; then local pictures_path pictures_path=$(source "$config_file" >/dev/null 2>&1; echo "$XDG_PICTURES_DIR") echo "${pictures_path/#\$HOME/$HOME}" return fi echo "$HOME/Pictures" } QUICKSHELL_CONFIG_NAME="ii" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" PICTURES_DIR=$(get_pictures_dir) CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" mkdir -p "$PICTURES_DIR/Wallpapers" page=$((1 + RANDOM % 1000)); response=$(curl "https://konachan.net/post.json?tags=rating%3Asafe&limit=1&page=$page") link=$(echo "$response" | jq '.[0].file_url' -r); ext=$(echo "$link" | awk -F. '{print $NF}') downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper.$ext" illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json" currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath) if [ "$downloadPath" == "$currentWallpaperPath" ]; then downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext" fi curl "$link" -o "$downloadPath" "$SCRIPT_DIR/../switchwall.sh" --image "$downloadPath" ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/random/random_osu_wall.sh ================================================ #!/usr/bin/env bash get_pictures_dir() { if command -v xdg-user-dir &> /dev/null; then xdg-user-dir PICTURES return fi local config_file="${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" if [ -f "$config_file" ]; then local pictures_path pictures_path=$(source "$config_file" >/dev/null 2>&1; echo "$XDG_PICTURES_DIR") echo "${pictures_path/#\$HOME/$HOME}" return fi echo "$HOME/Pictures" } QUICKSHELL_CONFIG_NAME="ii" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" PICTURES_DIR=$(get_pictures_dir) CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" mkdir -p "$PICTURES_DIR/Wallpapers" response=$(curl "https://osu.ppy.sh/api/v2/seasonal-backgrounds") images=$(echo "$response" | jq '.backgrounds | length' -r); randomIndex=$((RANDOM % images)); link=$(echo "$response" | jq ".backgrounds[$randomIndex].url" -r) ext=$(echo "$link" | awk -F. '{print $NF}') downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper.$ext" illogicalImpulseConfigPath="$HOME/.config/illogical-impulse/config.json" currentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath) if [ "$downloadPath" == "$currentWallpaperPath" ]; then downloadPath="$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext" fi curl "$link" -o "$downloadPath" "$SCRIPT_DIR/../switchwall.sh" --image "$downloadPath" ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/scheme_for_image.py ================================================ #!/usr/bin/env python3 import sys import cv2 import numpy as np # Allowed scheme types SCHEMES = [ "scheme-content", "scheme-expressive", "scheme-fidelity", "scheme-fruit-salad", "scheme-monochrome", "scheme-neutral", "scheme-rainbow", "scheme-tonal-spot" ] def image_colorfulness(image): # Based on Hasler and Süsstrunk's colorfulness metric (B, G, R) = cv2.split(image.astype("float")) rg = np.absolute(R - G) yb = np.absolute(0.5 * (R + G) - B) std_rg = np.std(rg) std_yb = np.std(yb) mean_rg = np.mean(rg) mean_yb = np.mean(yb) colorfulness = np.sqrt(std_rg ** 2 + std_yb ** 2) + (0.3 * np.sqrt(mean_rg ** 2 + mean_yb ** 2)) return colorfulness # scheme-content respects the image's colors very well, but it might # look too saturated, so we only use it for not very colorful images to be safe def pick_scheme(colorfulness): if colorfulness < 40: return "scheme-neutral" else: return "scheme-tonal-spot" def load_and_resize_image(img_path, max_dim=128): img = cv2.imread(img_path) if img is None: return None h, w = img.shape[:2] if max(h, w) > max_dim: scale = max_dim / max(h, w) img = cv2.resize(img, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA) return img def main(): colorfulness_mode = False args = sys.argv[1:] if '--colorfulness' in args: colorfulness_mode = True args.remove('--colorfulness') if len(args) < 1: print("scheme-tonal-spot") sys.exit(1) img_path = args[0] img = load_and_resize_image(img_path) if img is None: print("scheme-tonal-spot") sys.exit(1) colorfulness = image_colorfulness(img) if colorfulness_mode: print(f"{colorfulness}") else: scheme = pick_scheme(colorfulness) print(scheme) if __name__ == "__main__": main() ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/switchwall.sh ================================================ #!/usr/bin/env bash QUICKSHELL_CONFIG_NAME="ii" XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}" XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}" XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}" CONFIG_DIR="$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME" CACHE_DIR="$XDG_CACHE_HOME/quickshell" STATE_DIR="$XDG_STATE_HOME/quickshell" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SHELL_CONFIG_FILE="$XDG_CONFIG_HOME/illogical-impulse/config.json" MATUGEN_DIR="$XDG_CONFIG_HOME/matugen" terminalscheme="$SCRIPT_DIR/terminal/scheme-base.json" handle_kde_material_you_colors() { # Check if Qt app theming is enabled in config if [ -f "$SHELL_CONFIG_FILE" ]; then enable_qt_apps=$(jq -r '.appearance.wallpaperTheming.enableQtApps' "$SHELL_CONFIG_FILE") if [ "$enable_qt_apps" == "false" ]; then return fi fi # Map $type_flag to allowed scheme variants for kde-material-you-colors-wrapper.sh local kde_scheme_variant="" case "$type_flag" in scheme-content|scheme-expressive|scheme-fidelity|scheme-fruit-salad|scheme-monochrome|scheme-neutral|scheme-rainbow|scheme-tonal-spot) kde_scheme_variant="$type_flag" ;; *) kde_scheme_variant="scheme-tonal-spot" # default ;; esac "$XDG_CONFIG_HOME"/matugen/templates/kde/kde-material-you-colors-wrapper.sh --scheme-variant "$kde_scheme_variant" } pre_process() { local mode_flag="$1" # Set GNOME color-scheme if mode_flag is dark or light if [[ "$mode_flag" == "dark" ]]; then gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark' elif [[ "$mode_flag" == "light" ]]; then gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3' fi if [ ! -d "$CACHE_DIR"/user/generated ]; then mkdir -p "$CACHE_DIR"/user/generated fi } post_process() { local screen_width="$1" local screen_height="$2" local wallpaper_path="$3" handle_kde_material_you_colors & "$SCRIPT_DIR/code/material-code-set-color.sh" & } check_and_prompt_upscale() { local img="$1" min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" # max monitor width min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" # max monitor height if command -v identify &>/dev/null && [ -f "$img" ]; then local img_width img_height if is_video "$img"; then # Not check resolution for videos, just let em pass img_width=$min_width_desired img_height=$min_height_desired else img_width=$(identify -format "%w" "$img" 2>/dev/null) img_height=$(identify -format "%h" "$img" 2>/dev/null) fi if [[ "$img_width" -lt "$min_width_desired" || "$img_height" -lt "$min_height_desired" ]]; then action=$(notify-send "Upscale?" \ "Image resolution (${img_width}x${img_height}) is lower than screen resolution (${min_width_desired}x${min_height_desired})" \ -A "open_upscayl=Open Upscayl"\ -a "Wallpaper switcher") if [[ "$action" == "open_upscayl" ]]; then if command -v upscayl &>/dev/null; then nohup upscayl > /dev/null 2>&1 & else action2=$(notify-send \ -a "Wallpaper switcher" \ -c "im.error" \ -A "install_upscayl=Install Upscayl (Arch)" \ "Install Upscayl?" \ "yay -S upscayl-bin") if [[ "$action2" == "install_upscayl" ]]; then kitty -1 yay -S upscayl-bin if command -v upscayl &>/dev/null; then nohup upscayl > /dev/null 2>&1 & fi fi fi fi fi fi } CUSTOM_DIR="$XDG_CONFIG_HOME/hypr/custom" RESTORE_SCRIPT_DIR="$CUSTOM_DIR/scripts" RESTORE_SCRIPT="$RESTORE_SCRIPT_DIR/__restore_video_wallpaper.sh" THUMBNAIL_DIR="$RESTORE_SCRIPT_DIR/mpvpaper_thumbnails" VIDEO_OPTS="no-audio loop hwdec=auto scale=bilinear interpolation=no video-sync=display-resample panscan=1.0 video-scale-x=1.0 video-scale-y=1.0 video-align-x=0.5 video-align-y=0.5 load-scripts=no" is_video() { local extension="${1##*.}" [[ "$extension" == "mp4" || "$extension" == "webm" || "$extension" == "mkv" || "$extension" == "avi" || "$extension" == "mov" ]] && return 0 || return 1 } kill_existing_mpvpaper() { pkill -f -9 mpvpaper || true } create_restore_script() { local video_path=$1 cat > "$RESTORE_SCRIPT.tmp" << EOF #!/bin/bash # Generated by switchwall.sh - Don't modify it by yourself. # Time: $(date) pkill -f -9 mpvpaper for monitor in \$(hyprctl monitors -j | jq -r '.[] | .name'); do mpvpaper -o "$VIDEO_OPTS" "\$monitor" "$video_path" & sleep 0.1 done EOF mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT" chmod +x "$RESTORE_SCRIPT" } remove_restore() { cat > "$RESTORE_SCRIPT.tmp" << EOF #!/bin/bash # The content of this script will be generated by switchwall.sh - Don't modify it by yourself. EOF mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT" } set_wallpaper_path() { local path="$1" if [ -f "$SHELL_CONFIG_FILE" ]; then jq --arg path "$path" '.background.wallpaperPath = $path' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE" fi } set_thumbnail_path() { local path="$1" if [ -f "$SHELL_CONFIG_FILE" ]; then jq --arg path "$path" '.background.thumbnailPath = $path' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE" fi } categorize_wallpaper() { img_cat=$("$SCRIPT_DIR/../ai/gemini-categorize-wallpaper.sh" "$1") # notify-send "Wallpaper category" "$img_cat" echo "$img_cat" > "$STATE_DIR/user/generated/wallpaper/category.txt" } switch() { imgpath="$1" mode_flag="$2" type_flag="$3" color_flag="$4" color="$5" # Start Gemini auto-categorization if enabled aiStylingEnabled=$(jq -r '.background.widgets.clock.cookie.aiStyling' "$SHELL_CONFIG_FILE") if [[ "$aiStylingEnabled" == "true" ]]; then categorize_wallpaper "$imgpath" & fi read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs) cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960 cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1") cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540 cursorposy=$(bc <<< "scale=0; ($cursorposy - $screeny) * $scale / 1") cursorposy_inverted=$((screensizey - cursorposy)) matugen_args=(--source-color-index 0) if [[ "$color_flag" == "1" ]]; then matugen_args+=(color hex "$color") generate_colors_material_args=(--color "$color") else if [[ -z "$imgpath" ]]; then echo 'Aborted' exit 0 fi check_and_prompt_upscale "$imgpath" & kill_existing_mpvpaper if is_video "$imgpath"; then mkdir -p "$THUMBNAIL_DIR" missing_deps=() if ! command -v mpvpaper &> /dev/null; then missing_deps+=("mpvpaper") fi if ! command -v ffmpeg &> /dev/null; then missing_deps+=("ffmpeg") fi if [ ${#missing_deps[@]} -gt 0 ]; then echo "Missing deps: ${missing_deps[*]}" echo "Arch: sudo pacman -S ${missing_deps[*]}" action=$(notify-send \ -a "Wallpaper switcher" \ -c "im.error" \ -A "install_arch=Install (Arch)" \ "Can't switch to video wallpaper" \ "Missing dependencies: ${missing_deps[*]}") if [[ "$action" == "install_arch" ]]; then kitty -1 sudo pacman -S "${missing_deps[*]}" if command -v mpvpaper &>/dev/null && command -v ffmpeg &>/dev/null; then notify-send 'Wallpaper switcher' 'Alright, try again!' -a "Wallpaper switcher" fi fi exit 0 fi # Set wallpaper path set_wallpaper_path "$imgpath" # Set video wallpaper local video_path="$imgpath" monitors=$(hyprctl monitors -j | jq -r '.[] | .name') for monitor in $monitors; do mpvpaper -o "$VIDEO_OPTS" "$monitor" "$video_path" & sleep 0.1 done # Extract first frame for color generation thumbnail="$THUMBNAIL_DIR/$(basename "$imgpath").jpg" ffmpeg -y -i "$imgpath" -vframes 1 "$thumbnail" 2>/dev/null # Set thumbnail path set_thumbnail_path "$thumbnail" if [ -f "$thumbnail" ]; then matugen_args+=(image "$thumbnail") generate_colors_material_args=(--path "$thumbnail") create_restore_script "$video_path" else echo "Cannot create image to colorgen" remove_restore exit 1 fi else matugen_args+=(image "$imgpath") generate_colors_material_args=(--path "$imgpath") # Update wallpaper path in config set_wallpaper_path "$imgpath" remove_restore fi fi # Determine mode if not set if [[ -z "$mode_flag" ]]; then current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d "'") if [[ "$current_mode" == "prefer-dark" ]]; then mode_flag="dark" else mode_flag="light" fi fi # enforce dark mode for terminal if [[ -n "$mode_flag" ]]; then matugen_args+=(--mode "$mode_flag") if [[ $(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode' "$SHELL_CONFIG_FILE") == "true" ]]; then generate_colors_material_args+=(--mode "dark") else generate_colors_material_args+=(--mode "$mode_flag") fi fi [[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag") generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) generate_colors_material_args+=(--cache "$STATE_DIR/user/generated/color.txt") pre_process "$mode_flag" # Check if app and shell theming is enabled in config if [ -f "$SHELL_CONFIG_FILE" ]; then enable_apps_shell=$(jq -r '.appearance.wallpaperTheming.enableAppsAndShell' "$SHELL_CONFIG_FILE") if [ "$enable_apps_shell" == "false" ]; then echo "App and shell theming disabled, skipping matugen and color generation" return fi fi # Set harmony and related properties if [ -f "$SHELL_CONFIG_FILE" ]; then harmony=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.harmony' "$SHELL_CONFIG_FILE") harmonize_threshold=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold' "$SHELL_CONFIG_FILE") term_fg_boost=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost' "$SHELL_CONFIG_FILE") [[ "$harmony" != "null" && -n "$harmony" ]] && generate_colors_material_args+=(--harmony "$harmony") [[ "$harmonize_threshold" != "null" && -n "$harmonize_threshold" ]] && generate_colors_material_args+=(--harmonize_threshold "$harmonize_threshold") [[ "$term_fg_boost" != "null" && -n "$term_fg_boost" ]] && generate_colors_material_args+=(--term_fg_boost "$term_fg_boost") fi matugen "${matugen_args[@]}" source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" python3 "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \ > "$STATE_DIR"/user/generated/material_colors.scss deactivate "$SCRIPT_DIR"/applycolor.sh # Pass screen width, height, and wallpaper path to post_process max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)" max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)" post_process "$max_width_desired" "$max_height_desired" "$imgpath" } main() { imgpath="" mode_flag="" type_flag="" color_flag="" color="" noswitch_flag="" get_type_from_config() { jq -r '.appearance.palette.type' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "auto" } get_accent_color_from_config() { jq -r '.appearance.palette.accentColor' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "" } set_accent_color() { local color="$1" jq --arg color "$color" '.appearance.palette.accentColor = $color' "$SHELL_CONFIG_FILE" > "$SHELL_CONFIG_FILE.tmp" && mv "$SHELL_CONFIG_FILE.tmp" "$SHELL_CONFIG_FILE" } detect_scheme_type_from_image() { local img="$1" source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" "$SCRIPT_DIR"/scheme_for_image.py "$img" 2>/dev/null | tr -d '\n' deactivate } while [[ $# -gt 0 ]]; do case "$1" in --mode) mode_flag="$2" shift 2 ;; --type) type_flag="$2" shift 2 ;; --color) if [[ "$2" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then set_accent_color "$2" shift 2 elif [[ "$2" == "clear" ]]; then set_accent_color "" shift 2 else set_accent_color $(hyprpicker --no-fancy) shift fi ;; --image) imgpath="$2" shift 2 ;; --noswitch) noswitch_flag="1" imgpath=$(jq -r '.background.wallpaperPath' "$SHELL_CONFIG_FILE" 2>/dev/null || echo "") shift ;; *) if [[ -z "$imgpath" ]]; then imgpath="$1" fi shift ;; esac done # If accentColor is set in config, use it config_color="$(get_accent_color_from_config)" if [[ "$config_color" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then color_flag="1" color="$config_color" fi # If type_flag is not set, get it from config if [[ -z "$type_flag" ]]; then type_flag="$(get_type_from_config)" fi # Validate type_flag (allow 'auto' as well) allowed_types=(scheme-content scheme-expressive scheme-fidelity scheme-fruit-salad scheme-monochrome scheme-neutral scheme-rainbow scheme-tonal-spot auto) valid_type=0 for t in "${allowed_types[@]}"; do if [[ "$type_flag" == "$t" ]]; then valid_type=1 break fi done if [[ $valid_type -eq 0 ]]; then echo "[switchwall.sh] Warning: Invalid type '$type_flag', defaulting to 'auto'" >&2 type_flag="auto" fi # Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then cd "$(xdg-user-dir PICTURES)/Wallpapers/showcase" 2>/dev/null || cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1 imgpath="$(kdialog --getopenfilename . --title 'Choose wallpaper')" fi if [[ -n "$imgpath" && -z "$noswitch_flag" ]]; then set_accent_color "" color_flag="" color="" fi # If type_flag is 'auto', detect scheme type from image (after imgpath is set) if [[ "$type_flag" == "auto" ]]; then if [[ -n "$imgpath" && -f "$imgpath" ]]; then detected_type="$(detect_scheme_type_from_image "$imgpath")" # Only use detected_type if it's valid valid_detected=0 for t in "${allowed_types[@]}"; do if [[ "$detected_type" == "$t" && "$detected_type" != "auto" ]]; then valid_detected=1 break fi done if [[ $valid_detected -eq 1 ]]; then type_flag="$detected_type" else echo "[switchwall] Warning: Could not auto-detect a valid scheme, defaulting to 'scheme-tonal-spot'" >&2 type_flag="scheme-tonal-spot" fi else echo "[switchwall] Warning: No image to auto-detect scheme from, defaulting to 'scheme-tonal-spot'" >&2 type_flag="scheme-tonal-spot" fi fi switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color" } main "$@" ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/terminal/kitty-theme.conf ================================================ background #$term0 # color0 #$term0 # color1 #$term1 # color2 #$term2 # color3 #$term3 # color4 #$term4 # color5 #$term5 # color6 #$term6 # color7 #$term7 # color8 #$term8 # color9 #$term9 # color10 #$term10 # color11 #$term11 # color12 #$term12 # color13 #$term13 # color14 #$term14 # color15 #$term15 # cursor #$term7 # foreground #$term7 # selection_background #$onSecondaryContainer # selection_foreground #$secondaryContainer # # Override obscure colors for starship prompt (these are greys at the end) color255 #$primary # color254 #$primaryContainer # color253 #$secondary # color252 #$secondaryContainer # color251 #$tertiary # color250 #$tertiaryContainer # color249 #$error # color248 #$errorContainer # color232 #$onPrimary # color233 #$onPrimaryContainer # color234 #$onSecondary # color235 #$onSecondaryContainer # color236 #$onTertiary # color237 #$onTertiaryContainer # color238 #$onError # color239 #$onErrorContainer # color240 #$onPrimary # # Somehow 232 doesn't work so i gotta use another number # Some stuff should specifically use the colors in the middle so they look acceptable in both unthemed light/dark color243 #$primary # color244 #$error # color245 #$outlineVariant # ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/terminal/scheme-base.json ================================================ { "dark": { "term0" : "#282828", "term1" : "#CC241D", "term2" : "#98971A", "term3" : "#D79921", "term4" : "#458588", "term5" : "#B16286", "term6" : "#689D6A", "term7" : "#A89984", "term8" : "#928374", "term9" : "#FB4934", "term10" : "#B8BB26", "term11" : "#FABD2F", "term12" : "#83A598", "term13" : "#D3869B", "term14" : "#8EC07C", "term15" : "#EBDBB2" }, "light": { "term0" : "#FDF9F3", "term1" : "#FF6188", "term2" : "#A9DC76", "term3" : "#FC9867", "term4" : "#FFD866", "term5" : "#F47FD4", "term6" : "#78DCE8", "term7" : "#333034", "term8" : "#121212", "term9" : "#FF6188", "term10" : "#A9DC76", "term11" : "#FC9867", "term12" : "#FFD866", "term13" : "#F47FD4", "term14" : "#78DCE8", "term15" : "#333034" } } ================================================ FILE: dots/.config/quickshell/ii/scripts/colors/terminal/sequences.txt ================================================ ]4;0;#$term0 #\]1;0;#$term0 #\]4;1;#$term1 #\]4;2;#$term2 #\]4;3;#$term3 #\]4;4;#$term4 #\]4;5;#$term5 #\]4;6;#$term6 #\]4;7;#$term7 #\]4;8;#$term8 #\]4;9;#$term9 #\]4;10;#$term10 #\]4;11;#$term11 #\]4;12;#$term12 #\]4;13;#$term13 #\]4;14;#$term14 #\]4;15;#$term15 #\]4;232;#$term7 #\]4;255;#$primary #\]4;254;#$primaryContainer #\]4;253;#$secondary #\]4;252;#$secondaryContainer #\]4;251;#$tertiary #\]4;250;#$tertiaryContainer #\]4;249;#$error #\]4;248;#$errorContainer #\]4;232;#$onPrimary #\]4;233;#$onPrimaryContainer #\]4;234;#$onSecondary #\]4;235;#$onSecondaryContainer #\]4;236;#$onTertiary #\]4;237;#$onTertiaryContainer #\]4;238;#$onError #\]4;239;#$onErrorContainer #\]4;240;#$onPrimary #\]4;243;#$primary #\]4;244;#$error #\]4;245;#$outlineVariant #\]10;#$term7 #\]11;[100]#$term0 #\]12;#$term7 #\]13;#$term7 #\]17;#$term7 #\]19;#$term0 #\]708;[100]#$term0 # ================================================ FILE: dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py ================================================ #!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" import argparse import re import os import tempfile def format_value(value): """Format value: quote strings, leave numbers and booleans as-is""" if value in ('true', 'false'): return value try: float(value) return value except ValueError: return f'"{value}"' def build_nested_structure(key_parts, value): """Recursively build nested structure from key parts""" if len(key_parts) == 1: return f'{key_parts[0]}={format_value(value)}' else: return f'{key_parts[0]}={{{build_nested_structure(key_parts[1:], value)}}}' def generate_config_line(key, value): """Generate hl.config line for given key and value""" key_parts = key.split(':') nested_structure = build_nested_structure(key_parts, value) return f'hl.config({{{nested_structure}}})\n' def edit_hyprland_config(file_path, set_args, reset_args): if os.path.exists(file_path): with open(file_path, 'r') as file: lines = file.readlines() else: lines = [] set_dict = {k: v for k, v in set_args} if set_args else {} reset_set = set(reset_args) if reset_args else set() new_lines = [] found_keys = set() patterns = {} for k in list(set_dict.keys()) + list(reset_set): key_parts = k.split(':') main_key = key_parts[0] if len(key_parts) > 1: # Build pattern to match nested structure pattern_parts = [rf'\s*{re.escape(part)}\s*=' for part in key_parts] nested_pattern = '\{'.join(pattern_parts) patterns[k] = re.compile(rf'^\s*hl\.config\(\{{\s*{nested_pattern}') else: patterns[k] = re.compile(rf'^\s*hl\.config\(\{{\s*{re.escape(main_key)}\s*=') for line in lines: matched = False # Check if line matches a key to be reset for key in reset_set: if patterns[key].match(line): matched = True break if matched: continue # Check if line matches a key to be set for key, value in set_dict.items(): if patterns[key].match(line): new_line = generate_config_line(key, value) new_lines.append(new_line) found_keys.add(key) matched = True break if matched: continue new_lines.append(line) if set_dict: for key, value in set_dict.items(): if key not in found_keys: if new_lines and not new_lines[-1].endswith('\n'): new_lines[-1] += '\n' new_lines.append(generate_config_line(key, value)) dir_name = os.path.dirname(os.path.abspath(file_path)) os.makedirs(dir_name, exist_ok=True) temp_path = None try: with tempfile.NamedTemporaryFile(mode='w', dir=dir_name, delete=False) as temp_file: temp_file.writelines(new_lines) temp_path = temp_file.name if os.path.exists(file_path): os.chmod(temp_path, os.stat(file_path).st_mode) else: os.chmod(temp_path, 0o644) os.replace(temp_path, file_path) except Exception as e: if temp_path and os.path.exists(temp_path): os.remove(temp_path) print(f"Error saving file: {e}") return for key in reset_set: print(f"Removed '{key}' from '{file_path}'") for key, value in set_dict.items(): print(f"Updated '{file_path}' with {generate_config_line(key, value).strip()}") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Edit a Hyprland config file. Subkeys use colon (:) for nesting.") parser.add_argument("--file", default="~/.config/hypr/hyprland.conf", help="Path to the Hyprland config file (default: ~/.config/hypr/hyprland.conf).") parser.add_argument("--set", nargs=2, action="append", metavar=("KEY", "VALUE"), help="Set a configuration key to a value.") parser.add_argument("--reset", action="append", metavar="KEY", help="Remove a configuration key.") args = parser.parse_args() file_path = os.path.expanduser(args.file) raw_set_args = args.set or [] reset_args = args.reset or [] set_args = [] for key, value in raw_set_args: if value == "[[EMPTY]]": reset_args.append(key) else: set_args.append((key, value)) if not set_args and not reset_args: print("Error: Must specify at least one key to set or reset.") else: edit_hyprland_config(file_path, set_args, reset_args) ================================================ FILE: dots/.config/quickshell/ii/scripts/images/find-regions-venv.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate "$SCRIPT_DIR/find_regions.py" "$@" deactivate ================================================ FILE: dots/.config/quickshell/ii/scripts/images/find_regions.py ================================================ #!/usr/bin/env python3 import argparse import cv2 import json import numpy as np import sys DEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image' def iou(boxA, boxB): # Compute intersection over union for two boxes xA = max(boxA['x'], boxB['x']) yA = max(boxA['y'], boxB['y']) xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width']) yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height']) interW = max(0, xB - xA) interH = max(0, yB - yA) interArea = interW * interH boxAArea = boxA['width'] * boxA['height'] boxBArea = boxB['width'] * boxB['height'] iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0 return iou def non_max_suppression(regions, iou_threshold=0.7): # Sort by area (largest first) regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True) keep = [] while regions: current = regions.pop(0) keep.append(current) regions = [r for r in regions if iou(current, r) < iou_threshold] return keep def find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0): image = cv2.imread(image_path) if image is None: print(f'Error: Could not load image {image_path}', file=sys.stderr) sys.exit(1) orig_h, orig_w = image.shape[:2] if resize_factor != 1.0: image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA) ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation() ss.setBaseImage(image) if quality: ss.switchToSelectiveSearchQuality(k, min_size, sigma) else: ss.switchToSelectiveSearchFast(k, min_size, sigma) rects = ss.process() regions = [] for (x, y, w, h) in rects: # Scale regions back to original image size if resized if resize_factor != 1.0: x = int(x / resize_factor) y = int(y / resize_factor) w = int(w / resize_factor) h = int(h / resize_factor) # Filter out region that is exactly the same size as the original image if w == orig_w and h == orig_h and x == 0 and y == 0: continue if w > min_width and h > min_height: if (max_width is None or w < max_width) and (max_height is None or h < max_height): regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)}) # Remove duplicates/overlaps regions = non_max_suppression(regions, iou_threshold=0.7) return regions, cv2.imread(image_path) # Return original image for drawing def draw_regions(image, regions, output_path): for region in regions: if 'x' in region: x, y, w, h = region['x'], region['y'], region['width'], region['height'] elif 'at' in region and 'size' in region: x, y = region['at'] w, h = region['size'] else: continue cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2) cv2.imwrite(output_path, image) def main(): parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.') parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image') parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles') parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region') parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region') parser.add_argument('--max-width', type=int, help='Maximum width of detected region') parser.add_argument('--max-height', type=int, help='Maximum height of detected region') parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region') parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)') parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)') parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)') parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)') parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)') parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}') args = parser.parse_args() regions, image = find_regions( args.image, min_width=args.min_width, min_height=args.min_height, max_width=args.max_width, max_height=args.max_height, quality=args.quality, k=args.k, min_size=args.min_size, sigma=args.sigma, resize_factor=args.resize_factor ) if args.single and regions: largest = max(regions, key=lambda r: r['width'] * r['height']) regions = [largest] if args.hyprctl: regions = [{"at": [r['x'], r['y']], "size": [r['width'], r['height']]} for r in regions] print(json.dumps(regions)) if args.debug_output: draw_regions(image, regions, args.debug_output) if __name__ == '__main__': main() ================================================ FILE: dots/.config/quickshell/ii/scripts/images/least-busy-region-venv.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate "$SCRIPT_DIR/least_busy_region.py" "$@" deactivate ================================================ FILE: dots/.config/quickshell/ii/scripts/images/least_busy_region.py ================================================ #!/usr/bin/env python3 # Disclaimer: This script was ai-generated and went through minimal revision. import os os.environ["OPENCV_LOG_LEVEL"] = "SILENT" import cv2 import numpy as np import argparse import json def center_crop(img, target_w, target_h): h, w = img.shape[:2] if w == target_w and h == target_h: return img x1 = max(0, (w - target_w) // 2) y1 = max(0, (h - target_h) // 2) x2 = x1 + target_w y2 = y1 + target_h return img[y1:y2, x1:x2] def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", horizontal_padding=50, vertical_padding=50, busiest=False): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") orig_h, orig_w = img.shape scale = 1.0 if screen_width is not None and screen_height is not None: scale_w = screen_width / orig_w scale_h = screen_height / orig_h if screen_mode == "fill": scale = max(scale_w, scale_h) else: scale = min(scale_w, scale_h) new_w = int(orig_w * scale) new_h = int(orig_h * scale) if verbose: print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) img = center_crop(img, screen_width, screen_height) if verbose: print(f"Cropped image to {screen_width}x{screen_height}") else: if verbose: print(f"Using original image size: {orig_w}x{orig_h}") arr = img.astype(np.float64) h, w = arr.shape # Validate & adjust stride stride = max(1, int(stride) if stride else 1) # Adjust region size if it does not fit given padding if horizontal_padding * 2 >= w or vertical_padding * 2 >= h: # Reduce padding to fit at least a 1x1 region horizontal_padding = max(0, min(horizontal_padding, (w - 1) // 2)) vertical_padding = max(0, min(vertical_padding, (h - 1) // 2)) max_region_w = w - 2 * horizontal_padding max_region_h = h - 2 * vertical_padding if max_region_w <= 0 or max_region_h <= 0: raise ValueError("Image too small for the specified padding.") if region_width > max_region_w: if verbose: print(f"Requested region_width {region_width} too large; clamping to {max_region_w}") region_width = max_region_w if region_height > max_region_h: if verbose: print(f"Requested region_height {region_height} too large; clamping to {max_region_h}") region_height = max_region_h # Use OpenCV's integral for fast computation integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:] integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:] def region_sum(ii, x1, y1, x2, y2): # Assume bounds have been checked before calling total = ii[y2, x2] if x1 > 0: total -= ii[y2, x1-1] if y1 > 0: total -= ii[y1-1, x2] if x1 > 0 and y1 > 0: total += ii[y1-1, x1-1] return total min_var = None max_var = None min_coords = (horizontal_padding, vertical_padding) max_coords = (horizontal_padding, vertical_padding) area = region_width * region_height x_start = horizontal_padding y_start = vertical_padding x_end = w - region_width - horizontal_padding + 1 y_end = h - region_height - vertical_padding + 1 if x_end < x_start: x_end = x_start if y_end < y_start: y_end = y_start for y in range(y_start, y_end + 1, stride): for x in range(x_start, x_end + 1, stride): x1, y1 = x, y x2, y2 = x + region_width - 1, y + region_height - 1 if x2 >= w or y2 >= h: continue # Skip out-of-bounds window s = region_sum(integral, x1, y1, x2, y2) s2 = region_sum(integral_sq, x1, y1, x2, y2) mean = s / area var = (s2 / area) - (mean ** 2) if (min_var is None) or (var < min_var): min_var = var min_coords = (x, y) if (max_var is None) or (var > max_var): max_var = var max_coords = (x, y) if busiest: return max_coords, max_var else: return min_coords, min_var def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50): img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") orig_h, orig_w = img.shape # ...existing scaling logic... scale = 1.0 if screen_width is not None and screen_height is not None: scale_w = screen_width / orig_w scale_h = screen_height / orig_h if screen_mode == "fill": scale = max(scale_w, scale_h) else: scale = min(scale_w, scale_h) new_w = int(orig_w * scale) new_h = int(orig_h * scale) if verbose: print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})") img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) img = center_crop(img, screen_width, screen_height) if verbose: print(f"Cropped image to {screen_width}x{screen_height}") else: if verbose: print(f"Using original image size: {orig_w}x{orig_h}") arr = img.astype(np.float64) h, w = arr.shape stride = max(1, int(stride) if stride else 1) threshold = max(0.0, float(threshold)) # Adjust padding if image too small if horizontal_padding * 2 >= w or vertical_padding * 2 >= h: horizontal_padding = max(0, min(horizontal_padding, (w - 1) // 2)) vertical_padding = max(0, min(vertical_padding, (h - 1) // 2)) # Use OpenCV's integral for fast computation integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:] integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:] def region_sum(ii, x1, y1, x2, y2): total = ii[y2, x2] if x1 > 0: total -= ii[y2, x1-1] if y1 > 0: total -= ii[y1-1, x2] if x1 > 0 and y1 > 0: total += ii[y1-1, x1-1] return total min_size = 10 # Determine maximum feasible size respecting padding effective_w = w - 2 * horizontal_padding effective_h = h - 2 * vertical_padding if effective_w <= 0 or effective_h <= 0: return None, (0, 0), None # Largest square-ish dimension given aspect ratio and effective space if aspect_ratio >= 1.0: max_size = min(effective_h, int(effective_w / aspect_ratio)) else: max_size = min(int(effective_h * aspect_ratio), effective_w) if max_size < min_size: min_size = 1 max_size = max(1, max_size) best = None while min_size <= max_size: mid = (min_size + max_size) // 2 if aspect_ratio >= 1.0: region_h = mid region_w = int(round(mid * aspect_ratio)) else: region_w = mid region_h = int(round(mid / aspect_ratio if aspect_ratio != 0 else mid)) if region_w <= 0 or region_h <= 0: break if region_w > effective_w or region_h > effective_h: max_size = mid - 1 continue found = False x_start = horizontal_padding y_start = vertical_padding x_end = w - region_w - horizontal_padding y_end = h - region_h - vertical_padding for y in range(y_start, y_end + 1, stride): for x in range(x_start, x_end + 1, stride): x1, y1 = x, y x2, y2 = x + region_w - 1, y + region_h - 1 if x2 >= w or y2 >= h: continue s = region_sum(integral, x1, y1, x2, y2) s2 = region_sum(integral_sq, x1, y1, x2, y2) area = region_w * region_h mean = s / area var = (s2 / area) - (mean ** 2) if var <= threshold: found = True best = (x, y, region_w, region_h, var) break if found: break if found: min_size = mid + 1 else: max_size = mid - 1 if best: x, y, region_w, region_h, var = best center_x = x + region_w // 2 center_y = y + region_h // 2 return (center_x, center_y), (region_w, region_h), var else: return None, (0, 0), None def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"): img = cv2.imread(image_path) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") orig_h, orig_w = img.shape[:2] if screen_width is not None and screen_height is not None: scale_w = screen_width / orig_w scale_h = screen_height / orig_h if screen_mode == "fill": scale = max(scale_w, scale_h) else: scale = min(scale_w, scale_h) new_w = int(orig_w * scale) new_h = int(orig_h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) img = center_crop(img, screen_width, screen_height) x, y = coords cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3) cv2.imwrite(output_path, img) # print removed for quieter operation def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"): img = cv2.imread(image_path) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") orig_h, orig_w = img.shape[:2] if screen_width is not None and screen_height is not None: scale_w = screen_width / orig_w scale_h = screen_height / orig_h if screen_mode == "fill": scale = max(scale_w, scale_h) else: scale = min(scale_w, scale_h) new_w = int(orig_w * scale) new_h = int(orig_h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) img = center_crop(img, screen_width, screen_height) cx, cy = center region_w, region_h = size x1 = cx - region_w // 2 y1 = cy - region_h // 2 x2 = cx + region_w // 2 - 1 y2 = cy + region_h // 2 - 1 cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3) cv2.imwrite(output_path, img) # print removed for quieter operation def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"): img = cv2.imread(image_path) if img is None: raise FileNotFoundError(f"Image not found: {image_path}") orig_h, orig_w = img.shape[:2] if screen_width is not None and screen_height is not None: scale_w = screen_width / orig_w scale_h = screen_height / orig_h if screen_mode == "fill": scale = max(scale_w, scale_h) else: scale = min(scale_w, scale_h) new_w = int(orig_w * scale) new_h = int(orig_h * scale) img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4) img = center_crop(img, screen_width, screen_height) # Ensure region is within bounds x = max(0, x) y = max(0, y) w = max(1, min(w, img.shape[1] - x)) h = max(1, min(h, img.shape[0] - y)) region = img[y:y+h, x:x+w] if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0: return [0, 0, 0] region = region.reshape((-1, 3)) # Filter out black pixels (optional, improves accuracy for some images) non_black = region[np.any(region > 10, axis=1)] if non_black.shape[0] == 0: non_black = region region = np.float32(non_black) if region.shape[0] < 3: return [int(x) for x in np.mean(region, axis=0)] # K-means to find dominant color criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0) K = min(3, region.shape[0]) _, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) counts = np.bincount(labels.flatten()) dominant = centers[np.argmax(counts)] # Reverse from BGR to RGB return [int(x) for x in reversed(dominant)] def main(): parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.") parser.add_argument("image_path", help="Path to the input image") parser.add_argument("--width", type=int, default=300, help="Region width") parser.add_argument("--height", type=int, default=200, help="Region height") parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle") parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling") parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling") parser.add_argument("--stride", type=int, default=10, help="Step size for sliding window (higher is faster, less precise)") parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'") parser.add_argument("--verbose", action="store_true", help="Print verbose output") parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center") parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode") parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode") parser.add_argument("--horizontal-padding", "-hp", type=int, default=50, help="Minimum horizontal distance from region to image edge") parser.add_argument("--vertical-padding", "-vp", type=int, default=50, help="Minimum vertical distance from region to image edge") parser.add_argument("--busiest", action="store_true", help="Find the busiest region instead of the least busy") args = parser.parse_args() if args.largest_region: center, size, var = find_largest_region( args.image_path, screen_width=args.screen_width, screen_height=args.screen_height, verbose=args.verbose, stride=args.stride, screen_mode=args.screen_mode, threshold=args.variance_threshold, aspect_ratio=args.aspect_ratio, horizontal_padding=args.horizontal_padding, vertical_padding=args.vertical_padding ) if center: if args.visual_output: draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) # Extract dominant color cx, cy = center region_w, region_h = size x1 = cx - region_w // 2 y1 = cy - region_h // 2 dominant_color = get_dominant_color( args.image_path, x1, y1, region_w, region_h, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode ) dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color) print(json.dumps({ "center_x": center[0], "center_y": center[1], "width": size[0], "height": size[1], "variance": var, "dominant_color": dominant_color_hex })) else: print(json.dumps({"error": "No region found under the threshold."})) return coords, variance = find_least_busy_region( args.image_path, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, verbose=args.verbose, stride=args.stride, screen_mode=args.screen_mode, horizontal_padding=args.horizontal_padding, vertical_padding=args.vertical_padding, busiest=args.busiest ) if args.visual_output: draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode) # Output JSON with center point center_x = coords[0] + args.width // 2 center_y = coords[1] + args.height // 2 dominant_color = get_dominant_color( args.image_path, coords[0], coords[1], args.width, args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode ) dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color) print(json.dumps({ "center_x": center_x, "center_y": center_y, "width": args.width, "height": args.height, "variance": variance, "dominant_color": dominant_color_hex })) if __name__ == "__main__": main() ================================================ FILE: dots/.config/quickshell/ii/scripts/images/text-color-venv.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate "$SCRIPT_DIR/text_color.py" "$@" deactivate ================================================ FILE: dots/.config/quickshell/ii/scripts/images/text_color.py ================================================ #!/usr/bin/env python3 # Disclaimer: This script was ai-generated and went through minimal revision. import cv2 import numpy as np import json import sys def to_hex(color): return "#{:02x}{:02x}{:02x}".format(int(color[0]), int(color[1]), int(color[2])) def get_color_from_stdin(): # Read raw bytes from stdin input_data = sys.stdin.buffer.read() if not input_data: return {"error": "No data received via stdin"} # Convert bytes to numpy array and decode to image nparr = np.frombuffer(input_data, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if img is None: return {"error": "Could not decode image data"} img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w, _ = img_rgb.shape # 1. Sample corner pixels (The background anchors) corners = np.array([ img_rgb[0, 0], img_rgb[0, w-1], img_rgb[h-1, 0], img_rgb[h-1, w-1] ]) # 2. Determine single dominant background # Using median handles noise/gradients better than a simple average bg_color = np.median(corners, axis=0).astype(int) # 3. Find the Text Color pixels = img_rgb.reshape(-1, 3).astype(int) distances = np.linalg.norm(pixels - bg_color, axis=1) # Take the 95th percentile of pixels furthest from background threshold = np.percentile(distances, 95) text_pixels = pixels[distances >= threshold] if len(text_pixels) == 0: text_color = [255, 255, 255] # Fallback else: text_color = np.median(text_pixels, axis=0).astype(int) return { "background": to_hex(bg_color), "text": to_hex(text_color) } if __name__ == "__main__": result = get_color_from_stdin() print(json.dumps(result)) ================================================ FILE: dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh ================================================ #!/usr/bin/env bash locked_state=$(busctl --user get-property org.freedesktop.secrets \ /org/freedesktop/secrets/collection/login \ org.freedesktop.Secret.Collection Locked) if [[ "${locked_state}" == "b false" ]]; then echo 'Keyring is unlocked' >&2 exit 0 else echo 'Keyring is locked' >&2 exit 1 fi ================================================ FILE: dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" data=$(secret-tool lookup 'application' 'illogical-impulse') if [[ -z "$data" ]]; then if "${SCRIPT_DIR}/is_unlocked.sh"; then echo 'not found' exit 1 else echo 'locked' exit 2 fi fi echo "$data" ================================================ FILE: dots/.config/quickshell/ii/scripts/keyring/unlock.sh ================================================ #!/usr/bin/env bash # Based on https://unix.stackexchange.com/a/602935 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Skip if already unlocked if "${SCRIPT_DIR}/is_unlocked.sh"; then exit 1 fi # Prompt for password if not provided if [[ -z "${UNLOCK_PASSWORD}" ]]; then echo -n 'Login password: ' >&2 read -s UNLOCK_PASSWORD || return fi # Unlock killall -q -u "$(whoami)" gnome-keyring-daemon eval $(echo -n "${UNLOCK_PASSWORD}" \ | gnome-keyring-daemon --daemonize --login \ | sed -e 's/^/export /') unset UNLOCK_PASSWORD echo '' >&2 ================================================ FILE: dots/.config/quickshell/ii/scripts/musicRecognition/recognize-music.sh ================================================ #!/bin/bash INTERVAL=2 TOTAL_DURATION=30 SOURCE_TYPE="monitor" # monitor | input FIFO=$(mktemp -u /tmp/songrec_out_XXXXXX) while getopts "i:t:s:" opt; do case $opt in i) INTERVAL=$OPTARG ;; t) TOTAL_DURATION=$OPTARG ;; s) SOURCE_TYPE=$OPTARG ;; *) exit 1 ;; esac done if ! command -v songrec >/dev/null 2>&1; then exit 1 fi if [ "$SOURCE_TYPE" = "monitor" ]; then AUDIO_DEVICE=$(pactl get-default-sink).monitor elif [ "$SOURCE_TYPE" = "input" ]; then AUDIO_DEVICE=$(pactl info | grep "Default Source:" | awk '{print $3}' || true) else echo "Invalid source type" exit 1 fi if [ -z "$AUDIO_DEVICE" ] || ! pactl list short sources | grep -q "$AUDIO_DEVICE"; then exit 1 fi mkfifo "$FIFO" cleanup() { kill "$SONGREC_PID" 2>/dev/null || true wait "$SONGREC_PID" 2>/dev/null rm -f "$FIFO" } trap cleanup EXIT songrec listen --audio-device "$AUDIO_DEVICE" --request-interval "$INTERVAL" --json --disable-mpris > "$FIFO" & SONGREC_PID=$! ( sleep "$TOTAL_DURATION" && kill "$SONGREC_PID" 2>/dev/null ) & while IFS= read -r line; do if echo "$line" | grep -q '"matches": \['; then echo "$line" exit 0 fi done < "$FIFO" exit 0 ================================================ FILE: dots/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh ================================================ #!/usr/bin/env bash # Generate thumbnails for files using ImageMagick, following Freedesktop spec # Usage: # ./generate-thumbnails-magick.sh --file # ./generate-thumbnails-magick.sh --directory set -e # Thumbnail sizes mapping get_thumbnail_size() { case "$1" in normal) echo 128 ;; large) echo 256 ;; x-large) echo 512 ;; xx-large) echo 1024 ;; *) echo 128 ;; esac } usage() { echo "Usage: $0 --file | --directory " exit 1 } md5() { # Calculate md5 hash of the file's absolute path echo -n "$1" | md5sum | awk '{print $1}' } urlencode() { # Percent-encode a string for use in a URI, but do not encode slashes local str="$1" local encoded="" local c for ((i=0; i<${#str}; i++)); do c="${str:$i:1}" case "$c" in [a-zA-Z0-9.~_-]|/|'('|')'|'*') encoded+="$c" ;; *) printf -v hex '%%%02X' "'${c}'"; encoded+="$hex" ;; esac done echo "$encoded" } generate_thumbnail() { local src="$1" local abs_path abs_path="$(realpath "$src")" # Skip files with multiple frames (GIFs, videos, etc.) case "${abs_path,,}" in *.gif|*.mp4|*.webm|*.mkv|*.avi|*.mov) return ;; esac local encoded_path encoded_path="$(urlencode "$abs_path")" local uri uri="file://$encoded_path" local hash hash="$(md5 "$uri")" local out="$CACHE_DIR/$hash.png" mkdir -p "$CACHE_DIR" if [ -f "$out" ]; then return fi magick "$abs_path" -resize "${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE}" "$out" } # Parse arguments SIZE_NAME="normal" MODE="" TARGET="" while [[ $# -gt 0 ]]; do case "$1" in --file|-f) MODE="file" TARGET="$2" shift 2 ;; --directory|-d) MODE="dir" TARGET="$2" shift 2 ;; --size|-s) SIZE_NAME="$2" shift 2 ;; *) usage ;; esac # Only one mode allowed [[ -n "$MODE" ]] && break done THUMBNAIL_SIZE="$(get_thumbnail_size "$SIZE_NAME")" CACHE_DIR="$HOME/.cache/thumbnails/$SIZE_NAME" if [ -z "$MODE" ] || [ -z "$TARGET" ]; then usage fi case "$MODE" in file) if [ ! -f "$TARGET" ]; then echo "File not found: $TARGET" exit 2 fi generate_thumbnail "$TARGET" ;; dir) if [ ! -d "$TARGET" ]; then echo "Directory not found: $TARGET" exit 2 fi for f in "$TARGET"/*; do [ -f "$f" ] || continue generate_thumbnail "$f" & done wait ;; *) usage ;; esac ================================================ FILE: dots/.config/quickshell/ii/scripts/thumbnails/thumbgen-venv.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate GIO_USE_VFS=local "$SCRIPT_DIR/thumbgen.py" "$@" THUMBGEN_EXIT_CODE=$? deactivate exit $THUMBGEN_EXIT_CODE ================================================ FILE: dots/.config/quickshell/ii/scripts/thumbnails/thumbgen.py ================================================ #!/usr/bin/env python3 # From https://github.com/difference-engine/thumbnail-generator-ubuntu (MIT License) # Since the script is small and the maintainers seem inactive to accept my PR (#11) I decided to just copy it over. # When it gets merged and the python package gets updated we can just use it import os import sys from multiprocessing import Pool from pathlib import Path from typing import List, Union import click import gi from loguru import logger from tqdm import tqdm gi.require_version("GnomeDesktop", "4.0") from gi.repository import Gio, GnomeDesktop # isort:skip thumbnail_size_map = { "normal": GnomeDesktop.DesktopThumbnailSize.NORMAL, "large": GnomeDesktop.DesktopThumbnailSize.LARGE, "x-large": GnomeDesktop.DesktopThumbnailSize.XLARGE, "xx-large": GnomeDesktop.DesktopThumbnailSize.XXLARGE, } factory = None logger.remove() logger.add(sys.stdout, level="INFO") logger.add("/tmp/thumbgen.log", level="DEBUG", rotation="100 MB") def make_thumbnail(fpath: str) -> bool: mtime = os.path.getmtime(fpath) # Use Gio to determine the URI and mime type f = Gio.file_new_for_path(str(fpath)) uri = f.get_uri() info = f.query_info("standard::content-type", Gio.FileQueryInfoFlags.NONE, None) mime_type = info.get_content_type() if factory.lookup(uri, mtime) is not None: logger.debug("FRESH {}".format(uri)) return False if not factory.can_thumbnail(uri, mime_type, mtime): logger.debug("UNSUPPORTED {}".format(uri)) return False thumbnail = factory.generate_thumbnail(uri, mime_type) if thumbnail is None: logger.debug("ERROR {}".format(uri)) return False logger.debug("OK {}".format(uri)) factory.save_thumbnail(thumbnail, uri, mtime) return True @logger.catch() def thumbnail_folder(*, dir_path: Path, workers: int, only_images: bool, recursive: bool, machine_progress: bool = False) -> None: all_files = get_all_files(dir_path=dir_path, recursive=recursive) if only_images: all_files = get_all_images(all_files=all_files) all_files = [str(fpath) for fpath in all_files] if machine_progress: completed = 0 total = len(all_files) with Pool(processes=workers) as p: for result in p.imap(make_thumbnail, all_files): completed += 1 print(f"PROGRESS {completed}/{total} FILE {all_files[completed-1]}") sys.stdout.flush() else: with Pool(processes=workers) as p: list(tqdm(p.imap(make_thumbnail, all_files), total=len(all_files))) def get_all_images(*, all_files: List[Path]) -> List[Path]: img_suffixes = [".jpg", ".jpeg", ".png", ".gif"] all_images = [fpath for fpath in all_files if fpath.suffix in img_suffixes] print("Found {} images".format(len(all_images))) return all_images def get_all_files(*, dir_path: Path, recursive: bool) -> List[Path]: if not (dir_path.exists() and dir_path.is_dir()): raise ValueError("{} doesn't exist or isn't a valid directory!".format(dir_path.resolve())) if recursive: all_files = dir_path.rglob("*") else: all_files = dir_path.glob("*") all_files = [fpath for fpath in all_files if fpath.is_file()] print("Found {} files in the directory: {}".format(len(all_files), dir_path.resolve())) return all_files @click.command() @click.option( "-d", "--img_dirs", required=True, help='directories to generate thumbnails seperated by space, eg: "dir1/dir2 dir3"' ) @click.option( "-s", "--size", default="normal", type=click.Choice(["normal", "large", "x-large", "xx-large"]), help="Thumbnail size: normal, large, x-large, xx-large" ) @click.option("-w", "--workers", default=1, help="no of cpus to use for processing") @click.option( "-i", "--only_images", is_flag=True, default=False, help="Whether to only look for images to be thumbnailed" ) @click.option("-r", "--recursive", is_flag=True, default=False, help="Whether to recursively look for files") @click.option("--machine_progress", is_flag=True, default=False, help="Print machine-readable progress lines instead of a progress bar") def main(img_dirs: str, size: str, workers: str, only_images: bool, recursive: bool, machine_progress: bool) -> None: img_dirs = [Path(img_dir) for img_dir in img_dirs.split()] global factory factory = GnomeDesktop.DesktopThumbnailFactory.new(thumbnail_size_map[size]) for img_dir in img_dirs: thumbnail_folder(dir_path=img_dir, workers=workers, only_images=only_images, recursive=recursive, machine_progress=machine_progress) print("Thumbnail Generation Completed!") if __name__ == "__main__": main() ================================================ FILE: dots/.config/quickshell/ii/scripts/videos/record.sh ================================================ #!/usr/bin/env bash CONFIG_FILE="$HOME/.config/illogical-impulse/config.json" JSON_PATH=".screenRecord.savePath" CUSTOM_PATH=$(jq -r "$JSON_PATH" "$CONFIG_FILE" 2>/dev/null) RECORDING_DIR="" if [[ -n "$CUSTOM_PATH" ]]; then RECORDING_DIR="$CUSTOM_PATH" else RECORDING_DIR="$HOME/Videos" # Use default path fi getdate() { date '+%Y-%m-%d_%H.%M.%S' } getaudiooutput() { pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 } getactivemonitor() { hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' } mkdir -p "$RECORDING_DIR" cd "$RECORDING_DIR" || exit # parse --region without modifying $@ so other flags like --fullscreen still work ARGS=("$@") MANUAL_REGION="" SOUND_FLAG=0 FULLSCREEN_FLAG=0 for ((i=0;i<${#ARGS[@]};i++)); do if [[ "${ARGS[i]}" == "--region" ]]; then if (( i+1 < ${#ARGS[@]} )); then MANUAL_REGION="${ARGS[i+1]}" else notify-send "Recording cancelled" "No region specified for --region" -a 'Recorder' & disown exit 1 fi elif [[ "${ARGS[i]}" == "--sound" ]]; then SOUND_FLAG=1 elif [[ "${ARGS[i]}" == "--fullscreen" ]]; then FULLSCREEN_FLAG=1 fi done if pgrep wf-recorder > /dev/null; then notify-send "Recording Stopped" "Stopped" -a 'Recorder' & pkill wf-recorder & else if [[ $FULLSCREEN_FLAG -eq 1 ]]; then notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown if [[ $SOUND_FLAG -eq 1 ]]; then wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --audio="$(getaudiooutput)" else wf-recorder -o "$(getactivemonitor)" --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t fi else # If a manual region was provided via --region, use it; otherwise run slurp as before. if [[ -n "$MANUAL_REGION" ]]; then region="$MANUAL_REGION" else if ! region="$(slurp 2>&1)"; then notify-send "Recording cancelled" "Selection was cancelled" -a 'Recorder' & disown exit 1 fi fi notify-send "Starting recording" 'recording_'"$(getdate)"'.mp4' -a 'Recorder' & disown if [[ $SOUND_FLAG -eq 1 ]]; then wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" --audio="$(getaudiooutput)" else wf-recorder --pixel-format yuv420p -f './recording_'"$(getdate)"'.mp4' -t --geometry "$region" fi fi fi ================================================ FILE: dots/.config/quickshell/ii/services/Ai.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common.functions as CF import qs.modules.common import Quickshell import Quickshell.Io import Quickshell.Wayland import QtQuick import qs.services.ai /** * Basic service to handle LLM chats. Supports Google's and OpenAI's API formats. * Supports Gemini and OpenAI models. * Limitations: * - For now functions only work with Gemini API format */ Singleton { id: root property Component aiMessageComponent: AiMessageData {} property Component aiModelComponent: AiModel {} property Component geminiApiStrategy: GeminiApiStrategy {} property Component openaiApiStrategy: OpenAiApiStrategy {} property Component mistralApiStrategy: MistralApiStrategy {} readonly property string interfaceRole: "interface" readonly property string apiKeyEnvVarName: "API_KEY" signal responseFinished() property string systemPrompt: { let prompt = Config.options?.ai?.systemPrompt ?? ""; for (let key in root.promptSubstitutions) { // prompt = prompt.replaceAll(key, root.promptSubstitutions[key]); // QML/JS doesn't support replaceAll, so use split/join prompt = prompt.split(key).join(root.promptSubstitutions[key]); } return prompt; } // property var messages: [] property var messageIDs: [] property var messageByID: ({}) readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} readonly property var apiKeysLoaded: KeyringStorage.loaded readonly property bool currentModelHasApiKey: { const model = models[currentModelId]; if (!model || !model.requires_key) return true; if (!apiKeysLoaded) return false; const key = apiKeys[model.key_id]; return (key?.length > 0); } property var postResponseHook property real temperature: Persistent.states?.ai?.temperature ?? 0.5 property QtObject tokenCount: QtObject { property int input: -1 property int output: -1 property int total: -1 } function idForMessage(message) { // Generate a unique ID using timestamp and random value return Date.now().toString(36) + Math.random().toString(36).substr(2, 8); } function safeModelName(modelName) { return modelName.replace(/:/g, "_").replace(/ /g, "-").replace(/\//g, "-") } property list defaultPrompts: [] property list userPrompts: [] property list promptFiles: [...defaultPrompts, ...userPrompts] property list savedChats: [] property var promptSubstitutions: { "{DISTRO}": SystemInfo.distroName, "{DATETIME}": `${DateTime.time}, ${DateTime.collapsedCalendarFormat}`, "{WINDOWCLASS}": ToplevelManager.activeToplevel?.appId ?? "Unknown", "{DE}": `${SystemInfo.desktopEnvironment} (${SystemInfo.windowingSystem})` } // Gemini: https://ai.google.dev/gemini-api/docs/function-calling // OpenAI: https://platform.openai.com/docs/guides/function-calling property string currentTool: Config?.options.ai.tool ?? "search" property var tools: { "gemini": { "functions": [{"functionDeclarations": [ { "name": "switch_to_search_mode", "description": "Search the web", }, { "name": "get_shell_config", "description": "Get the desktop shell config file contents", }, { "name": "set_shell_config", "description": "Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.", "parameters": { "type": "object", "properties": { "key": { "type": "string", "description": "The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.", }, "value": { "type": "string", "description": "The value to set, e.g. `true`" } }, "required": ["key", "value"] } }, { "name": "run_shell_command", "description": "Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "The bash command to run", }, }, "required": ["command"] } }, ]}], "search": [{ "google_search": {} }], "none": [] }, "openai": { "functions": [ { "type": "function", "function": { "name": "get_shell_config", "description": "Get the desktop shell config file contents", "parameters": {} }, }, { "type": "function", "function": { "name": "set_shell_config", "description": "Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.", "parameters": { "type": "object", "properties": { "key": { "type": "string", "description": "The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.", }, "value": { "type": "string", "description": "The value to set, e.g. `true`" } }, "required": ["key", "value"] } } }, { "type": "function", "function": { "name": "run_shell_command", "description": "Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "The bash command to run", }, }, "required": ["command"] } }, }, ], "search": [], "none": [], }, "mistral": { "functions": [ { "type": "function", "function": { "name": "get_shell_config", "description": "Get the desktop shell config file contents", "parameters": {} }, }, { "type": "function", "function": { "name": "set_shell_config", "description": "Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.", "parameters": { "type": "object", "properties": { "key": { "type": "string", "description": "The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.", }, "value": { "type": "string", "description": "The value to set, e.g. `true`" } }, "required": ["key", "value"] } } }, { "type": "function", "function": { "name": "run_shell_command", "description": "Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.", "parameters": { "type": "object", "properties": { "command": { "type": "string", "description": "The bash command to run", }, }, "required": ["command"] } }, }, ], "search": [], "none": [], } } property list availableTools: Object.keys(root.tools[models[currentModelId]?.api_format]) property var toolDescriptions: { "functions": Translation.tr("Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed"), "search": Translation.tr("Gives the model search capabilities (immediately)"), "none": Translation.tr("Disable tools") } // Model properties: // - name: Name of the model // - icon: Icon name of the model // - description: Description of the model // - endpoint: Endpoint of the model // - model: Model name of the model // - requires_key: Whether the model requires an API key // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. // - key_get_link: Link to get an API key // - key_get_description: Description of pricing and how to get an API key // - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai". // - extraParams: Extra parameters to be passed to the model. This is a JSON object. property var models: Config.options.policies.ai === 2 ? {} : { "gemini-2.5-flash": aiModelComponent.createObject(this, { "name": "Gemini 2.5 Flash", "icon": "google-gemini-symbolic", "description": Translation.tr("Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers"), "homepage": "https://aistudio.google.com", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent", "model": "gemini-2.5-flash", "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", "key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), "api_format": "gemini", }), "gemini-3-flash": aiModelComponent.createObject(this, { "name": "Gemini 3 Flash", "icon": "google-gemini-symbolic", "description": Translation.tr("Online | Google's model\nPro-level intelligence at the speed and pricing of Flash."), "homepage": "https://aistudio.google.com", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:streamGenerateContent", "model": "gemini-3-flash-preview", "requires_key": true, "key_id": "gemini", "key_get_link": "https://aistudio.google.com/app/apikey", "key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"), "api_format": "gemini", }), "mistral-medium-3": aiModelComponent.createObject(this, { "name": "Mistral Medium 3", "icon": "mistral-symbolic", "description": Translation.tr("Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls").arg("Mistral"), "homepage": "https://mistral.ai/news/mistral-medium-3", "endpoint": "https://api.mistral.ai/v1/chat/completions", "model": "mistral-medium-2505", "requires_key": true, "key_id": "mistral", "key_get_link": "https://console.mistral.ai/api-keys", "key_get_description": Translation.tr("**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key"), "api_format": "mistral", }), } property var modelList: Object.keys(root.models) property var currentModelId: Persistent.states?.ai?.model || modelList[0] property var apiStrategies: { "openai": openaiApiStrategy.createObject(this), "gemini": geminiApiStrategy.createObject(this), "mistral": mistralApiStrategy.createObject(this), } property ApiStrategy currentApiStrategy: apiStrategies[models[currentModelId]?.api_format || "openai"] function addUserModels() { (Config?.options.ai?.extraModels ?? []).forEach(model => { const safeModelName = root.safeModelName(model["model"]); root.addModel(safeModelName, model) }); } Connections { target: Config function onReadyChanged() { if (!Config.ready) return; root.addUserModels() } } property string requestScriptFilePath: "/tmp/quickshell/ai/request.sh" property string pendingFilePath: "" Component.onCompleted: { setModel(currentModelId, false, false); // Do necessary setup for model root.addUserModels() // Config onReadyChanged above might not fire if config is loaded before this service } function guessModelLogo(model) { if (model.includes("llama")) return "ollama-symbolic"; if (model.includes("gemma")) return "google-gemini-symbolic"; if (model.includes("deepseek")) return "deepseek-symbolic"; if (/^phi\d*:/i.test(model)) return "microsoft-symbolic"; return "ollama-symbolic"; } function guessModelName(model) { const replaced = model.replace(/-/g, ' ').replace(/:/g, ' '); let words = replaced.split(' '); words[words.length - 1] = words[words.length - 1].replace(/(\d+)b$/, (_, num) => `${num}B`) words = words.map((word) => { return (word.charAt(0).toUpperCase() + word.slice(1)) }); if (words[words.length - 1] === "Latest") words.pop(); else words[words.length - 1] = `(${words[words.length - 1]})`; // Surround the last word with square brackets const result = words.join(' '); return result; } function addModel(modelName, data) { root.models = Object.assign({}, root.models, { [modelName]: aiModelComponent.createObject(this, data) }); } Process { id: getOllamaModels running: true command: ["bash", "-c", `${Directories.scriptPath}/ai/show-installed-ollama-models.sh`.replace(/file:\/\//, "")] stdout: SplitParser { onRead: data => { try { if (data.length === 0) return; const dataJson = JSON.parse(data); root.modelList = [...root.modelList, ...dataJson]; dataJson.forEach(model => { const safeModelName = root.safeModelName(model); root.addModel(safeModelName, { "name": guessModelName(model), "icon": guessModelLogo(model), "description": Translation.tr("Local Ollama model | %1").arg(model), "homepage": `https://ollama.com/library/${model}`, "endpoint": "http://localhost:11434/v1/chat/completions", "model": model, "requires_key": false, }) }); root.modelList = Object.keys(root.models); } catch (e) { console.log("Could not fetch Ollama models:", e); } } } } Process { id: getDefaultPrompts running: true command: ["ls", "-1", Directories.defaultAiPrompts] stdout: StdioCollector { onStreamFinished: { if (text.length === 0) return; root.defaultPrompts = text.split("\n") .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) .map(fileName => `${Directories.defaultAiPrompts}/${fileName}`) } } } Process { id: getUserPrompts running: true command: ["ls", "-1", Directories.userAiPrompts] stdout: StdioCollector { onStreamFinished: { if (text.length === 0) return; root.userPrompts = text.split("\n") .filter(fileName => fileName.endsWith(".md") || fileName.endsWith(".txt")) .map(fileName => `${Directories.userAiPrompts}/${fileName}`) } } } Process { id: getSavedChats running: true command: ["ls", "-1", Directories.aiChats] stdout: StdioCollector { onStreamFinished: { if (text.length === 0) return; root.savedChats = text.split("\n") .filter(fileName => fileName.endsWith(".json")) .map(fileName => `${Directories.aiChats}/${fileName}`) } } } FileView { id: promptLoader watchChanges: false; onLoadedChanged: { if (!promptLoader.loaded) return; Config.options.ai.systemPrompt = promptLoader.text(); root.addMessage(Translation.tr("Loaded the following system prompt\n\n---\n\n%1").arg(Config.options.ai.systemPrompt), root.interfaceRole); } } function printPrompt() { root.addMessage(Translation.tr("The current system prompt is\n\n---\n\n%1").arg(Config.options.ai.systemPrompt), root.interfaceRole); } function loadPrompt(filePath) { promptLoader.path = "" // Unload promptLoader.path = filePath; // Load promptLoader.reload(); } function addMessage(message, role) { if (message.length === 0) return; const aiMessage = aiMessageComponent.createObject(root, { "role": role, "content": message, "rawContent": message, "thinking": false, "done": true, }); const id = idForMessage(aiMessage); root.messageIDs = [...root.messageIDs, id]; root.messageByID[id] = aiMessage; } function removeMessage(index) { if (index < 0 || index >= messageIDs.length) return; const id = root.messageIDs[index]; root.messageIDs.splice(index, 1); root.messageIDs = [...root.messageIDs]; delete root.messageByID[id]; } function addApiKeyAdvice(model) { root.addMessage( Translation.tr('To set an API key, pass it with the %4 command\n\nTo view the key, pass "get" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3') .arg(model.name).arg(model.key_get_link).arg(model.key_get_description ?? Translation.tr("No further instruction provided")).arg("/key"), Ai.interfaceRole ); } function getModel() { return models[currentModelId]; } function setModel(modelId, feedback = true, setPersistentState = true) { if (!modelId) modelId = "" modelId = modelId.toLowerCase() if (modelList.indexOf(modelId) !== -1) { const model = models[modelId] // See if policy prevents online models if (Config.options.policies.ai === 2 && !model.endpoint.includes("localhost")) { root.addMessage( Translation.tr("Online models disallowed\n\nControlled by `policies.ai` config option"), root.interfaceRole ); return; } if (setPersistentState) Persistent.states.ai.model = modelId; if (feedback) root.addMessage(Translation.tr("Model set to %1").arg(model.name), root.interfaceRole); if (model.requires_key) { // If key not there show advice if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) { root.addApiKeyAdvice(model) } } } else { if (feedback) root.addMessage(Translation.tr("Invalid model. Supported: \n```\n") + modelList.join("\n```\n```\n"), Ai.interfaceRole) + "\n```" } } function setTool(tool) { if (!root.tools[models[currentModelId]?.api_format] || !(tool in root.tools[models[currentModelId]?.api_format])) { root.addMessage(Translation.tr("Invalid tool. Supported tools:\n- %1").arg(root.availableTools.join("\n- ")), root.interfaceRole); return false; } Config.options.ai.tool = tool; return true; } function getTemperature() { return root.temperature; } function setTemperature(value) { if (value == NaN || value < 0 || value > 2) { root.addMessage(Translation.tr("Temperature must be between 0 and 2"), Ai.interfaceRole); return; } Persistent.states.ai.temperature = value; root.temperature = value; root.addMessage(Translation.tr("Temperature set to %1").arg(value), Ai.interfaceRole); } function setApiKey(key) { const model = models[currentModelId]; if (!model.requires_key) { root.addMessage(Translation.tr("%1 does not require an API key").arg(model.name), Ai.interfaceRole); return; } if (!key || key.length === 0) { const model = models[currentModelId]; root.addApiKeyAdvice(model) return; } KeyringStorage.setNestedField(["apiKeys", model.key_id], key.trim()); root.addMessage(Translation.tr("API key set for %1").arg(model.name), Ai.interfaceRole); } function printApiKey() { const model = models[currentModelId]; if (model.requires_key) { const key = root.apiKeys[model.key_id]; if (key) { root.addMessage(Translation.tr("API key:\n\n```txt\n%1\n```").arg(key), Ai.interfaceRole); } else { root.addMessage(Translation.tr("No API key set for %1").arg(model.name), Ai.interfaceRole); } } else { root.addMessage(Translation.tr("%1 does not require an API key").arg(model.name), Ai.interfaceRole); } } function printTemperature() { root.addMessage(Translation.tr("Temperature: %1").arg(root.temperature), Ai.interfaceRole); } function clearMessages() { root.messageIDs = []; root.messageByID = ({}); root.tokenCount.input = -1; root.tokenCount.output = -1; root.tokenCount.total = -1; } FileView { id: requesterScriptFile } Process { id: requester property list baseCommand: ["bash"] property AiMessageData message property ApiStrategy currentStrategy function markDone() { requester.message.done = true; if (root.postResponseHook) { root.postResponseHook(); root.postResponseHook = null; // Reset hook after use } root.saveChat("lastSession") root.responseFinished() } function makeRequest() { const model = models[currentModelId]; // Fetch API keys if needed if (model?.requires_key && !KeyringStorage.loaded) KeyringStorage.fetchKeyringData(); requester.currentStrategy = root.currentApiStrategy; requester.currentStrategy.reset(); // Reset strategy state /* Put API key in environment variable */ if (model.requires_key) requester.environment[`${root.apiKeyEnvVarName}`] = root.apiKeys ? (root.apiKeys[model.key_id] ?? "") : "" /* Build endpoint, request data */ const endpoint = root.currentApiStrategy.buildEndpoint(model); const messageArray = root.messageIDs.map(id => root.messageByID[id]); const filteredMessageArray = messageArray.filter(message => message.role !== Ai.interfaceRole); const data = root.currentApiStrategy.buildRequestData(model, filteredMessageArray, root.systemPrompt, root.temperature, root.tools[model.api_format][root.currentTool], root.pendingFilePath); // console.log("[Ai] Request data: ", JSON.stringify(data, null, 2)); let requestHeaders = { "Content-Type": "application/json", } /* Create local message object */ requester.message = root.aiMessageComponent.createObject(root, { "role": "assistant", "model": currentModelId, "content": "", "rawContent": "", "thinking": true, "done": false, }); const id = idForMessage(requester.message); root.messageIDs = [...root.messageIDs, id]; root.messageByID[id] = requester.message; /* Build header string for curl */ let headerString = Object.entries(requestHeaders) .filter(([k, v]) => v && v.length > 0) .map(([k, v]) => `-H '${k}: ${v}'`) .join(' '); // console.log("Request headers: ", JSON.stringify(requestHeaders)); // console.log("Header string: ", headerString); /* Get authorization header from strategy */ const authHeader = requester.currentStrategy.buildAuthorizationHeader(root.apiKeyEnvVarName); /* Script shebang */ const scriptShebang = "#!/usr/bin/env bash\n"; /* Create extra setup when there's an attached file */ let scriptFileSetupContent = "" if (root.pendingFilePath && root.pendingFilePath.length > 0) { requester.message.localFilePath = root.pendingFilePath; scriptFileSetupContent = requester.currentStrategy.buildScriptFileSetup(root.pendingFilePath); root.pendingFilePath = "" } /* Create command string */ let scriptRequestContent = "" scriptRequestContent += `curl --no-buffer "${endpoint}"` + ` ${headerString}` + (authHeader ? ` ${authHeader}` : "") + ` --data '${CF.StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'` + "\n" /* Send the request */ const scriptContent = requester.currentStrategy.finalizeScriptContent(scriptShebang + scriptFileSetupContent + scriptRequestContent) const shellScriptPath = CF.FileUtils.trimFileProtocol(root.requestScriptFilePath) requesterScriptFile.path = Qt.resolvedUrl(shellScriptPath) requesterScriptFile.setText(scriptContent) requester.command = baseCommand.concat([shellScriptPath]); requester.running = true } stdout: SplitParser { onRead: data => { if (data.length === 0) return; if (requester.message.thinking) requester.message.thinking = false; // console.log("[Ai] Raw response line: ", data); // Handle response line try { const result = requester.currentStrategy.parseResponseLine(data, requester.message); // console.log("[Ai] Parsed response result: ", JSON.stringify(result, null, 2)); if (result.functionCall) { requester.message.functionCall = result.functionCall; root.handleFunctionCall(result.functionCall.name, result.functionCall.args, requester.message); } if (result.tokenUsage) { root.tokenCount.input = result.tokenUsage.input; root.tokenCount.output = result.tokenUsage.output; root.tokenCount.total = result.tokenUsage.total; } if (result.finished) { requester.markDone(); } } catch (e) { console.log("[AI] Could not parse response: ", e); requester.message.rawContent += data; requester.message.content += data; } } } onExited: (exitCode, exitStatus) => { const result = requester.currentStrategy.onRequestFinished(requester.message); if (result.finished) { requester.markDone(); } else if (!requester.message.done) { requester.markDone(); } // Handle error responses if (requester.message.content.includes("API key not valid")) { root.addApiKeyAdvice(models[requester.message.model]); } } } function sendUserMessage(message) { if (message.length === 0) return; root.addMessage(message, "user"); requester.makeRequest(); } function attachFile(filePath: string) { root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath); } function regenerate(messageIndex) { if (messageIndex < 0 || messageIndex >= messageIDs.length) return; const id = root.messageIDs[messageIndex]; const message = root.messageByID[id]; if (message.role !== "assistant") return; // Remove all messages after this one for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) { root.removeMessage(i); } requester.makeRequest(); } function createFunctionOutputMessage(name, output, includeOutputInChat = true) { return aiMessageComponent.createObject(root, { "role": "user", "content": `[[ Output of ${name} ]]${includeOutputInChat ? ("\n\n\n" + output + "\n") : ""}`, "rawContent": `[[ Output of ${name} ]]${includeOutputInChat ? ("\n\n\n" + output + "\n") : ""}`, "functionName": name, "functionResponse": output, "thinking": false, "done": true, // "visibleToUser": false, }); } function addFunctionOutputMessage(name, output) { const aiMessage = createFunctionOutputMessage(name, output); const id = idForMessage(aiMessage); root.messageIDs = [...root.messageIDs, id]; root.messageByID[id] = aiMessage; } function rejectCommand(message: AiMessageData) { if (!message.functionPending) return; message.functionPending = false; // User decided, no more "thinking" addFunctionOutputMessage(message.functionName, Translation.tr("Command rejected by user")) } function approveCommand(message: AiMessageData) { if (!message.functionPending) return; message.functionPending = false; // User decided, no more "thinking" const responseMessage = createFunctionOutputMessage(message.functionName, "", false); const id = idForMessage(responseMessage); root.messageIDs = [...root.messageIDs, id]; root.messageByID[id] = responseMessage; commandExecutionProc.message = responseMessage; commandExecutionProc.baseMessageContent = responseMessage.content; commandExecutionProc.shellCommand = message.functionCall.args.command; commandExecutionProc.running = true; // Start the command execution } Process { id: commandExecutionProc property string shellCommand: "" property AiMessageData message property string baseMessageContent: "" command: ["bash", "-c", shellCommand] stdout: SplitParser { onRead: (output) => { commandExecutionProc.message.functionResponse += output + "\n\n"; const updatedContent = commandExecutionProc.baseMessageContent + `\n\n\n${commandExecutionProc.message.functionResponse}\n`; commandExecutionProc.message.rawContent = updatedContent; commandExecutionProc.message.content = updatedContent; } } onExited: (exitCode, exitStatus) => { commandExecutionProc.message.functionResponse += `[[ Command exited with code ${exitCode} (${exitStatus}) ]]\n`; requester.makeRequest(); // Continue } } function handleFunctionCall(name, args: var, message: AiMessageData) { if (name === "switch_to_search_mode") { const modelId = root.currentModelId; root.currentTool = "search" root.postResponseHook = () => { root.currentTool = "functions" } addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request.")) requester.makeRequest(); } else if (name === "get_shell_config") { const configJson = CF.ObjectUtils.toPlainObject(Config.options) addFunctionOutputMessage(name, JSON.stringify(configJson)); requester.makeRequest(); } else if (name === "set_shell_config") { if (!args.key || !args.value) { addFunctionOutputMessage(name, Translation.tr("Invalid arguments. Must provide `key` and `value`.")); return; } const key = args.key; const value = args.value; Config.setNestedValue(key, value); } else if (name === "run_shell_command") { if (!args.command || args.command.length === 0) { addFunctionOutputMessage(name, Translation.tr("Invalid arguments. Must provide `command`.")); return; } const contentToAppend = `\n\n**Command execution request**\n\n\`\`\`command\n${args.command}\n\`\`\``; message.rawContent += contentToAppend; message.content += contentToAppend; message.functionPending = true; // Use thinking to indicate the command is waiting for approval } else root.addMessage(Translation.tr("Unknown function call: %1").arg(name), "assistant"); } function chatToJson() { return root.messageIDs.map(id => { const message = root.messageByID[id] return ({ "role": message.role, "rawContent": message.rawContent, "fileMimeType": message.fileMimeType, "fileUri": message.fileUri, "localFilePath": message.localFilePath, "model": message.model, "thinking": false, "done": true, "annotations": message.annotations, "annotationSources": message.annotationSources, "functionName": message.functionName, "functionCall": message.functionCall, "functionResponse": message.functionResponse, "visibleToUser": message.visibleToUser, }) }) } FileView { id: chatSaveFile property string chatName: "" path: chatName.length > 0 ? `${Directories.aiChats}/${chatName}.json` : "" blockLoading: true // Prevent race conditions } /** * Saves chat to a JSON list of message objects. * @param chatName name of the chat */ function saveChat(chatName) { chatSaveFile.chatName = chatName.trim() const saveContent = JSON.stringify(root.chatToJson()) chatSaveFile.setText(saveContent) getSavedChats.running = true; } /** * Loads chat from a JSON list of message objects. * @param chatName name of the chat */ function loadChat(chatName) { try { chatSaveFile.chatName = chatName.trim() chatSaveFile.reload() const saveContent = chatSaveFile.text() // console.log(saveContent) const saveData = JSON.parse(saveContent) root.clearMessages() root.messageIDs = saveData.map((_, i) => { return i }) // console.log(JSON.stringify(messageIDs)) for (let i = 0; i < saveData.length; i++) { const message = saveData[i]; root.messageByID[i] = root.aiMessageComponent.createObject(root, { "role": message.role, "rawContent": message.rawContent, "content": message.rawContent, "fileMimeType": message.fileMimeType, "fileUri": message.fileUri, "localFilePath": message.localFilePath, "model": message.model, "thinking": message.thinking, "done": message.done, "annotations": message.annotations, "annotationSources": message.annotationSources, "functionName": message.functionName, "functionCall": message.functionCall, "functionResponse": message.functionResponse, "visibleToUser": message.visibleToUser, }); } } catch (e) { console.log("[AI] Could not load chat: ", e); } finally { getSavedChats.running = true; } } } ================================================ FILE: dots/.config/quickshell/ii/services/AppSearch.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.functions import Quickshell /** * - Eases fuzzy searching for applications by name * - Guesses icon name for window class name */ Singleton { id: root property bool sloppySearch: Config.options?.search.sloppy ?? false property real scoreThreshold: 0.2 property var substitutions: ({ "code-url-handler": "visual-studio-code", "Code": "visual-studio-code", "gnome-tweaks": "org.gnome.tweaks", "pavucontrol-qt": "pavucontrol", "wps": "wps-office2019-kprometheus", "wpsoffice": "wps-office2019-kprometheus", "footclient": "foot", }) property var regexSubstitutions: [ { "regex": /^steam_app_(\d+)$/, "replace": "steam_icon_$1" }, { "regex": /Minecraft.*/, "replace": "minecraft" }, { "regex": /.*polkit.*/, "replace": "system-lock-screen" }, { "regex": /gcr.prompter/, "replace": "system-lock-screen" } ] // Deduped list to fix double icons readonly property list list: Array.from(DesktopEntries.applications.values) .filter((app, index, self) => index === self.findIndex((t) => ( t.id === app.id )) ) readonly property var preppedNames: list.map(a => ({ name: Fuzzy.prepare(`${a.name} `), entry: a })) readonly property var preppedIcons: list.map(a => ({ name: Fuzzy.prepare(`${a.icon} `), entry: a })) function fuzzyQuery(search: string): var { // Idk why list doesn't work if (root.sloppySearch) { const results = list.map(obj => ({ entry: obj, score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase()) })).filter(item => item.score > root.scoreThreshold) .sort((a, b) => b.score - a.score) return results .map(item => item.entry) } return Fuzzy.go(search, preppedNames, { all: true, key: "name" }).map(r => { return r.obj.entry }); } function iconExists(iconName) { if (!iconName || iconName.length == 0) return false; return (Quickshell.iconPath(iconName, true).length > 0) && !iconName.includes("image-missing"); } function getReverseDomainNameAppName(str) { return str.split('.').slice(-1)[0] } function getKebabNormalizedAppName(str) { return str.toLowerCase().replace(/\s+/g, "-"); } function getUndescoreToKebabAppName(str) { return str.toLowerCase().replace(/_/g, "-"); } function guessIcon(str) { if (!str || str.length == 0) return "image-missing"; // Quickshell's desktop entry lookup const entry = DesktopEntries.byId(str); if (entry) return entry.icon; // Normal substitutions if (substitutions[str]) return substitutions[str]; if (substitutions[str.toLowerCase()]) return substitutions[str.toLowerCase()]; // Regex substitutions for (let i = 0; i < regexSubstitutions.length; i++) { const substitution = regexSubstitutions[i]; const replacedName = str.replace( substitution.regex, substitution.replace, ); if (replacedName != str) return replacedName; } // Icon exists -> return as is if (iconExists(str)) return str; // Simple guesses const lowercased = str.toLowerCase(); if (iconExists(lowercased)) return lowercased; const reverseDomainNameAppName = getReverseDomainNameAppName(str); if (iconExists(reverseDomainNameAppName)) return reverseDomainNameAppName; const lowercasedDomainNameAppName = reverseDomainNameAppName.toLowerCase(); if (iconExists(lowercasedDomainNameAppName)) return lowercasedDomainNameAppName; const kebabNormalizedGuess = getKebabNormalizedAppName(str); if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess; const undescoreToKebabGuess = getUndescoreToKebabAppName(str); if (iconExists(undescoreToKebabGuess)) return undescoreToKebabGuess; // Search in desktop entries const iconSearchResults = Fuzzy.go(str, preppedIcons, { all: true, key: "name" }).map(r => { return r.obj.entry }); if (iconSearchResults.length > 0) { const guess = iconSearchResults[0].icon if (iconExists(guess)) return guess; } const nameSearchResults = root.fuzzyQuery(str); if (nameSearchResults.length > 0) { const guess = nameSearchResults[0].icon if (iconExists(guess)) return guess; } // Quickshell's desktop entry lookup const heuristicEntry = DesktopEntries.heuristicLookup(str); if (heuristicEntry) return heuristicEntry.icon; // Give up return "application-x-executable"; } } ================================================ FILE: dots/.config/quickshell/ii/services/Audio.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Services.Pipewire /** * A nice wrapper for default Pipewire audio sink and source. */ Singleton { id: root // Misc props property bool ready: Pipewire.defaultAudioSink?.ready ?? false property PwNode sink: Pipewire.defaultAudioSink property PwNode source: Pipewire.defaultAudioSource readonly property real hardMaxValue: 2.00 // People keep joking about setting volume to 5172% so... property string audioTheme: Config.options.sounds.theme property real value: sink?.audio.volume ?? 0 function friendlyDeviceName(node) { return (node.nickname || node.description || Translation.tr("Unknown")); } function appNodeDisplayName(node) { return (node.properties["application.name"] || node.description || node.name) } // Lists function correctType(node, isSink) { return (node.isSink === isSink) && node.audio } function appNodes(isSink) { return Pipewire.nodes.values.filter((node) => { // Should be list but it breaks ScriptModel return root.correctType(node, isSink) && node.isStream }) } function devices(isSink) { return Pipewire.nodes.values.filter(node => { return root.correctType(node, isSink) && !node.isStream }) } readonly property list outputAppNodes: root.appNodes(true) readonly property list inputAppNodes: root.appNodes(false) readonly property list outputDevices: root.devices(true) readonly property list inputDevices: root.devices(false) // Signals signal sinkProtectionTriggered(string reason); // Controls function toggleMute() { Audio.sink.audio.muted = !Audio.sink.audio.muted } function toggleMicMute() { Audio.source.audio.muted = !Audio.source.audio.muted } function incrementVolume() { const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step); } function decrementVolume() { const currentVolume = Audio.value; const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2; Audio.sink.audio.volume -= step; } function setDefaultSink(node) { Pipewire.preferredDefaultAudioSink = node; } function setDefaultSource(node) { Pipewire.preferredDefaultAudioSource = node; } // Internals PwObjectTracker { objects: [sink, source] } Connections { // Protection against sudden volume changes target: sink?.audio ?? null property bool lastReady: false property real lastVolume: 0 function onVolumeChanged() { if (!Config.options.audio.protection.enable) return; const newVolume = sink.audio.volume; // when resuming from suspend, we should not write volume to avoid pipewire volume reset issues if (isNaN(newVolume) || newVolume === undefined || newVolume === null) { lastReady = false; lastVolume = 0; return; } if (!lastReady) { lastVolume = newVolume; lastReady = true; return; } const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; const maxAllowed = Config.options.audio.protection.maxAllowed / 100; if (newVolume - lastVolume > maxAllowedIncrease) { sink.audio.volume = lastVolume; root.sinkProtectionTriggered(Translation.tr("Illegal increment")); } else if (newVolume > maxAllowed || newVolume > root.hardMaxValue) { root.sinkProtectionTriggered(Translation.tr("Exceeded max allowed")); sink.audio.volume = Math.min(lastVolume, maxAllowed); } lastVolume = sink.audio.volume; } } function playSystemSound(soundName) { const ogaPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.oga`; const oggPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.ogg`; // Try playing .oga first let command = [ "ffplay", "-nodisp", "-autoexit", ogaPath ]; Quickshell.execDetached(command); // Also try playing .ogg (ffplay will just fail silently if file doesn't exist) command = [ "ffplay", "-nodisp", "-autoexit", oggPath ]; Quickshell.execDetached(command); } } ================================================ FILE: dots/.config/quickshell/ii/services/Battery.qml ================================================ pragma Singleton import qs.services import qs.modules.common import Quickshell import Quickshell.Services.UPower import QtQuick import Quickshell.Io Singleton { id: root property bool available: UPower.displayDevice.isLaptopBattery property var chargeState: UPower.displayDevice.state property bool isCharging: chargeState == UPowerDeviceState.Charging property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge property real percentage: UPower.displayDevice?.percentage ?? 1 readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend readonly property bool soundEnabled: Config.options.sounds.battery property bool isLow: available && (percentage <= Config.options.battery.low / 100) property bool isCritical: available && (percentage <= Config.options.battery.critical / 100) property bool isSuspending: available && (percentage <= Config.options.battery.suspend / 100) property bool isFull: available && (percentage >= Config.options.battery.full / 100) property bool isLowAndNotCharging: isLow && !isCharging property bool isCriticalAndNotCharging: isCritical && !isCharging property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging property bool isFullAndCharging: isFull && isCharging property real energyRate: UPower.displayDevice.changeRate property real timeToEmpty: UPower.displayDevice.timeToEmpty property real timeToFull: UPower.displayDevice.timeToFull property real health: (function() { const devList = UPower.devices.values; for (let i = 0; i < devList.length; ++i) { const dev = devList[i]; if (dev.isLaptopBattery && dev.healthSupported) { const health = dev.healthPercentage; if (health === 0) { return 0.01; } else if (health < 1) { return health * 100; } else { return health; } } } return 0; })() onIsLowAndNotChargingChanged: { if (!root.available || !isLowAndNotCharging) return; Quickshell.execDetached([ "notify-send", Translation.tr("Low battery"), Translation.tr("Consider plugging in your device"), "-u", "critical", "-a", "Shell", "--hint=int:transient:1", ]) if (root.soundEnabled) Audio.playSystemSound("dialog-warning"); } onIsCriticalAndNotChargingChanged: { if (!root.available || !isCriticalAndNotCharging) return; Quickshell.execDetached([ "notify-send", Translation.tr("Critically low battery"), Translation.tr("Please charge!\nAutomatic suspend triggers at %1%").arg(Config.options.battery.suspend), "-u", "critical", "-a", "Shell", "--hint=int:transient:1", ]); if (root.soundEnabled) Audio.playSystemSound("suspend-error"); } onIsSuspendingAndNotChargingChanged: { if (root.available && isSuspendingAndNotCharging) { Quickshell.execDetached(["bash", "-c", `systemctl suspend || loginctl suspend`]); } } onIsFullAndChargingChanged: { if (!root.available || !isFullAndCharging) return; Quickshell.execDetached([ "notify-send", Translation.tr("Battery full"), Translation.tr("Please unplug the charger"), "-a", "Shell", "--hint=int:transient:1", ]); if (root.soundEnabled) Audio.playSystemSound("complete"); } onIsPluggedInChanged: { if (!root.available || !root.soundEnabled) return; if (isPluggedIn) { Audio.playSystemSound("power-plug") } else { Audio.playSystemSound("power-unplug") } } } ================================================ FILE: dots/.config/quickshell/ii/services/BluetoothStatus.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import Quickshell import Quickshell.Bluetooth import Quickshell.Io import QtQuick Singleton { id: root readonly property bool available: Bluetooth.adapters.values.length > 0 readonly property bool enabled: Bluetooth.defaultAdapter?.enabled ?? false readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0 readonly property bool connected: Bluetooth.devices.values.some(d => d.connected) function sortFunction(a, b) { // Ones with meaningful names before MAC addresses const macRegex = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/; const aIsMac = macRegex.test(a.name); const bIsMac = macRegex.test(b.name); if (aIsMac !== bIsMac) return aIsMac ? 1 : -1; // Alphabetical by name return a.name.localeCompare(b.name); } property list connectedDevices: Bluetooth.devices.values.filter(d => d.connected).sort(sortFunction) property list pairedButNotConnectedDevices: Bluetooth.devices.values.filter(d => d.paired && !d.connected).sort(sortFunction) property list unpairedDevices: Bluetooth.devices.values.filter(d => !d.paired && !d.connected).sort(sortFunction) property list friendlyDeviceList: [ ...connectedDevices, ...pairedButNotConnectedDevices, ...unpairedDevices ] } ================================================ FILE: dots/.config/quickshell/ii/services/Booru.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import qs.services import Quickshell; import QtQuick; /** * A service for interacting with various booru APIs. */ Singleton { id: root property Component booruResponseDataComponent: BooruResponseData {} signal tagSuggestion(string query, var suggestions) signal responseFinished() property string failMessage: Translation.tr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property int runningRequests: 0 property var defaultUserAgent: Config.options?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api) property var providers: { "system": { "name": Translation.tr("System") }, "yandere": { "name": "yande.re", "url": "https://yande.re", "api": "https://yande.re/post.json", "description": Translation.tr("All-rounder | Good quality, decent quantity"), "mapFunc": (response) => { return response.map(item => { return { "id": item.id, "width": item.width, "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating, "is_nsfw": (item.rating != 's'), "md5": item.md5, "preview_url": item.preview_url, "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_ext, "source": getWorkingImageSource(item.source) ?? item.file_url, } }) }, "tagSearchTemplate": "https://yande.re/tag.json?order=count&limit=10&name={{query}}*", "tagMapFunc": (response) => { return response.map(item => { return { "name": item.name, "count": item.count } }) } }, "konachan": { "name": "Konachan", "url": "https://konachan.net", "api": "https://konachan.net/post.json", "description": Translation.tr("For desktop wallpapers | Good quality"), "mapFunc": (response) => { return response.map(item => { return { "id": item.id, "width": item.width, "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating, "is_nsfw": (item.rating != 's'), "md5": item.md5, "preview_url": item.preview_url, "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_ext, "source": getWorkingImageSource(item.source) ?? item.file_url, } }) }, "tagSearchTemplate": "https://konachan.net/tag.json?order=count&limit=10&name={{query}}*", "tagMapFunc": (response) => { return response.map(item => { return { "name": item.name, "count": item.count } }) } }, "zerochan": { "name": "Zerochan", "url": "https://www.zerochan.net", "api": "https://www.zerochan.net/?json", "description": Translation.tr("Clean stuff | Excellent quality, no NSFW"), "mapFunc": (response) => { response = response.items return response.map(item => { return { "id": item.id, "width": item.width, "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags.join(" "), "rating": "safe", // Zerochan doesn't have nsfw "is_nsfw": false, "md5": item.md5, "preview_url": item.thumbnail, "sample_url": item.thumbnail, "file_url": item.thumbnail, "file_ext": "avif", "source": getWorkingImageSource(item.source) ?? item.thumbnail, "character": item.tag } }) } }, "danbooru": { "name": "Danbooru", "url": "https://danbooru.donmai.us", "api": "https://danbooru.donmai.us/posts.json", "description": Translation.tr("The popular one | Best quantity, but quality can vary wildly"), "mapFunc": (response) => { return response.map(item => { return { "id": item.id, "width": item.image_width, "height": item.image_height, "aspect_ratio": item.image_width / item.image_height, "tags": item.tag_string, "rating": item.rating, "is_nsfw": (item.rating != 's'), "md5": item.md5, "preview_url": item.preview_file_url, "sample_url": item.file_url ?? item.large_file_url, "file_url": item.large_file_url, "file_ext": item.file_ext, "source": getWorkingImageSource(item.source) ?? item.file_url, } }) }, "tagSearchTemplate": "https://danbooru.donmai.us/tags.json?limit=10&search[name_matches]={{query}}*", "tagMapFunc": (response) => { return response.map(item => { return { "name": item.name, "count": item.post_count } }) } }, "gelbooru": { "name": "Gelbooru", "url": "https://gelbooru.com", "api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1", "description": Translation.tr("The hentai one | Great quantity, a lot of NSFW, quality varies wildly"), "mapFunc": (response) => { response = response.post return response.map(item => { return { "id": item.id, "width": item.width, "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags, "rating": item.rating.replace('general', 's').charAt(0), "is_nsfw": (item.rating != 's'), "md5": item.md5, "preview_url": item.preview_url, "sample_url": item.sample_url ?? item.file_url, "file_url": item.file_url, "file_ext": item.file_url.split('.').pop(), "source": getWorkingImageSource(item.source) ?? item.file_url, } }) }, "tagSearchTemplate": "https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&limit=10&name_pattern={{query}}%", "tagMapFunc": (response) => { return response.tag.map(item => { return { "name": item.name, "count": item.count } }) } }, "waifu.im": { "name": "waifu.im", "url": "https://waifu.im", "api": "https://api.waifu.im/search", "description": Translation.tr("Waifus only | Excellent quality, limited quantity"), "mapFunc": (response) => { response = response.images return response.map(item => { return { "id": item.image_id, "width": item.width, "height": item.height, "aspect_ratio": item.width / item.height, "tags": item.tags.map(tag => {return tag.name}).join(" "), "rating": item.is_nsfw ? "e" : "s", "is_nsfw": item.is_nsfw, "md5": item.md5, "preview_url": item.sample_url ?? item.url, // preview_url just says access denied (maybe i fucked up and sent too many requests idk) "sample_url": item.url, "file_url": item.url, "file_ext": item.extension, "source": getWorkingImageSource(item.source) ?? item.url, } }) }, "tagSearchTemplate": "https://api.waifu.im/tags", "tagMapFunc": (response) => { return [...response.versatile.map(item => {return {"name": item}}), ...response.nsfw.map(item => {return {"name": item}})] } }, "t.alcy.cc": { "name": "Alcy", "url": "https://t.alcy.cc", "api": "https://t.alcy.cc/", "description": Translation.tr("Large images | God tier quality, no NSFW."), "fixedTags": [ { "name": "ycy", "count": "General" }, { "name": "moez", "count": "Moe" }, { "name": "ysz", "count": "Genshin Impact" }, { "name": "fj", "count": "Landscape" }, { "name": "bd", "count": "Girl on white background" }, { "name": "xhl", "count": "Shiggy" }, ], "manualParseFunc": (responseText) => { // Alcy just returns image links, each on a new line const lines = responseText.trim().split('\n'); return lines.map(line => { return { "id": Qt.md5(line), // Alcy doesn't provide dimensions and images are often of god resolution "width": 1000, "height": 1000, "aspect_ratio": 1, "tags": "[no tags]", "rating": "s", "is_nsfw": false, "md5": Qt.md5(line), "preview_url": line, "sample_url": line, "file_url": line, "file_ext": line.split('.').pop(), "source": "", } }); }, } } property var currentProvider: Persistent.states.booru.provider function getWorkingImageSource(url) { if (url.includes('pximg.net')) { return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`; } return url; } function setProvider(provider) { provider = provider.toLowerCase() if (providerList.indexOf(provider) !== -1) { Persistent.states.booru.provider = provider root.addSystemMessage(Translation.tr("Provider set to ") + providers[provider].name + (provider == "zerochan" ? Translation.tr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : "")) } else { root.addSystemMessage(Translation.tr("Invalid API provider. Supported: \n- ") + providerList.join("\n- ")) } } function clearResponses() { responses = [] } function addSystemMessage(message) { responses = [...responses, root.booruResponseDataComponent.createObject(null, { "provider": "system", "tags": [], "page": -1, "images": [], "message": `${message}` })] } function constructRequestUrl(tags, nsfw=true, limit=20, page=1) { var provider = providers[currentProvider] var baseUrl = provider.api var url = baseUrl var tagString = tags.join(" ") if (!nsfw && !(["zerochan", "waifu.im", "t.alcy.cc"].includes(currentProvider))) { if (currentProvider == "gelbooru") tagString += " rating:general"; else tagString += " rating:safe"; } var params = [] // Tags & limit if (currentProvider === "zerochan") { params.push("c=" + tagString) // zerochan doesn't have search in api, so we use color params.push("l=" + limit) params.push("s=" + "fav") params.push("t=" + 1) params.push("p=" + page) } else if (currentProvider === "waifu.im") { var tagsArray = tagString.split(" "); tagsArray.forEach(tag => { params.push("included_tags=" + encodeURIComponent(tag)); }); params.push("limit=" + Math.min(limit, 30)) // Only admin can do > 30 params.push("is_nsfw=" + (nsfw ? "null" : "false")) // null is random } else if (currentProvider === "t.alcy.cc") { url += tagString params.push("json") params.push("quantity=" + limit) } else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) if (currentProvider == "gelbooru") { params.push("pid=" + page) } else { params.push("page=" + page) } } if (baseUrl.indexOf("?") === -1) { url += "?" + params.join("&") } else { url += "&" + params.join("&") } return url } function makeRequest(tags, nsfw=false, limit=20, page=1) { var url = constructRequestUrl(tags, nsfw, limit, page) console.log("[Booru] Making request to " + url) const newResponse = root.booruResponseDataComponent.createObject(null, { "provider": currentProvider, "tags": tags, "page": page, "images": [], "message": "" }) var xhr = new XMLHttpRequest() xhr.open("GET", url) xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response: " + xhr.responseText) const provider = providers[currentProvider] let response; if (provider.manualParseFunc) { response = provider.manualParseFunc(xhr.responseText) } else { response = JSON.parse(xhr.responseText) response = provider.mapFunc(response) } // console.log("[Booru] Mapped response: " + JSON.stringify(response)) newResponse.images = response newResponse.message = response.length > 0 ? "" : root.failMessage } catch (e) { console.log("[Booru] Failed to parse response: " + e) newResponse.message = root.failMessage } finally { root.runningRequests--; root.responses = [...root.responses, newResponse] } } else if (xhr.readyState === XMLHttpRequest.DONE) { console.log("[Booru] Request failed with status: " + xhr.status) newResponse.message = root.failMessage root.runningRequests--; root.responses = [...root.responses, newResponse] } root.responseFinished() } try { // Required for danbooru if (currentProvider == "danbooru") { xhr.setRequestHeader("User-Agent", defaultUserAgent) } else if (currentProvider == "zerochan") { const userAgent = Config.options?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${Config.options.sidebar.booru.zerochan.username}` : defaultUserAgent xhr.setRequestHeader("User-Agent", userAgent) } root.runningRequests++; xhr.send() } catch (error) { console.log("Could not set User-Agent:", error) } } property var currentTagRequest: null function triggerTagSearch(query) { if (currentTagRequest) { currentTagRequest.abort(); } var provider = providers[currentProvider] if (provider.fixedTags) { root.tagSuggestion(query, provider.fixedTags) return provider.fixedTags; } else if (!provider.tagSearchTemplate) { return } var url = provider.tagSearchTemplate.replace("{{query}}", encodeURIComponent(query)) var xhr = new XMLHttpRequest() currentTagRequest = xhr xhr.open("GET", url) xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { currentTagRequest = null try { // console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) response = provider.tagMapFunc(response) // console.log("[Booru] Mapped response: " + JSON.stringify(response)) root.tagSuggestion(query, response) } catch (e) { console.log("[Booru] Failed to parse response: " + e) } } else if (xhr.readyState === XMLHttpRequest.DONE) { console.log("[Booru] Request failed with status: " + xhr.status) } } try { // Required for danbooru if (currentProvider == "danbooru") { xhr.setRequestHeader("User-Agent", defaultUserAgent) } xhr.send() } catch (error) { console.log("Could not set User-Agent:", error) } } } ================================================ FILE: dots/.config/quickshell/ii/services/BooruResponseData.qml ================================================ import qs.modules.common import QtQuick; /** * A booru response. */ QtObject { property string provider property var tags property var page property var images property string message } ================================================ FILE: dots/.config/quickshell/ii/services/Brightness.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound // From https://github.com/caelestia-dots/shell with modifications. // License: GPLv3 import qs.modules.common import qs.modules.common.functions import Quickshell import Quickshell.Io import Quickshell.Hyprland import QtQuick /** * For managing brightness of monitors. Supports both brightnessctl and ddcutil. */ Singleton { id: root signal brightnessChanged() property var ddcMonitors: [] readonly property list monitors: Quickshell.screens.map(screen => monitorComp.createObject(root, { screen })) function getMonitorForScreen(screen: ShellScreen): var { return monitors.find(m => m.screen === screen); } function increaseBrightness(): void { // if gamma is not yet 100, first increase gamma if (Hyprsunset.gamma !== 100) { Hyprsunset.setGamma(Hyprsunset.gamma + 5); return; } const focusedName = Hyprland.focusedMonitor.name; const monitor = monitors.find(m => focusedName === m.screen.name); if (monitor) monitor.setBrightness(monitor.brightness + 0.05); } function decreaseBrightness(): void { const focusedName = Hyprland.focusedMonitor.name; const monitor = monitors.find(m => focusedName === m.screen.name); if (monitor && monitor.brightness > 0) monitor.setBrightness(monitor.brightness - 0.05); // if brightness is 0, then decrease gamma else { Hyprsunset.setGamma(Hyprsunset.gamma - 5); } } reloadableId: "brightness" onMonitorsChanged: { ddcMonitors = []; ddcProc.running = true; } function initializeMonitor(i: int): void { if (i >= monitors.length) return; monitors[i].initialize(); } function ddcDetectFinished(): void { initializeMonitor(0); } Process { id: ddcProc command: ["ddcutil", "detect", "--brief"] stdout: SplitParser { splitMarker: "\n\n" onRead: data => { if (data.startsWith("Display ")) { const lines = data.split("\n").map(l => l.trim()); root.ddcMonitors.push({ name: lines.find(l => l.startsWith("DRM connector:")).split("-").slice(1).join('-'), busNum: lines.find(l => l.startsWith("I2C bus:")).split("/dev/i2c-")[1] }); } } } onExited: root.ddcDetectFinished() } Process { id: setProc } component BrightnessMonitor: QtObject { id: monitor required property ShellScreen screen property bool isDdc property string busNum property int rawMaxBrightness: 100 property real brightness property real brightnessMultiplier: 1.0 property real multipliedBrightness: Math.max(0, Math.min(1, brightness * (Config.options.light.antiFlashbang.enable ? brightnessMultiplier : 1))) property bool ready: false property bool animateChanges: !monitor.isDdc onBrightnessChanged: { if (!monitor.ready) return; root.brightnessChanged(); } Behavior on multipliedBrightness { enabled: monitor.animateChanges NumberAnimation { duration: 200 easing.type: Easing.BezierSpline easing.bezierCurve: Appearance.animationCurves.expressiveEffects } } onMultipliedBrightnessChanged: { if (monitor.animationEnabled) syncBrightness(); else setTimer.restart(); } function initialize() { monitor.ready = false; const match = root.ddcMonitors.find(m => m.name === screen.name && !root.monitors.slice(0, root.monitors.indexOf(this)).some(mon => mon.busNum === m.busNum)); isDdc = !!match; busNum = match?.busNum ?? ""; initProc.command = isDdc ? ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"] : ["sh", "-c", `echo "a b c $(brightnessctl g) $(brightnessctl m)"`]; initProc.running = true; } readonly property Process initProc: Process { stdout: SplitParser { onRead: data => { const [, , , current, max] = data.split(" "); monitor.rawMaxBrightness = parseInt(max); monitor.brightness = parseInt(current) / monitor.rawMaxBrightness; monitor.ready = true; } } onExited: (exitCode, exitStatus) => { initializeMonitor(root.monitors.indexOf(monitor) + 1); } } // We need a delay for DDC monitors because they can be quite slow and might act weird with rapid changes property var setTimer: Timer { id: setTimer interval: monitor.isDdc ? 300 : 0 onTriggered: { syncBrightness(); } } function syncBrightness() { const brightnessValue = Math.max(monitor.multipliedBrightness, 0); if (isDdc) { const rawValueRounded = Math.max(Math.floor(brightnessValue * monitor.rawMaxBrightness), 1); setProc.exec(["ddcutil", "-b", busNum, "setvcp", "10", rawValueRounded]); } else { const valuePercentNumber = Math.floor(brightnessValue * 100); let valuePercent = `${valuePercentNumber}%`; if (valuePercentNumber == 0) valuePercent = "1"; // Prevent fully black setProc.exec(["brightnessctl", "--class", "backlight", "s", valuePercent, "--quiet"]) } } function setBrightness(value: real): void { value = Math.max(0, Math.min(1, value)); monitor.brightness = value; } function setBrightnessMultiplier(value: real): void { monitor.brightnessMultiplier = value; } } Component { id: monitorComp BrightnessMonitor {} } // Anti-flashbang property int workspaceAnimationDelay: 500 property int contentSwitchDelay: 30 property string screenshotDir: "/tmp/quickshell/brightness/antiflashbang" function brightnessMultiplierForLightness(x: real): real { // I hand picked some values and fitted an exponential curve for this // 6.600135 + 216.360356 * e^(-0.0811129189x) // Division by 100 is to normalize to [0, 1] return (6.600135 + 216.360356 * Math.pow(Math.E, -0.0811129189 * x)) / 100.0; } Variants { model: Quickshell.screens Scope { id: screenScope required property var modelData property string screenName: modelData.name property string screenshotPath: `${root.screenshotDir}/screenshot-${screenName}.png` Connections { enabled: Config.options.light.antiFlashbang.enable && Appearance.m3colors.darkmode target: Hyprland function onRawEvent(event) { if (["activewindowv2", "windowtitlev2"].includes(event.name)) { screenshotTimer.interval = root.contentSwitchDelay; screenshotTimer.restart(); } else if (["workspacev2"].includes(event.name)) { screenshotTimer.interval = root.workspaceAnimationDelay; screenshotTimer.restart(); } } } Timer { id: screenshotTimer interval: 700 // This is what I have for a Hyprland ws anim onTriggered: { screenshotProc.running = false; screenshotProc.running = true; } } Process { id: screenshotProc command: ["bash", "-c", `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}'` + ` && grim -o '${StringUtils.shellSingleQuoteEscape(screenScope.screenName)}' -` + ` | magick png:- -colorspace Gray -format "%[fx:mean*100]" info:` ] stdout: StdioCollector { id: lightnessCollector onStreamFinished: { Quickshell.execDetached(["rm", screenScope.screenshotPath]); // Cleanup const lightness = lightnessCollector.text const newMultiplier = root.brightnessMultiplierForLightness(parseFloat(lightness)) Brightness.getMonitorForScreen(screenScope.modelData).setBrightnessMultiplier(newMultiplier) } } } } } // External trigger points IpcHandler { target: "brightness" function increment() { onPressed: root.increaseBrightness() } function decrement() { onPressed: root.decreaseBrightness() } } GlobalShortcut { name: "brightnessIncrease" description: "Increase brightness" onPressed: root.increaseBrightness() } GlobalShortcut { name: "brightnessDecrease" description: "Decrease brightness" onPressed: root.decreaseBrightness() } } ================================================ FILE: dots/.config/quickshell/ii/services/Cliphist.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io Singleton { id: root // property string cliphistBinary: FileUtils.trimFileProtocol(`${Directories.home}/.cargo/bin/stash`) property string cliphistBinary: "cliphist" property real pasteDelay: 0.05 property string pressPasteCommand: "ydotool key -d 1 29:1 47:1 47:0 29:0" property bool sloppySearch: Config.options?.search.sloppy ?? false property real scoreThreshold: 0.2 property list entries: [] readonly property var preparedEntries: entries.map(a => ({ name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`), entry: a })) function fuzzyQuery(search: string): var { if (search.trim() === "") { return entries; } if (root.sloppySearch) { const results = entries.slice(0, 100).map(str => ({ entry: str, score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase()) })).filter(item => item.score > root.scoreThreshold) .sort((a, b) => b.score - a.score) return results .map(item => item.entry) } return Fuzzy.go(search, preparedEntries, { all: true, key: "name" }).map(r => { return r.obj.entry }); } function entryIsImage(entry) { return !!(/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(entry)) } function refresh() { readProc.buffer = [] readProc.running = true } function copy(entry) { if (root.cliphistBinary.includes("cliphist")) // Classic cliphist Quickshell.execDetached(["bash", "-c", `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]); else { // Stash const entryNumber = entry.split("\t")[0]; Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy`]); } } function paste(entry) { if (root.cliphistBinary.includes("cliphist")) // Classic cliphist Quickshell.execDetached(["bash", "-c", `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]); else { // Stash const entryNumber = entry.split("\t")[0]; Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy; ${root.pressPasteCommand}`]); } } function superpaste(count, isImage = false) { // Find entries const targetEntries = entries.filter(entry => { if (!isImage) return true; return entryIsImage(entry); }).slice(0, count) const pasteCommands = [...targetEntries].reverse().map(entry => `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && sleep ${root.pasteDelay} && ${root.pressPasteCommand}`) // Act Quickshell.execDetached(["bash", "-c", pasteCommands.join(` && sleep ${root.pasteDelay} && `)]); } Process { id: deleteProc property string entry: "" command: ["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`] function deleteEntry(entry) { deleteProc.entry = entry; deleteProc.running = true; deleteProc.entry = ""; } onExited: (exitCode, exitStatus) => { root.refresh(); } } function deleteEntry(entry) { deleteProc.deleteEntry(entry); } Process { id: wipeProc command: [root.cliphistBinary, "wipe"] onExited: (exitCode, exitStatus) => { root.refresh(); } } function wipe() { wipeProc.running = true; } Connections { target: Quickshell function onClipboardTextChanged() { delayedUpdateTimer.restart() } } Timer { id: delayedUpdateTimer interval: Config.options.hacks.arbitraryRaceConditionDelay repeat: false onTriggered: { root.refresh() } } Process { id: readProc property list buffer: [] command: [root.cliphistBinary, "list"] stdout: SplitParser { onRead: (line) => { readProc.buffer.push(line) } } onExited: (exitCode, exitStatus) => { if (exitCode === 0) { root.entries = readProc.buffer } else { console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus) } } } IpcHandler { target: "cliphistService" function update(): void { root.refresh() } } } ================================================ FILE: dots/.config/quickshell/ii/services/ConflictKiller.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io Singleton { id: root property string killDialogQmlPath: FileUtils.trimFileProtocol(Quickshell.shellPath("killDialog.qml")) function load() { // dummy to force init } Connections { target: Config function onReadyChanged() { if (Config.ready) checkConflictsProc.running = true } } Process { id: checkConflictsProc command: ["bash", "-c", `echo "$(pidof kded6);$(pidof mako dunst)"`] stdout: StdioCollector { onStreamFinished: { const output = this.text; const conflictingTrays = output.split(";")[0].trim().length > 0; const conflictingNotifications = output.split(";")[1].trim().length > 0; var openDialog = false; if (conflictingTrays) { if (!Config.options.conflictKiller.autoKillTrays) openDialog = true; else Quickshell.execDetached(["killall", "kded6"]) } if (conflictingNotifications) { if (!Config.options.conflictKiller.autoKillNotificationDaemons) openDialog = true; else Quickshell.execDetached(["killall", "mako", "dunst"]) } if (openDialog) { Quickshell.execDetached(["qs", "-p", root.killDialogQmlPath]) } } } } } ================================================ FILE: dots/.config/quickshell/ii/services/DateTime.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs import qs.modules.common import QtQuick import Quickshell import Quickshell.Io /** * A nice wrapper for date and time strings. */ Singleton { property var clock: SystemClock { id: clock precision: { if (Config.options.time.secondPrecision || GlobalStates.screenLocked) return SystemClock.Seconds; return SystemClock.Minutes; } } property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? "hh:mm") property string shortDate: Qt.locale().toString(clock.date, Config.options?.time.shortDateFormat ?? "dd/MM") property string date: Qt.locale().toString(clock.date, Config.options?.time.dateWithYearFormat ?? "dd/MM/yyyy") property string longDate: Qt.locale().toString(clock.date, Config.options?.time.dateFormat ?? "dddd, dd/MM") property string collapsedCalendarFormat: Qt.locale().toString(clock.date, "dddd, MMMM dd") property string uptime: "0h, 0m" Timer { interval: 10 running: true repeat: true onTriggered: { fileUptime.reload(); const textUptime = fileUptime.text(); const uptimeSeconds = Number(textUptime.split(" ")[0] ?? 0); // Convert seconds to days, hours, and minutes const days = Math.floor(uptimeSeconds / 86400); const hours = Math.floor((uptimeSeconds % 86400) / 3600); const minutes = Math.floor((uptimeSeconds % 3600) / 60); // Build the formatted uptime string let formatted = ""; if (days > 0) formatted += `${days}d`; if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h`; if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m`; uptime = formatted; interval = Config.options?.resources?.updateInterval ?? 3000; } } FileView { id: fileUptime path: "/proc/uptime" } } ================================================ FILE: dots/.config/quickshell/ii/services/EasyEffects.qml ================================================ import qs.modules.common import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Pipewire pragma Singleton pragma ComponentBehavior: Bound /** * Handles EasyEffects active state and presets. */ Singleton { id: root property bool available: false property bool active: false function fetchAvailability() { fetchAvailabilityProc.running = true } function fetchActiveState() { fetchActiveStateProc.running = true } function disable() { root.active = false Quickshell.execDetached(["bash", "-c", "pkill easyeffects || flatpak pkill com.github.wwmm.easyeffects"]) } function enable() { root.active = true Quickshell.execDetached(["bash", "-c", "easyeffects --hide-window --service-mode || flatpak run com.github.wwmm.easyeffects --hide-window --service-mode"]) } function toggle() { if (root.active) { root.disable() } else { root.enable() } } Process { id: fetchAvailabilityProc running: true command: ["bash", "-c", "command -v easyeffects || flatpak info com.github.wwmm.easyeffects > /dev/null 2>&1"] onExited: (exitCode, exitStatus) => { root.available = exitCode === 0 } } Process { id: fetchActiveStateProc running: true command: ["bash", "-c", "pidof easyeffects || flatpak ps | grep com.github.wwmm.easyeffects > /dev/null 2>&1"] onExited: (exitCode, exitStatus) => { root.active = exitCode === 0 } } } ================================================ FILE: dots/.config/quickshell/ii/services/Emojis.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io /** * Emojis. */ Singleton { id: root property string emojiScriptPath: `${Directories.config}/hypr/hyprland/scripts/fuzzel-emoji.sh` property string lineBeforeData: "### DATA ###" property list list readonly property var preparedEntries: list.map(a => ({ name: Fuzzy.prepare(`${a}`), entry: a })) function fuzzyQuery(search: string): var { if (root.sloppySearch) { const results = entries.slice(0, 100).map(str => ({ entry: str, score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase()) })).filter(item => item.score > root.scoreThreshold) .sort((a, b) => b.score - a.score) return results .map(item => item.entry) } return Fuzzy.go(search, preparedEntries, { all: true, key: "name" }).map(r => { return r.obj.entry }); } function load() { emojiFileView.reload() } function updateEmojis(fileContent) { const lines = fileContent.split("\n") const dataIndex = lines.indexOf(root.lineBeforeData) if (dataIndex === -1) { console.warn("No data section found in emoji script file.") return } const emojis = lines.slice(dataIndex + 1).filter(line => line.trim() !== "") root.list = emojis.map(line => line.trim()) } FileView { id: emojiFileView path: Qt.resolvedUrl(root.emojiScriptPath) onLoadedChanged: { const fileContent = emojiFileView.text() root.updateEmojis(fileContent) } } } ================================================ FILE: dots/.config/quickshell/ii/services/FirstRunExperience.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.functions import Quickshell import Quickshell.Io Singleton { id: root property string firstRunFilePath: `${Directories.state}/user/first_run.txt` property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" property string firstRunNotifSummary: "Welcome!" property string firstRunNotifBody: "Hit Super+/ for a list of keybinds" property string defaultWallpaperPath: FileUtils.trimFileProtocol(`${Directories.assetsPath}/images/default_wallpaper.png`) property string welcomeQmlPath: FileUtils.trimFileProtocol(Quickshell.shellPath("welcome.qml")) function load() { firstRunFileView.reload() } function enableNextTime() { Quickshell.execDetached(["rm", "-f", root.firstRunFilePath]) } function disableNextTime() { Quickshell.execDetached(["bash", "-c", `echo '${root.firstRunFileContent}' > '${root.firstRunFilePath}'`]) } function handleFirstRun() { Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, root.defaultWallpaperPath]) Quickshell.execDetached(["bash", "-c", `qs -p '${root.welcomeQmlPath}'`]) } FileView { id: firstRunFileView path: Qt.resolvedUrl(firstRunFilePath) onLoadFailed: (error) => { if (error == FileViewError.FileNotFound) { firstRunFileView.setText(root.firstRunFileContent) root.handleFirstRun() } } } } ================================================ FILE: dots/.config/quickshell/ii/services/GlobalFocusGrab.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Hyprland /** * Manages a HyprlandFocusGrab that's to be shared by all windows. * "Persistent" is for windows that should always be included but not closed on dismiss, like bar and onscreen keyboard. * "Dismissable" is for stuff like sidebars */ Singleton { id: root signal dismissed() property list persistent: [] property list dismissable: [] function dismiss() { root.dismissable = []; root.dismissed(); } Component.onCompleted: { console.log("[GlobalFocusGrab] Initialized"); } function addPersistent(window) { if (root.persistent.indexOf(window) === -1) { root.persistent.push(window); } } function removePersistent(window) { var index = root.persistent.indexOf(window); if (index !== -1) { root.persistent.splice(index, 1); } } function addDismissable(window) { if (root.dismissable.indexOf(window) === -1) { root.dismissable.push(window); } } function removeDismissable(window) { var index = root.dismissable.indexOf(window); if (index !== -1) { root.dismissable.splice(index, 1); } } function hasActive(element) { return element?.activeFocus || Array.from( element?.children ).some( (child) => hasActive(child) ); } HyprlandFocusGrab { id: grab windows: root.dismissable.every(w => !w?.focusable) || root.dismissable.some(w => hasActive(w?.contentItem)) ? [...root.dismissable, ...root.persistent] : [...root.dismissable] active: root.dismissable.length > 0 onCleared: () => { root.dismiss(); } } } ================================================ FILE: dots/.config/quickshell/ii/services/GoogleCloud.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import qs.modules.common.utils Singleton { id: root property var keyContent: ({}) property string keyProjectId: keyContent?.project_id property bool keyError: false property bool keyReady: false property string token: "" property date tokenExpiry property bool tokenError: false property bool tokenReady: false readonly property string projectId: keyProjectId readonly property bool loaded: keyReady && tokenReady readonly property string tokenForKeyScriptPath: Quickshell.shellPath("services/gCloud/token-from-key-venv.sh") function load() { // Init load will be handled by Component.onCompleted if (!tokenReady) return; // We just reload if key expired if (new Date() >= root.tokenExpiry) { root.tokenReady = false; root.keyReady = false; loadKeyIfPossible(); } } function unready() { root.keyReady = false; root.tokenReady = false; root.keyError = false; root.tokenError = false; } function setKeyJson(str: string): bool { try { var keyData = JSON.parse(str) root.unready(); KeyringStorage.setNestedField(["googleCloud", "serviceAccountKey"], keyData); return true; } catch(e) { return false; } } function getToken() { if (root.keyError) { root.tokenError = true; root.tokenReady = true; return; } tokenProc.runSequence([(() => { // prep token fetcher tokenProc.environment.SERVICE_KEY_CONTENT = JSON.stringify(root.keyContent); tokenProc.command = [ // "bash", "-c" // , `${tokenForKeyScriptPath} "$SERVICE_KEY_CONTENT"`]; }), // [], // run token fetcher ((out) => { try { const data = JSON.parse(out) root.token = data.token // Js wants millis instead of seconds root.tokenExpiry = new Date(data.expiry * 1000) root.tokenError = false; } catch(e) { root.tokenError = true; print("[GoogleCloud] Failed to parse token response: " + e + "\n" + out) } root.tokenReady = true; } )]); } function loadKeyIfPossible() { if (KeyringStorage.loaded) { root.keyContent = KeyringStorage.keyringData?.googleCloud?.serviceAccountKey; if (!root.keyContent?.project_id) { root.keyError = true; } else { root.keyError = false; root.keyProjectId = root.keyContent.project_id; } root.keyReady = true; root.getToken(); } else { KeyringStorage.fetchKeyringData(); } } Component.onCompleted: { loadKeyIfPossible(); } Connections { target: KeyringStorage function onLoadedChanged() { root.loadKeyIfPossible(); } function onDataChanged() { root.loadKeyIfPossible(); } } MultiTurnProcess { id: tokenProc } } ================================================ FILE: dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import qs.modules.common.models.hyprland Singleton { id: root readonly property string shaderPath: Quickshell.shellPath("services/hyprlandAntiFlashbangShader/anti-flashbang.glsl") property bool enabled: confOpt.value == shaderPath function enable() { HyprlandConfig.setMany({ "decoration:screen_shader": root.shaderPath, "debug:damage_tracking": 1, // Turn off dmg tracking to prevent weird flashes. 1 = monitor only }); } function disable() { HyprlandConfig.resetMany([ "decoration:screen_shader", "debug:damage_tracking" ]); } function toggle() { if (root.enabled) disable() else enable() } HyprlandConfigOption { id: confOpt key: "decoration:screen_shader" } } ================================================ FILE: dots/.config/quickshell/ii/services/HyprlandConfig.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Hyprland import qs.modules.common import qs.modules.common.functions /** * Configs Hyprland */ Singleton { id: root signal reloaded() readonly property string configuratorScriptPath: Quickshell.shellPath("scripts/hyprland/hyprconfigurator.py") readonly property string shellOverridesPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/main.lua`) function set(key: string, value: var) { Quickshell.execDetached(["bash", "-c", // `${root.configuratorScriptPath} --file ${root.shellOverridesPath} --set "${key}" "${value}"` // ]) } function setMany(entries: var) { let args = "" for (let key in entries) { args += `--set "${key}" "${entries[key]}" ` } Quickshell.execDetached(["bash", "-c", // `${root.configuratorScriptPath} --file ${root.shellOverridesPath} ${args}` // ]) } function reset(key: string) { Quickshell.execDetached(["bash", "-c", // `${root.configuratorScriptPath} --file ${root.shellOverridesPath} --reset "${key}"` // ]) } function resetMany(keys: list) { let args = "" for (let i = 0; i < keys.length; i++) { args += `--reset "${keys[i]}" ` } Quickshell.execDetached(["bash", "-c", // `${root.configuratorScriptPath} --file ${root.shellOverridesPath} ${args}` // ]) } Connections { target: Hyprland function onRawEvent(event) { if (event.name == "configreloaded") { root.reloaded() } } } } ================================================ FILE: dots/.config/quickshell/ii/services/HyprlandData.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland /** * Provides access to some Hyprland data not available in Quickshell.Hyprland. */ Singleton { id: root property var windowList: [] property var addresses: [] property var windowByAddress: ({}) property var workspaces: [] property var workspaceIds: [] property var workspaceById: ({}) property var activeWorkspace: null property var monitors: [] property var layers: ({}) // Convenient stuff function toplevelsForWorkspace(workspace) { return ToplevelManager.toplevels.values.filter(toplevel => { const address = `0x${toplevel.HyprlandToplevel?.address}`; var win = HyprlandData.windowByAddress[address]; return win?.workspace?.id === workspace; }) } function hyprlandClientsForWorkspace(workspace) { return root.windowList.filter(win => win.workspace.id === workspace); } function clientForToplevel(toplevel) { if (!toplevel || !toplevel.HyprlandToplevel) { return null; } const address = `0x${toplevel?.HyprlandToplevel?.address}`; return root.windowByAddress[address]; } // Internals function updateWindowList() { getClients.running = true; } function updateLayers() { getLayers.running = true; } function updateMonitors() { getMonitors.running = true; } function updateWorkspaces() { getWorkspaces.running = true; getActiveWorkspace.running = true; } function updateAll() { updateWindowList(); updateMonitors(); updateLayers(); updateWorkspaces(); } function biggestWindowForWorkspace(workspaceId) { const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId); return windowsInThisWorkspace.reduce((maxWin, win) => { const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0); const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0); return winArea > maxArea ? win : maxWin; }, null); } Component.onCompleted: { updateAll(); } Connections { target: Hyprland function onRawEvent(event) { // console.log("Hyprland raw event:", event.name); if (["openlayer", "closelayer", "screencast"].includes(event.name)) return; updateAll() } } Process { id: getClients command: ["hyprctl", "clients", "-j"] stdout: StdioCollector { id: clientsCollector onStreamFinished: { root.windowList = JSON.parse(clientsCollector.text) let tempWinByAddress = {}; for (var i = 0; i < root.windowList.length; ++i) { var win = root.windowList[i]; tempWinByAddress[win.address] = win; } root.windowByAddress = tempWinByAddress; root.addresses = root.windowList.map(win => win.address); } } } Process { id: getMonitors command: ["hyprctl", "monitors", "-j"] stdout: StdioCollector { id: monitorsCollector onStreamFinished: { root.monitors = JSON.parse(monitorsCollector.text); } } } Process { id: getLayers command: ["hyprctl", "layers", "-j"] stdout: StdioCollector { id: layersCollector onStreamFinished: { root.layers = JSON.parse(layersCollector.text); } } } Process { id: getWorkspaces command: ["hyprctl", "workspaces", "-j"] stdout: StdioCollector { id: workspacesCollector onStreamFinished: { var rawWorkspaces = JSON.parse(workspacesCollector.text); // Filter out invalid workspace ids (e.g. lock-screen temp workspace 2147483647 - N) root.workspaces = rawWorkspaces.filter(ws => ws.id >= 1 && ws.id <= 100); let tempWorkspaceById = {}; for (var i = 0; i < root.workspaces.length; ++i) { var ws = root.workspaces[i]; tempWorkspaceById[ws.id] = ws; } root.workspaceById = tempWorkspaceById; root.workspaceIds = root.workspaces.map(ws => ws.id); } } } Process { id: getActiveWorkspace command: ["hyprctl", "activeworkspace", "-j"] stdout: StdioCollector { id: activeWorkspaceCollector onStreamFinished: { root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text); } } } } ================================================ FILE: dots/.config/quickshell/ii/services/HyprlandKeybinds.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland /** * A service that provides access to Hyprland keybinds. * Uses the `get_keybinds.py` script to parse comments in config files in a certain format and convert to JSON. */ Singleton { id: root property var keybinds: [] property var keybindCategories: [] Connections { target: Hyprland function onRawEvent(event) { if (event.name == "configreloaded") { getKeybinds.running = true } } } Process { id: getKeybinds running: true command: ["hyprctl", "binds", "-j"] stdout: StdioCollector { onStreamFinished: { try { root.keybinds = JSON.parse(text) var groups = [] for (var i = 0; i < root.keybinds.length; i++) { var bind = root.keybinds[i].description var group = bind.substring(0, bind.indexOf(":")) if (!groups.includes(group) && group.length > 0) { groups.push(group) } } root.keybindCategories = groups } catch (e) { console.error("[CheatsheetKeybinds] Error parsing keybinds:", e) } } } } } ================================================ FILE: dots/.config/quickshell/ii/services/HyprlandXkb.qml ================================================ pragma Singleton import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland import qs.modules.common /** * Exposes the active Hyprland Xkb keyboard layout name and code for indicators. */ Singleton { id: root // You can read these property list layoutCodes: [] property var cachedLayoutCodes: ({}) property string currentLayoutName: "" property string currentLayoutCode: "" // For the service property var baseLayoutFilePath: "/usr/share/X11/xkb/rules/base.lst" property bool needsLayoutRefresh: false // Update the layout code according to the layout name (Hyprland gives the name not the code) onCurrentLayoutNameChanged: root.updateLayoutCode() function updateLayoutCode() { if (cachedLayoutCodes.hasOwnProperty(currentLayoutName)) { root.currentLayoutCode = cachedLayoutCodes[currentLayoutName]; } else { getLayoutProc.running = true; } } // Get the layout code from the base.lst file by grabbing the line with the current layout name Process { id: getLayoutProc command: ["cat", root.baseLayoutFilePath] stdout: StdioCollector { id: layoutCollector onStreamFinished: { const lines = layoutCollector.text.split("\n"); const targetDescription = root.currentLayoutName; const foundLine = lines.find(line => { // Skip comment lines and empty lines if (!line.trim() || line.trim().startsWith('!')) return false; // Match layout: (whitespace + ) key + whitespace + description const matchLayout = line.match(/^\s*(\S+)\s+(.+)$/); if (matchLayout && matchLayout[2] === targetDescription) { root.cachedLayoutCodes[matchLayout[2]] = matchLayout[1]; root.currentLayoutCode = matchLayout[1]; return true; } // Match variant: (whitespace + ) variant + whitespace + key + whitespace + description const matchVariant = line.match(/^\s*(\S+)\s+(\S+)\s+(.+)$/); if (matchVariant && matchVariant[3] === targetDescription) { const complexLayout = matchVariant[2] + matchVariant[1]; root.cachedLayoutCodes[matchVariant[3]] = complexLayout; root.currentLayoutCode = complexLayout; return true; } return false; }); // console.log("[HyprlandXkb] Found line:", foundLine); // console.log("[HyprlandXkb] Layout:", root.currentLayoutName, "| Code:", root.currentLayoutCode); // console.log("[HyprlandXkb] Cached layout codes:", JSON.stringify(root.cachedLayoutCodes, null, 2)); } } } // Find out available layouts and current active layout. Should only be necessary on init Process { id: fetchLayoutsProc running: true command: ["hyprctl", "-j", "devices"] stdout: StdioCollector { id: devicesCollector onStreamFinished: { const parsedOutput = JSON.parse(devicesCollector.text); const hyprlandKeyboard = parsedOutput["keyboards"].find(kb => kb.main === true); root.layoutCodes = hyprlandKeyboard["layout"].split(","); root.currentLayoutName = hyprlandKeyboard["active_keymap"]; // console.log("[HyprlandXkb] Fetched | Layouts (multiple: " + (root.layoutCodes.length > 1) + "): " // + root.layoutCodes.join(", ") + " | Active: " + root.currentLayoutName); } } } // Update the layout name when it changes Connections { target: Hyprland function onRawEvent(event) { if (event.name === "activelayout") { if (root.needsLayoutRefresh) { root.needsLayoutRefresh = false; fetchLayoutsProc.running = true; } // If there's only one layout, the updated layout is always the same if (root.layoutCodes.length <= 1) return; // Update when layout might have changed const dataString = event.data; root.currentLayoutName = dataString.substring(dataString.indexOf(",") + 1); // Update layout for on-screen keyboard (osk) Config.options.osk.layout = root.currentLayoutName.split(" (")[0]; } else if (event.name == "configreloaded") { // Mark layout code list to be updated when config is reloaded root.needsLayoutRefresh = true; } } } } ================================================ FILE: dots/.config/quickshell/ii/services/Hyprsunset.qml ================================================ pragma Singleton import QtQuick import qs.modules.common import Quickshell import Quickshell.Io import Quickshell.Hyprland /** * Simple hyprsunset service with automatic mode. * In theory we don't need this because hyprsunset has a config file, but it somehow doesn't work. * It should also be possible to control it via hyprctl, but it doesn't work consistently either so we're just killing and launching. */ Singleton { id: root signal gammaChangeAttempt() readonly property real gammaLowerLimit: 25 property string from: Config.options?.light?.night?.from ?? "19:00" property string to: Config.options?.light?.night?.to ?? "06:30" property bool automatic: Config.options?.light?.night?.automatic && (Config?.ready ?? true) property int colorTemperature: Config.options?.light?.night?.colorTemperature ?? 5000 property int defaultColorTemperature: 6000 property int gamma: 100 property bool shouldBeOn property bool firstEvaluation: true property bool temperatureActive: false property int fromHour: Number(from.split(":")[0]) property int fromMinute: Number(from.split(":")[1]) property int toHour: Number(to.split(":")[0]) property int toMinute: Number(to.split(":")[1]) property int clockHour: DateTime.clock.hours property int clockMinute: DateTime.clock.minutes property var manualActive property int manualActiveHour property int manualActiveMinute onClockMinuteChanged: reEvaluate() onAutomaticChanged: { root.manualActive = undefined; root.firstEvaluation = true; reEvaluate(); } function inBetween(t, from, to) { if (from < to) { return (t >= from && t <= to); } else { // Wrapped around midnight return (t >= from || t <= to); } } function reEvaluate() { const t = clockHour * 60 + clockMinute; const from = fromHour * 60 + fromMinute; const to = toHour * 60 + toMinute; const manualActive = manualActiveHour * 60 + manualActiveMinute; if (root.manualActive !== undefined && (inBetween(from, manualActive, t) || inBetween(to, manualActive, t))) { root.manualActive = undefined; } root.shouldBeOn = inBetween(t, from, to); if (firstEvaluation) { firstEvaluation = false; root.ensureState(); } } onShouldBeOnChanged: ensureState() function ensureState() { // console.log("[Hyprsunset] Ensuring state:", root.shouldBeOn, "Automatic mode:", root.automatic); if (!root.automatic || root.manualActive !== undefined) return; if (root.shouldBeOn) { root.enableTemperature(); } else { root.disableTemperature(); } } function startHyprsunset() { Quickshell.execDetached(["bash", "-c", `pidof hyprsunset || hyprsunset`]); } function load() { root.startHyprsunset(); root.ensureState(); } Timer { id: updateHyprsunset interval: 100 repeat: false onTriggered: { root.ensureState(); root.setGamma(root.gamma); } } function enableTemperature() { root.temperatureActive = true; // console.log("[Hyprsunset] Enabling"); root.startHyprsunset(); Quickshell.execDetached(["bash", "-c", `hyprctl hyprsunset temperature ${root.colorTemperature}`]); } function disableTemperature() { root.temperatureActive = false; // console.log("[Hyprsunset] Disabling"); Quickshell.execDetached(["bash", "-c", `hyprctl hyprsunset temperature ${root.defaultColorTemperature}`]); } function setGamma(gamma) { root.gamma = Math.max(root.gammaLowerLimit, Math.min(100, gamma)); root.gammaChangeAttempt(); root.startHyprsunset(); Quickshell.execDetached(["bash", "-c", `hyprctl hyprsunset gamma ${root.gamma}`]); } function fetchState() { fetchProc.running = true; } Process { id: fetchProc running: true command: ["bash", "-c", "hyprctl hyprsunset temperature"] stdout: StdioCollector { id: stateCollector onStreamFinished: { const output = stateCollector.text.trim(); if (output.length == 0 || output.startsWith("Couldn't")) root.temperatureActive = false; else root.temperatureActive = (output != root.defaultColorTemperature); // 6000 is the default when off // console.log("[Hyprsunset] Fetched state:", output, "->", root.temperatureActive); } } } function toggleTemperature(active = undefined) { if (root.manualActive === undefined) { root.manualActive = root.temperatureActive; root.manualActiveHour = root.clockHour; root.manualActiveMinute = root.clockMinute; } root.manualActive = active !== undefined ? active : !root.manualActive; if (root.manualActive) { root.enableTemperature(); } else { root.disableTemperature(); } } // Change temp Connections { target: Config.options.light.night function onColorTemperatureChanged() { if (!root.temperatureActive) return; Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${Config.options.light.night.colorTemperature}`]); } } } ================================================ FILE: dots/.config/quickshell/ii/services/Idle.qml ================================================ pragma Singleton import qs.modules.common import QtQuick import Quickshell import Quickshell.Wayland /** * A nice wrapper for date and time strings. */ Singleton { id: root property alias inhibit: idleInhibitor.enabled inhibit: false Connections { target: Persistent function onReadyChanged() { if (!Persistent.isNewHyprlandInstance) { root.inhibit = Persistent.states.idle.inhibit; } else { Persistent.states.idle.inhibit = root.inhibit; } } } function toggleInhibit(active = null) { if (active !== null) { root.inhibit = active; } else { root.inhibit = !root.inhibit; } Persistent.states.idle.inhibit = root.inhibit; } IdleInhibitor { id: idleInhibitor window: PanelWindow { // Inhibitor requires a "visible" surface // Actually not lol implicitWidth: 0 implicitHeight: 0 color: "transparent" // Just in case... anchors { right: true bottom: true } // Make it not interactable mask: Region { item: null } } } } ================================================ FILE: dots/.config/quickshell/ii/services/KeyringStorage.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs import qs.modules.common import qs.modules.common.functions import Quickshell; import Quickshell.Io; import QtQuick; /** * For storing sensitive data in the keyring. * Use this for small data only, since it stores a JSON of the contents directly and doesn't use a database. */ Singleton { id: root signal dataChanged() property bool loaded: false property var keyringData: ({}) property var properties: { "application": "illogical-impulse", "explanation": Translation.tr("For storing API keys and other sensitive information"), } property var propertiesAsArgs: Object.keys(root.properties).reduce( function(arr, key) { return arr.concat([key, root.properties[key]]); }, [] ) property string keyringLabel: Translation.tr("%1 Safe Storage").arg("illogical-impulse") function setNestedField(path, value) { if (!root.keyringData) root.keyringData = {}; let keys = path; let obj = root.keyringData; let parents = [obj]; // Traverse and collect parent objects for (let i = 0; i < keys.length - 1; ++i) { if (!obj[keys[i]] || typeof obj[keys[i]] !== "object") { obj[keys[i]] = {}; } obj = obj[keys[i]]; parents.push(obj); } // Set the value at the innermost key obj[keys[keys.length - 1]] = value; // Reassign each parent object from the bottom up to trigger change notifications for (let i = keys.length - 2; i >= 0; --i) { let parent = parents[i]; let key = keys[i]; // Shallow clone to change object identity (spread replaced with Object.assign) parent[key] = Object.assign({}, parent[key]); } // Finally, reassign root.keyringData to trigger top-level change root.keyringData = Object.assign({}, root.keyringData); saveKeyringData(); } function fetchKeyringData() { // console.log("[KeyringStorage] Fetching keyring data..."); // console.log("[KeyringStorage] getData command:'" + getData.command.join("' '") + "'"); getData.running = true; } function saveKeyringData() { saveData.stdinEnabled = true; saveData.running = true; } Process { id: saveData command: [ "secret-tool", "store", "--label=" + keyringLabel, ...propertiesAsArgs, ] onRunningChanged: { if (saveData.running) { // console.log("[KeyringStorage] Saving with command: '" + saveData.command.join("' '") + "'"); saveData.write(JSON.stringify(root.keyringData)); root.dataChanged() stdinEnabled = false // End input stream } } } Process { id: getData command: [ // We need to use echo for a newline so splitparser does parse "bash", "-c", `${Directories.scriptPath}/keyring/try_lookup.sh 2> /dev/null`, ] stdout: StdioCollector { id: keyringDataOutputCollector onStreamFinished: { const data = keyringDataOutputCollector.text; if (data.length === 0 || !data.startsWith("{")) return; try { root.keyringData = JSON.parse(data); // console.log("[KeyringStorage] Keyring data fetched:", JSON.stringify(root.keyringData)); } catch (e) { console.error("[KeyringStorage] Failed to get keyring data, reinitializing."); root.keyringData = {}; saveKeyringData() } } } onExited: (exitCode, exitStatus) => { // console.log("[KeyringStorage] Keyring data fetch process exited with code:", exitCode); if (exitCode === 1) { console.error("[KeyringStorage] Entry not found, initializing."); root.keyringData = {}; saveKeyringData() } if (exitCode !== 2) { root.loaded = true; } } } } ================================================ FILE: dots/.config/quickshell/ii/services/LatexRenderer.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common.functions import qs.modules.common import QtQuick import Quickshell /** * Renders LaTeX snippets with MicroTeX. * For every request: * 1. Hash it * 2. Check if the hash is already processed * 3. If not, render it with MicroTeX and mark as processed */ Singleton { id: root readonly property var renderPadding: 4 // This is to prevent cutoff in the rendered images property list processedHashes: [] property var processedExpressions: ({}) property var renderedImagePaths: ({}) property string microtexBinaryDir: "/opt/MicroTeX" property string microtexBinaryName: "LaTeX" property string latexOutputPath: Directories.latexOutput signal renderFinished(string hash, string imagePath) /** * Requests rendering of a LaTeX expression. * Returns the [hash, isNew] */ function requestRender(expression) { // 1. Hash it and initialize necessary variables const hash = Qt.md5(expression) const imagePath = `${latexOutputPath}/${hash}.svg` // 2. Check if the hash is already processed if (processedHashes.includes(hash)) { // console.log("Already processed: " + hash) renderFinished(hash, imagePath) return [hash, false] } else { root.processedHashes.push(hash) root.processedExpressions[hash] = expression // console.log("Rendering expression: " + expression) } // 3. If not, render it with MicroTeX and mark as processed // console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`) // console.log(` to file: ${imagePath}`) // console.log(` with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`) const processQml = ` import Quickshell.Io Process { id: microtexProcess${hash} running: true command: [ "bash", "-c", "cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' " + "'-output=${imagePath}' " + "'-textsize=${Appearance.font.pixelSize.normal}' " + "'-padding=${renderPadding}' " // + "'-background=${Appearance.m3colors.m3tertiary}' " + "'-foreground=${Appearance.colors.colOnLayer1}' " + "-maxwidth=0.85 " ] // stdout: SplitParser { // onRead: data => { console.log("MicroTeX: " + data) } // } onExited: (exitCode, exitStatus) => { // console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus) renderedImagePaths["${hash}"] = "${imagePath}" root.renderFinished("${hash}", "${imagePath}") microtexProcess${hash}.destroy() } } ` // console.log("MicroTeX: " + processQml) Qt.createQmlObject(processQml, root, `MicroTeXProcess_${hash}`) return [hash, true] } } ================================================ FILE: dots/.config/quickshell/ii/services/LauncherApps.qml ================================================ pragma Singleton import qs.modules.common import QtQuick import Quickshell Singleton { id: root function isPinned(appId) { return Config.options.launcher.pinnedApps.indexOf(appId) !== -1; } function togglePin(appId) { if (root.isPinned(appId)) { Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId) } else { Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([appId]) } } function moveToFront(appId) { if (!root.isPinned(appId)) return; const pinnedApps = Config.options.launcher.pinnedApps; Config.options.launcher.pinnedApps = [appId].concat(pinnedApps.filter(id => id !== appId)); } function moveLeft(appId) { const pinnedApps = Config.options.launcher.pinnedApps; const index = pinnedApps.indexOf(appId); if (index === -1 || index === 0) return; Config.options.launcher.pinnedApps = pinnedApps.slice(0, index - 1).concat([appId]).concat(pinnedApps[index - 1]).concat(pinnedApps.slice(index + 1)); } function moveRight(appId) { const pinnedApps = Config.options.launcher.pinnedApps; const index = pinnedApps.indexOf(appId); if (index === -1 || index === pinnedApps.length - 1) return; Config.options.launcher.pinnedApps = pinnedApps.slice(0, index).concat(pinnedApps[index + 1]).concat([appId]).concat(pinnedApps.slice(index + 2)); } } ================================================ FILE: dots/.config/quickshell/ii/services/LauncherSearch.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.models import qs.modules.common.functions import QtQuick import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io import Quickshell.Hyprland Singleton { id: root property string query: "" function ensurePrefix(prefix) { if ([Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch,].some(i => root.query.startsWith(i))) { root.query = prefix + root.query.slice(1); } else { root.query = prefix + root.query; } } // https://specifications.freedesktop.org/menu/latest/category-registry.html property list mainRegisteredCategories: ["AudioVideo", "Development", "Education", "Game", "Graphics", "Network", "Office", "Science", "Settings", "System", "Utility"] property list appCategories: DesktopEntries.applications.values.reduce((acc, entry) => { for (const category of entry.categories) { if (!acc.includes(category) && mainRegisteredCategories.includes(category)) { acc.push(category); } } return acc; }, []).sort() // Load user action scripts from ~/.config/illogical-impulse/actions/ // Uses FolderListModel to auto-reload when scripts are added/removed property var userActionScripts: { const actions = []; for (let i = 0; i < userActionsFolder.count; i++) { const fileName = userActionsFolder.get(i, "fileName"); const filePath = userActionsFolder.get(i, "filePath"); if (fileName && filePath) { const actionName = fileName.replace(/\.[^/.]+$/, ""); // strip extension actions.push({ action: actionName, execute: ((path) => (args) => { Quickshell.execDetached([path, ...(args ? args.split(" ") : [])]); })(FileUtils.trimFileProtocol(filePath.toString())) }); } } return actions; } FolderListModel { id: userActionsFolder folder: Qt.resolvedUrl(Directories.userActions) showDirs: false showHidden: false sortField: FolderListModel.Name } property var searchActions: [ { action: "accentcolor", execute: args => { Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--noswitch", "--color", ...(args != '' ? [`${args}`] : [])]); } }, { action: "dark", execute: () => { Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", "dark", "--noswitch"]); } }, { action: "konachanwallpaper", execute: () => { Quickshell.execDetached([Quickshell.shellPath("scripts/colors/random/random_konachan_wall.sh")]); } }, { action: "light", execute: () => { Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", "light", "--noswitch"]); } }, { action: "superpaste", execute: args => { if (!/^(\d+)/.test(args.trim())) { // Invalid if doesn't start with numbers Quickshell.execDetached(["notify-send", Translation.tr("Superpaste"), Translation.tr("Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries").arg(Config.options.search.prefix.action), "-a", "Shell"]); return; } const syntaxMatch = /^(?:(\d+)(i)?)/.exec(args.trim()); const count = syntaxMatch[1] ? parseInt(syntaxMatch[1]) : 1; const isImage = !!syntaxMatch[2]; Cliphist.superpaste(count, isImage); } }, { action: "todo", execute: args => { Todo.addTask(args); } }, { action: "wallpaper", execute: () => { Hyprland.dispatch(`hl.dsp.global("quickshell:wallpaperSelectorToggle")`) } }, { action: "wipeclipboard", execute: () => { Cliphist.wipe(); } }, ] // Combined built-in and user actions property var allActions: searchActions.concat(userActionScripts) property string mathResult: "" property bool clipboardWorkSafetyActive: { const enabled = Config.options.workSafety.enable.clipboard; const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveNetwork; } function containsUnsafeLink(entry) { if (entry == undefined) return false; const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords; return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords); } Timer { id: nonAppResultsTimer interval: Config.options.search.nonAppResultDelay onTriggered: { let expr = root.query; if (expr.startsWith(Config.options.search.prefix.math)) { expr = expr.slice(Config.options.search.prefix.math.length); } mathProc.calculateExpression(expr); } } Process { id: mathProc property list baseCommand: ["qalc", "-t"] function calculateExpression(expression) { mathProc.running = false; mathProc.command = baseCommand.concat(expression); mathProc.running = true; } stdout: SplitParser { onRead: data => { root.mathResult = data; } } } property list results: { // Search results are handled here ////////////////// Skip? ////////////////// if (root.query == "") return []; ///////////// Special cases /////////////// if (root.query.startsWith(Config.options.search.prefix.clipboard)) { // Clipboard const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.clipboard); return Cliphist.fuzzyQuery(searchString).map((entry, index, array) => { const mightBlurImage = Cliphist.entryIsImage(entry) && root.clipboardWorkSafetyActive; let shouldBlurImage = mightBlurImage; if (mightBlurImage) { shouldBlurImage = shouldBlurImage && (root.containsUnsafeLink(array[index - 1]) || root.containsUnsafeLink(array[index + 1])); } const type = `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`; return resultComp.createObject(null, { rawValue: entry, name: StringUtils.cleanCliphistEntry(entry), verb: "", type: type, execute: () => { Cliphist.copy(entry); }, actions: [resultComp.createObject(null, { name: Translation.tr("Copy"), iconName: "content_copy", iconType: LauncherSearchResult.IconType.Material, execute: () => { Cliphist.copy(entry); } }), resultComp.createObject(null, { name: Translation.tr("Delete"), iconName: "delete", iconType: LauncherSearchResult.IconType.Material, execute: () => { Cliphist.deleteEntry(entry); } })], blurImage: shouldBlurImage }); }).filter(Boolean); } else if (root.query.startsWith(Config.options.search.prefix.emojis)) { // Clipboard const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.emojis); return Emojis.fuzzyQuery(searchString).map(entry => { const emoji = entry.match(/^\s*(\S+)/)?.[1] || ""; return resultComp.createObject(null, { rawValue: entry, name: entry.replace(/^\s*\S+\s+/, ""), iconName: emoji, iconType: LauncherSearchResult.IconType.Text, verb: Translation.tr("Copy"), type: Translation.tr("Emoji"), execute: () => { Quickshell.clipboardText = entry.match(/^\s*(\S+)/)?.[1]; } }); }).filter(Boolean); } ////////////////// Init /////////////////// nonAppResultsTimer.restart(); const mathResultObject = resultComp.createObject(null, { name: root.mathResult, verb: Translation.tr("Copy"), type: Translation.tr("Math result"), fontType: LauncherSearchResult.FontType.Monospace, iconName: 'calculate', iconType: LauncherSearchResult.IconType.Material, execute: () => { Quickshell.clipboardText = root.mathResult; } }); const appResultObjects = AppSearch.fuzzyQuery(StringUtils.cleanPrefix(root.query, Config.options.search.prefix.app)).map(entry => { return resultComp.createObject(null, { type: Translation.tr("App"), id: entry.id, name: entry.name, iconName: entry.icon, iconType: LauncherSearchResult.IconType.System, verb: Translation.tr("Open"), execute: () => { if (!entry.runInTerminal) entry.execute(); else { // Probably needs more proper escaping, but this will do for now Quickshell.execDetached(["bash", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(entry.command.join(' '))}'`]); } }, comment: entry.comment, runInTerminal: entry.runInTerminal, genericName: entry.genericName, keywords: entry.keywords, actions: entry.actions.map(action => { return resultComp.createObject(null, { name: action.name, iconName: action.icon, iconType: LauncherSearchResult.IconType.System, execute: () => { if (!action.runInTerminal) action.execute(); else { Quickshell.execDetached(["bash", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(action.command.join(' '))}'`]); } } }); }) }); }); const commandResultObject = resultComp.createObject(null, { name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.shellCommand).replace("file://", ""), verb: Translation.tr("Run"), type: Translation.tr("Command"), fontType: LauncherSearchResult.FontType.Monospace, iconName: 'terminal', iconType: LauncherSearchResult.IconType.Material, execute: () => { let cleanedCommand = root.query.replace("file://", ""); cleanedCommand = StringUtils.cleanPrefix(cleanedCommand, Config.options.search.prefix.shellCommand); if (cleanedCommand.startsWith(Config.options.search.prefix.shellCommand)) { cleanedCommand = cleanedCommand.slice(Config.options.search.prefix.shellCommand.length); } Quickshell.execDetached(["bash", "-c", root.query.startsWith('sudo') ? `${Config.options.apps.terminal} fish -C '${cleanedCommand}'` : cleanedCommand]); } }); const webSearchResultObject = resultComp.createObject(null, { name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch), verb: Translation.tr("Search"), type: Translation.tr("Web search"), iconName: 'travel_explore', iconType: LauncherSearchResult.IconType.Material, execute: () => { let query = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch); let url = Config.options.search.engineBaseUrl + query; for (let site of Config.options.search.excludedSites) { url += ` -site:${site}`; } Qt.openUrlExternally(url); } }); const launcherActionObjects = root.allActions.map(action => { const actionString = `${Config.options.search.prefix.action}${action.action}`; if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) { return resultComp.createObject(null, { name: root.query.startsWith(actionString) ? root.query : actionString, verb: Translation.tr("Run"), type: Translation.tr("Action"), iconName: 'settings_suggest', iconType: LauncherSearchResult.IconType.Material, execute: () => { action.execute(root.query.split(" ").slice(1).join(" ")); } }); } return null; }).filter(Boolean); //////// Prioritized by prefix ///////// let result = []; const startsWithNumber = /^\d/.test(root.query); const startsWithMathPrefix = root.query.startsWith(Config.options.search.prefix.math); const startsWithShellCommandPrefix = root.query.startsWith(Config.options.search.prefix.shellCommand); const startsWithWebSearchPrefix = root.query.startsWith(Config.options.search.prefix.webSearch); if (startsWithNumber || startsWithMathPrefix) { result.push(mathResultObject); } else if (startsWithShellCommandPrefix) { result.push(commandResultObject); } else if (startsWithWebSearchPrefix) { result.push(webSearchResultObject); } //////////////// Apps ////////////////// result = result.concat(appResultObjects); ////////// Launcher actions //////////// result = result.concat(launcherActionObjects); /// Math result, command, web search /// if (Config.options.search.prefix.showDefaultActionsWithoutPrefix) { if (!startsWithShellCommandPrefix) result.push(commandResultObject); if (!startsWithNumber && !startsWithMathPrefix) result.push(mathResultObject); if (!startsWithWebSearchPrefix) result.push(webSearchResultObject); } return result; } Component { id: resultComp LauncherSearchResult {} } } ================================================ FILE: dots/.config/quickshell/ii/services/MaterialThemeLoader.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Io import Quickshell.Hyprland /** * Automatically reloads generated material colors. * It is necessary to run reapplyTheme() on startup because Singletons are lazily loaded. */ Singleton { id: root property string filePath: Directories.generatedMaterialThemePath function reapplyTheme() { themeFileView.reload() } function applyColors(fileContent) { const json = JSON.parse(fileContent) for (const key in json) { if (json.hasOwnProperty(key)) { // Convert snake_case to CamelCase const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) const m3Key = `m3${camelCaseKey}` Appearance.m3colors[m3Key] = json[key] } } Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5) } function resetFilePathNextTime() { resetFilePathNextWallpaperChange.enabled = true } Connections { id: resetFilePathNextWallpaperChange enabled: false target: Config.options.background function onWallpaperPathChanged() { root.filePath = "" root.filePath = Directories.generatedMaterialThemePath resetFilePathNextWallpaperChange.enabled = false } } Timer { id: delayedFileRead interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100 repeat: false running: false onTriggered: { root.applyColors(themeFileView.text()) } } FileView { id: themeFileView path: Qt.resolvedUrl(root.filePath) watchChanges: true onFileChanged: { this.reload() delayedFileRead.start() } onLoadedChanged: { const fileContent = themeFileView.text() root.applyColors(fileContent) } onLoadFailed: root.resetFilePathNextTime(); } function toggleLightDark() { const currentlyDark = Appearance.m3colors.darkmode; Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", currentlyDark ? "light" : "dark", "--noswitch"]); } GlobalShortcut { name: "toggleLightDark" description: "Toggles between dark theme and light theme" onPressed: { root.toggleLightDark(); } } IpcHandler { target: "theme" function toggleLightDark(): void { root.toggleLightDark(); } } } ================================================ FILE: dots/.config/quickshell/ii/services/MprisController.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound // From https://git.outfoxxed.me/outfoxxed/nixnew // It does not have a license, but the author is okay with redistribution. import QtQml.Models import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Mpris import qs.modules.common /** * A service that provides easy access to the active Mpris player. */ Singleton { id: root; property list players: Mpris.players.values.filter(player => isRealPlayer(player)); property MprisPlayer trackedPlayer: null; property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null; signal trackChanged(reverse: bool); property bool __reverse: false; property var activeTrack; readonly property bool hasActivePlasmaIntegration: Mpris.players.values.some( p => p.dbusName?.startsWith('org.mpris.MediaPlayer2.plasma-browser-integration') ) function isRealPlayer(player) { if (!Config.options.media.filterDuplicatePlayers) { return true; } return ( // Remove native browser buses only if plasma-browser-integration is actually active on D-Bus !(hasActivePlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) && !(hasActivePlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) && // playerctld just copies other buses and we don't need duplicates !player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') && // Non-instance mpd bus !(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd'))); } // Original stuff from fox below Instantiator { model: Mpris.players; Connections { required property MprisPlayer modelData; target: modelData; Component.onCompleted: { if (root.trackedPlayer == null || modelData.isPlaying) { root.trackedPlayer = modelData; } } Component.onDestruction: { if (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) { for (const player of Mpris.players.values) { if (player.playbackState.isPlaying) { root.trackedPlayer = player; break; } } if (trackedPlayer == null && Mpris.players.values.length != 0) { trackedPlayer = Mpris.players.values[0]; } } } function onPlaybackStateChanged() { if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData; } } } Connections { target: activePlayer function onPostTrackChanged() { root.updateTrack(); } function onTrackArtUrlChanged() { // console.log("arturl:", activePlayer.trackArtUrl) // root.updateTrack(); if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) { // cantata likes to send cover updates *BEFORE* updating the track info. // as such, art url changes shouldn't be able to break the reverse animation const r = root.__reverse; root.updateTrack(); root.__reverse = r; } } } onActivePlayerChanged: this.updateTrack(); function updateTrack() { //console.log(`update: ${this.activePlayer?.trackTitle ?? ""} : ${this.activePlayer?.trackArtists}`) this.activeTrack = { uniqueId: this.activePlayer?.uniqueId ?? 0, artUrl: this.activePlayer?.trackArtUrl ?? "", title: this.activePlayer?.trackTitle || Translation.tr("Unknown Title"), artist: this.activePlayer?.trackArtist || Translation.tr("Unknown Artist"), album: this.activePlayer?.trackAlbum || Translation.tr("Unknown Album"), }; this.trackChanged(__reverse); this.__reverse = false; } property bool isPlaying: this.activePlayer && this.activePlayer.isPlaying; property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false; function togglePlaying() { if (this.canTogglePlaying) this.activePlayer.togglePlaying(); } property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false; function previous() { if (this.canGoPrevious) { this.__reverse = true; this.activePlayer.previous(); } } property bool canGoNext: this.activePlayer?.canGoNext ?? false; function next() { if (this.canGoNext) { this.__reverse = false; this.activePlayer.next(); } } property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl; property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl; property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None; function setLoopState(loopState: var) { if (this.loopSupported) { this.activePlayer.loopState = loopState; } } property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl; property bool hasShuffle: this.activePlayer?.shuffle ?? false; function setShuffle(shuffle: bool) { if (this.shuffleSupported) { this.activePlayer.shuffle = shuffle; } } function setActivePlayer(player: MprisPlayer) { const targetPlayer = player ?? Mpris.players[0]; console.log(`[Mpris] Active player ${targetPlayer} << ${activePlayer}`) if (targetPlayer && this.activePlayer) { this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer); } else { // always animate forward if going to null this.__reverse = false; } this.trackedPlayer = targetPlayer; } IpcHandler { target: "mpris" function pauseAll(): void { for (const player of Mpris.players.values) { if (player.canPause) player.pause(); } } function playPause(): void { root.togglePlaying(); } function previous(): void { root.previous(); } function next(): void { root.next(); } } } ================================================ FILE: dots/.config/quickshell/ii/services/Network.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound // Took many bits from https://github.com/caelestia-dots/shell (GPLv3) import Quickshell import Quickshell.Io import QtQuick import qs.services.network /** * Network service with nmcli. */ Singleton { id: root property bool wifi: true property bool ethernet: false property bool wifiEnabled: false property bool wifiScanning: false property bool wifiConnecting: connectProc.running property WifiAccessPoint wifiConnectTarget readonly property list wifiNetworks: [] readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null readonly property list friendlyWifiNetworks: [...wifiNetworks].sort((a, b) => { if (a.active && !b.active) return -1; if (!a.active && b.active) return 1; return b.strength - a.strength; }) property string wifiStatus: "disconnected" property string networkName: "" property int networkStrength property string materialSymbol: root.ethernet ? "lan" : (root.wifiEnabled && root.wifiStatus === "connected") ? ( (root.active?.strength ?? 0) > 83 ? "signal_wifi_4_bar" : (root.active?.strength ?? 0) > 67 ? "network_wifi" : (root.active?.strength ?? 0) > 50 ? "network_wifi_3_bar" : (root.active?.strength ?? 0) > 33 ? "network_wifi_2_bar" : (root.active?.strength ?? 0) > 17 ? "network_wifi_1_bar" : "signal_wifi_0_bar" ) : (root.wifiStatus === "connecting") ? "signal_wifi_statusbar_not_connected" : (root.wifiStatus === "disconnected") ? "wifi_find" : (root.wifiStatus === "disabled") ? "signal_wifi_off" : "signal_wifi_bad" // Control function enableWifi(enabled = true): void { const cmd = enabled ? "on" : "off"; enableWifiProc.exec(["nmcli", "radio", "wifi", cmd]); } function toggleWifi(): void { enableWifi(!wifiEnabled); } function rescanWifi(): void { wifiScanning = true; rescanProcess.running = true; } function connectToWifiNetwork(accessPoint: WifiAccessPoint): void { accessPoint.askingPassword = false; root.wifiConnectTarget = accessPoint; // We use this instead of `nmcli connection up SSID` because this also creates a connection profile connectProc.exec(["nmcli", "dev", "wifi", "connect", accessPoint.ssid]) } function disconnectWifiNetwork(): void { if (active) disconnectProc.exec(["nmcli", "connection", "down", active.ssid]); } function openPublicWifiPortal() { Quickshell.execDetached(["xdg-open", "https://nmcheck.gnome.org/"]) // From some StackExchange thread, seems to work } function changePassword(network: WifiAccessPoint, password: string, username = ""): void { // TODO: enterprise wifi with username network.askingPassword = false; changePasswordProc.exec({ "environment": { "PASSWORD": password, "SSID": network.ssid }, "command": ["bash", "-c", 'nmcli connection modify "$SSID" wifi-sec.psk "$PASSWORD"'] }) } Process { id: enableWifiProc } Process { id: connectProc environment: ({ LANG: "C", LC_ALL: "C" }) stdout: SplitParser { onRead: line => { // print(line) getNetworks.running = true } } stderr: SplitParser { onRead: line => { // print("err:", line) if (line.includes("Secrets were required")) { root.wifiConnectTarget.askingPassword = true } } } onExited: (exitCode, exitStatus) => { root.wifiConnectTarget.askingPassword = (exitCode !== 0) root.wifiConnectTarget = null } } Process { id: disconnectProc stdout: SplitParser { onRead: getNetworks.running = true } } Process { id: changePasswordProc onExited: { // Re-attempt connection after changing password connectProc.running = false connectProc.running = true } } Process { id: rescanProcess command: ["nmcli", "dev", "wifi", "list", "--rescan", "yes"] stdout: SplitParser { onRead: { wifiScanning = false; getNetworks.running = true; } } } // Status update function update() { updateConnectionType.startCheck(); wifiStatusProcess.running = true updateNetworkName.running = true; updateNetworkStrength.running = true; } Process { id: subscriber running: true command: ["nmcli", "monitor"] stdout: SplitParser { onRead: root.update() } } Process { id: updateConnectionType property string buffer command: ["sh", "-c", "nmcli -t -f TYPE,STATE d status && nmcli -t -f CONNECTIVITY g"] running: true function startCheck() { buffer = ""; updateConnectionType.running = true; } stdout: SplitParser { onRead: data => { updateConnectionType.buffer += data + "\n"; } } onExited: (exitCode, exitStatus) => { const lines = updateConnectionType.buffer.trim().split('\n'); const connectivity = lines.pop() // none, limited, full let hasEthernet = false; let hasWifi = false; let wifiStatus = "disconnected"; lines.forEach(line => { if (line.includes("ethernet") && line.includes("connected")) hasEthernet = true; else if (line.includes("wifi:")) { if (line.includes("disconnected")) { wifiStatus = "disconnected" } else if (line.includes("connected")) { hasWifi = true; wifiStatus = "connected" if (connectivity === "limited") { hasWifi = false; wifiStatus = "limited" } } else if (line.includes("connecting")) { wifiStatus = "connecting" } else if (line.includes("unavailable")) { wifiStatus = "disabled" } } }); root.wifiStatus = wifiStatus; root.ethernet = hasEthernet; root.wifi = hasWifi; } } Process { id: updateNetworkName command: ["sh", "-c", "nmcli -t -f NAME c show --active | head -1"] running: true stdout: SplitParser { onRead: data => { root.networkName = data; } } } Process { id: updateNetworkStrength running: true command: ["sh", "-c", "nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\\*/{if (NR!=1) {print $2}}'"] stdout: SplitParser { onRead: data => { root.networkStrength = parseInt(data); } } } Process { id: wifiStatusProcess command: ["nmcli", "radio", "wifi"] Component.onCompleted: running = true environment: ({ LANG: "C", LC_ALL: "C" }) stdout: StdioCollector { onStreamFinished: { root.wifiEnabled = text.trim() === "enabled"; } } } Process { id: getNetworks running: true command: ["nmcli", "-g", "ACTIVE,SIGNAL,FREQ,SSID,BSSID,SECURITY", "d", "w"] environment: ({ LANG: "C", LC_ALL: "C" }) stdout: StdioCollector { onStreamFinished: { const PLACEHOLDER = "STRINGWHICHHOPEFULLYWONTBEUSED"; const rep = new RegExp("\\\\:", "g"); const rep2 = new RegExp(PLACEHOLDER, "g"); const allNetworks = text.trim().split("\n").map(n => { const net = n.replace(rep, PLACEHOLDER).split(":"); return { active: net[0] === "yes", strength: parseInt(net[1]), frequency: parseInt(net[2]), ssid: net[3], bssid: net[4]?.replace(rep2, ":") ?? "", security: net[5] || "" }; }).filter(n => n.ssid && n.ssid.length > 0); // Group networks by SSID and prioritize connected ones const networkMap = new Map(); for (const network of allNetworks) { const existing = networkMap.get(network.ssid); if (!existing) { networkMap.set(network.ssid, network); } else { // Prioritize active/connected networks if (network.active && !existing.active) { networkMap.set(network.ssid, network); } else if (!network.active && !existing.active) { // If both are inactive, keep the one with better signal if (network.strength > existing.strength) { networkMap.set(network.ssid, network); } } // If existing is active and new is not, keep existing } } const wifiNetworks = Array.from(networkMap.values()); const rNetworks = root.wifiNetworks; const destroyed = rNetworks.filter(rn => !wifiNetworks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid)); for (const network of destroyed) rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy()); for (const network of wifiNetworks) { const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid); if (match) { match.lastIpcObject = network; } else { rNetworks.push(apComp.createObject(root, { lastIpcObject: network })); } } } } } Component { id: apComp WifiAccessPoint {} } } ================================================ FILE: dots/.config/quickshell/ii/services/Notifications.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import qs import QtQuick import Quickshell import Quickshell.Io import Quickshell.Services.Notifications /** * Provides extra features not in Quickshell.Services.Notifications: * - Persistent storage * - Popup notifications, with timeout * - Notification groups by app */ Singleton { id: root component Notif: QtObject { id: wrapper required property int notificationId // Could just be `id` but it conflicts with the default prop in QtObject property Notification notification property list actions: notification?.actions.map((action) => ({ "identifier": action.identifier, "text": action.text, })) ?? [] property bool popup: false property bool isTransient: notification?.hints.transient ?? false property string appIcon: notification?.appIcon ?? "" property string appName: notification?.appName ?? "" property string body: notification?.body ?? "" property string image: notification?.image ?? "" property string summary: notification?.summary ?? "" property double time property string urgency: notification?.urgency.toString() ?? "normal" property Timer timer onNotificationChanged: { if (notification === null) { root.discardNotification(notificationId); } } } function notifToJSON(notif) { return { "notificationId": notif.notificationId, "actions": notif.actions, "appIcon": notif.appIcon, "appName": notif.appName, "body": notif.body, "image": notif.image, "summary": notif.summary, "time": notif.time, "urgency": notif.urgency, } } function notifToString(notif) { return JSON.stringify(notifToJSON(notif), null, 2); } component NotifTimer: Timer { required property int notificationId interval: 7000 running: true onTriggered: () => { const index = root.list.findIndex((notif) => notif.notificationId === notificationId); const notifObject = root.list[index]; print("[Notifications] Notification timer triggered for ID: " + notificationId + ", transient: " + notifObject?.isTransient); if (notifObject.isTransient) root.discardNotification(notificationId); else root.timeoutNotification(notificationId); destroy() } } property bool silent: false property int unread: 0 property var filePath: Directories.notificationsPath property list list: [] property var popupList: list.filter((notif) => notif.popup); property bool popupInhibited: (GlobalStates?.sidebarRightOpen ?? false) || silent property var latestTimeForApp: ({}) Component { id: notifComponent Notif {} } Component { id: notifTimerComponent NotifTimer {} } function stringifyList(list) { return JSON.stringify(list.map((notif) => notifToJSON(notif)), null, 2); } onListChanged: { // Update latest time for each app root.list.forEach((notif) => { if (!root.latestTimeForApp[notif.appName] || notif.time > root.latestTimeForApp[notif.appName]) { root.latestTimeForApp[notif.appName] = Math.max(root.latestTimeForApp[notif.appName] || 0, notif.time); } }); // Remove apps that no longer have notifications Object.keys(root.latestTimeForApp).forEach((appName) => { if (!root.list.some((notif) => notif.appName === appName)) { delete root.latestTimeForApp[appName]; } }); } function appNameListForGroups(groups) { return Object.keys(groups).sort((a, b) => { // Sort by time, descending return groups[b].time - groups[a].time; }); } function groupsForList(list) { const groups = {}; list.forEach((notif) => { if (!groups[notif.appName]) { groups[notif.appName] = { appName: notif.appName, appIcon: notif.appIcon, notifications: [], time: 0 }; } groups[notif.appName].notifications.push(notif); // Always set to the latest time in the group groups[notif.appName].time = latestTimeForApp[notif.appName] || notif.time; }); return groups; } property var groupsByAppName: groupsForList(root.list) property var popupGroupsByAppName: groupsForList(root.popupList) property list appNameList: appNameListForGroups(root.groupsByAppName) property list popupAppNameList: appNameListForGroups(root.popupGroupsByAppName) // Quickshell's notification IDs starts at 1 on each run, while saved notifications // can already contain higher IDs. This is for avoiding id collisions property int idOffset signal initDone(); signal notify(notification: var); signal discard(id: int); signal discardAll(); signal timeout(id: var); NotificationServer { id: notifServer // actionIconsSupported: true actionsSupported: true bodyHyperlinksSupported: true bodyImagesSupported: true bodyMarkupSupported: true bodySupported: true imageSupported: true keepOnReload: false persistenceSupported: true onNotification: (notification) => { notification.tracked = true const newNotifObject = notifComponent.createObject(root, { "notificationId": notification.id + root.idOffset, "notification": notification, "time": Date.now(), }); root.list = [...root.list, newNotifObject]; // Popup if (!root.popupInhibited) { newNotifObject.popup = true; if (notification.expireTimeout != 0) { newNotifObject.timer = notifTimerComponent.createObject(root, { "notificationId": newNotifObject.notificationId, "interval": notification.expireTimeout < 0 ? (Config?.options.notifications.timeout ?? 7000) : notification.expireTimeout, }); } root.unread++; } root.notify(newNotifObject); // console.log(notifToString(newNotifObject)); notifFileView.setText(stringifyList(root.list)); } } function markAllRead() { root.unread = 0; } function discardNotification(id) { console.log("[Notifications] Discarding notification with ID: " + id); const index = root.list.findIndex((notif) => notif.notificationId === id); const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id); if (index !== -1) { root.list.splice(index, 1); notifFileView.setText(stringifyList(root.list)); triggerListChange() } if (notifServerIndex !== -1) { notifServer.trackedNotifications.values[notifServerIndex].dismiss() } root.discard(id); // Emit signal } function discardAllNotifications() { root.list = [] triggerListChange() notifFileView.setText(stringifyList(root.list)); notifServer.trackedNotifications.values.forEach((notif) => { notif.dismiss() }) root.discardAll(); } function cancelTimeout(id) { const index = root.list.findIndex((notif) => notif.notificationId === id); if (root.list[index] != null) root.list[index].timer.stop(); } function timeoutNotification(id) { const index = root.list.findIndex((notif) => notif.notificationId === id); if (root.list[index] != null) root.list[index].popup = false; root.timeout(id); } function timeoutAll() { root.popupList.forEach((notif) => { root.timeout(notif.notificationId); }) root.popupList.forEach((notif) => { notif.popup = false; }); } function attemptInvokeAction(id, notifIdentifier) { console.log("[Notifications] Attempting to invoke action with identifier: " + notifIdentifier + " for notification ID: " + id); const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id); console.log("Notification server index: " + notifServerIndex); if (notifServerIndex !== -1) { const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex]; const action = notifServerNotif.actions.find((action) => action.identifier === notifIdentifier); // console.log("Action found: " + JSON.stringify(action)); action.invoke() } else { console.log("Notification not found in server: " + id) } root.discardNotification(id); } function triggerListChange() { root.list = root.list.slice(0) } function refresh() { notifFileView.reload() } Component.onCompleted: { refresh() } FileView { id: notifFileView path: Qt.resolvedUrl(filePath) onLoaded: { const fileContents = notifFileView.text() root.list = JSON.parse(fileContents).map((notif) => { return notifComponent.createObject(root, { "notificationId": notif.notificationId, "actions": [], // Notification actions are meaningless if they're not tracked by the server or the sender is dead "appIcon": notif.appIcon, "appName": notif.appName, "body": notif.body, "image": notif.image, "summary": notif.summary, "time": notif.time, "urgency": notif.urgency, }); }); // Find largest notificationId let maxId = 0 root.list.forEach((notif) => { maxId = Math.max(maxId, notif.notificationId) }) console.log("[Notifications] File loaded") root.idOffset = maxId root.initDone() } onLoadFailed: (error) => { if(error == FileViewError.FileNotFound) { console.log("[Notifications] File not found, creating new file.") root.list = [] notifFileView.setText(stringifyList(root.list)); } else { console.log("[Notifications] Error loading file: " + error) } } } } ================================================ FILE: dots/.config/quickshell/ii/services/PolkitService.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Services.Polkit Singleton { id: root property alias agent: polkitAgent property alias active: polkitAgent.isActive property alias flow: polkitAgent.flow property bool interactionAvailable: false property string cleanMessage: { if (!root.flow) return ""; return root.flow.message.endsWith(".") ? root.flow.message.slice(0, -1) : root.flow.message } property string cleanPrompt: { const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? ""; const cleanedInputPrompt = inputPrompt.endsWith(":") ? inputPrompt.slice(0, -1) : inputPrompt; const usePasswordChars = !PolkitService.flow?.responseVisible ?? true return cleanedInputPrompt || (usePasswordChars ? Translation.tr("Password") : Translation.tr("Input")) } function cancel() { root.flow.cancelAuthenticationRequest() } function submit(string) { root.flow.submit(string) root.interactionAvailable = false } Connections { target: root.flow function onAuthenticationFailed() { root.interactionAvailable = true; } } PolkitAgent { id: polkitAgent onAuthenticationRequestStarted: { root.interactionAvailable = true; } } } ================================================ FILE: dots/.config/quickshell/ii/services/Privacy.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Services.Pipewire /** * Screensharing and mic activity. */ Singleton { id: root property bool screenSharing: Pipewire.linkGroups.values.filter(pwlg => pwlg.source.type === PwNodeType.VideoSource).map(pwlg => pwlg.target) property bool micActive: Pipewire.linkGroups.values.filter(pwlg => pwlg.source.type === PwNodeType.AudioSource && pwlg.target.type === PwNodeType.AudioInStream).map(pwlg => pwlg.target) } ================================================ FILE: dots/.config/quickshell/ii/services/ResourceUsage.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Io /** * Simple polled resource usage service with RAM, Swap, and CPU usage. */ Singleton { id: root property real memoryTotal: 1 property real memoryFree: 0 property real memoryUsed: memoryTotal - memoryFree property real memoryUsedPercentage: memoryUsed / memoryTotal property real swapTotal: 1 property real swapFree: 0 property real swapUsed: swapTotal - swapFree property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0 property real cpuUsage: 0 property var previousCpuStats property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal) property string maxAvailableSwapString: kbToGbString(ResourceUsage.swapTotal) property string maxAvailableCpuString: "--" readonly property int historyLength: Config?.options.resources.historyLength ?? 60 property list cpuUsageHistory: [] property list memoryUsageHistory: [] property list swapUsageHistory: [] function kbToGbString(kb) { return (kb / (1024 * 1024)).toFixed(1) + " GB"; } function updateMemoryUsageHistory() { memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage] if (memoryUsageHistory.length > historyLength) { memoryUsageHistory.shift() } } function updateSwapUsageHistory() { swapUsageHistory = [...swapUsageHistory, swapUsedPercentage] if (swapUsageHistory.length > historyLength) { swapUsageHistory.shift() } } function updateCpuUsageHistory() { cpuUsageHistory = [...cpuUsageHistory, cpuUsage] if (cpuUsageHistory.length > historyLength) { cpuUsageHistory.shift() } } function updateHistories() { updateMemoryUsageHistory() updateSwapUsageHistory() updateCpuUsageHistory() } Timer { interval: 1 running: true repeat: true onTriggered: { // Reload files fileMeminfo.reload() fileStat.reload() // Parse memory and swap usage const textMeminfo = fileMeminfo.text() memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1) memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0) swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1) swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0) // Parse CPU usage const textStat = fileStat.text() const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/) if (cpuLine) { const stats = cpuLine.slice(1).map(Number) const total = stats.reduce((a, b) => a + b, 0) const idle = stats[3] if (previousCpuStats) { const totalDiff = total - previousCpuStats.total const idleDiff = idle - previousCpuStats.idle cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0 } previousCpuStats = { total, idle } } root.updateHistories() interval = Config.options?.resources?.updateInterval ?? 3000 } } FileView { id: fileMeminfo; path: "/proc/meminfo" } FileView { id: fileStat; path: "/proc/stat" } Process { id: findCpuMaxFreqProc environment: ({ LANG: "C", LC_ALL: "C" }) command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"] running: true stdout: StdioCollector { id: outputCollector onStreamFinished: { root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + " GHz" } } } } ================================================ FILE: dots/.config/quickshell/ii/services/SessionWarnings.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io Singleton { id: root property bool packageManagerRunning: false property bool downloadRunning: false function refresh() { packageManagerRunning = false; downloadRunning = false; detectPackageManagerProc.running = false; detectPackageManagerProc.running = true; detectDownloadProc.running = false; detectDownloadProc.running = true; } Process { id: detectPackageManagerProc command: ["bash", "-c", "pidof yay paru dnf zypper apt apx xbps snap apk yum epsi pikman || ls /var/lib/pacman/db.lck"] onExited: (exitCode, exitStatus) => { root.packageManagerRunning = (exitCode === 0); } } Process { id: detectDownloadProc command: ["bash", "-c", "pidof curl wget aria2c yt-dlp || ls ~/Downloads | grep -E '\.crdownload$|\.part$'"] onExited: (exitCode, exitStatus) => { root.downloadRunning = (exitCode === 0); } } } ================================================ FILE: dots/.config/quickshell/ii/services/SongRec.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import QtQuick import Quickshell import Quickshell.Io Singleton { id: root enum MonitorSource { Monitor, Input } property var monitorSource: SongRec.MonitorSource.Monitor property int timeoutInterval: Config.options.musicRecognition.interval property int timeoutDuration: Config.options.musicRecognition.timeout readonly property bool running: recognizeMusicProc.running function toggleRunning(running) { if (recognizeMusicProc.running && !running === true) root.manuallyStopped = true; if (running != undefined) { recognizeMusicProc.running = running } else { recognizeMusicProc.running = !root.running } musicReconizedProc.running = false } function toggleMonitorSource(source) { if (source !== undefined) { root.monitorSource = source return } root.monitorSource = (root.monitorSource === SongRec.MonitorSource.Monitor) ? SongRec.MonitorSource.Input : SongRec.MonitorSource.Monitor } function monitorSourceToString(source) { if (source === SongRec.MonitorSource.Monitor) { return "monitor" } else { return "input" } } readonly property string monitorSourceString: monitorSourceToString(monitorSource) property var recognizedTrack: ({ title:"", subtitle:"", url:""}) property bool manuallyStopped: false function handleRecognition(jsonText) { try { var obj = JSON.parse(jsonText) root.recognizedTrack = { title: obj.track.title, subtitle: obj.track.subtitle, url: obj.track.url } musicReconizedProc.running = true } catch(e) { Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Perhaps what you're listening to is too niche"), "-a", "Shell"]) } } Process { id: recognizeMusicProc running: false command: [`${Directories.scriptPath}/musicRecognition/recognize-music.sh`, "-i", root.timeoutInterval, "-t", root.timeoutDuration, "-s", root.monitorSourceString] stdout: StdioCollector { onStreamFinished: { if (root.manuallyStopped) { root.manuallyStopped = false return } handleRecognition(this.text) } } onExited: (exitCode, exitStatus) => { if (exitCode === 1) { Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Make sure you have songrec installed"), "-a", "Shell"]) } } } Process { id: musicReconizedProc running: false command: [ "notify-send", Translation.tr("Music Recognized"), root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle, "-A", "Shazam", "-A", "YouTube", "-a", "Shell" ] stdout: StdioCollector { onStreamFinished: { if (this.text === "") return if (this.text == 0) { Qt.openUrlExternally(root.recognizedTrack.url); } else { Qt.openUrlExternally("https://www.youtube.com/results?search_query=" + root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle); } } } } } ================================================ FILE: dots/.config/quickshell/ii/services/SystemInfo.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import QtQuick import Quickshell import Quickshell.Io /** * Provides some system info: distro, username. */ Singleton { id: root property string distroName: "Unknown" property string distroId: "unknown" property string distroIcon: "linux-symbolic" property string username: "user" property string homeUrl: "" property string documentationUrl: "" property string supportUrl: "" property string bugReportUrl: "" property string privacyPolicyUrl: "" property string logo: "" property string desktopEnvironment: "" property string windowingSystem: "" Timer { triggeredOnStart: true interval: 1 running: true repeat: false onTriggered: { getUsername.running = true fileOsRelease.reload() const textOsRelease = fileOsRelease.text() // Extract the friendly name (PRETTY_NAME field, fallback to NAME) const prettyNameMatch = textOsRelease.match(/^PRETTY_NAME="(.+?)"/m) const nameMatch = textOsRelease.match(/^NAME="(.+?)"/m) distroName = prettyNameMatch ? prettyNameMatch[1] : (nameMatch ? nameMatch[1].replace(/Linux/i, "").trim() : "Unknown") // Extract the ID const idMatch = textOsRelease.match(/^ID="?(.+?)"?$/m) distroId = idMatch ? idMatch[1] : "unknown" // Extract additional URLs and logo const homeUrlMatch = textOsRelease.match(/^HOME_URL="(.+?)"/m) homeUrl = homeUrlMatch ? homeUrlMatch[1] : "" const documentationUrlMatch = textOsRelease.match(/^DOCUMENTATION_URL="(.+?)"/m) documentationUrl = documentationUrlMatch ? documentationUrlMatch[1] : "" const supportUrlMatch = textOsRelease.match(/^SUPPORT_URL="(.+?)"/m) supportUrl = supportUrlMatch ? supportUrlMatch[1] : "" const bugReportUrlMatch = textOsRelease.match(/^BUG_REPORT_URL="(.+?)"/m) bugReportUrl = bugReportUrlMatch ? bugReportUrlMatch[1] : "" const privacyPolicyUrlMatch = textOsRelease.match(/^PRIVACY_POLICY_URL="(.+?)"/m) privacyPolicyUrl = privacyPolicyUrlMatch ? privacyPolicyUrlMatch[1] : "" const logoFieldMatch = textOsRelease.match(/^LOGO="?(.+?)"?$/m) logo = logoFieldMatch ? logoFieldMatch[1] : "" // Update the distroIcon property based on distroId switch (distroId) { case "artix": case "arch": distroIcon = "arch-symbolic"; break; case "endeavouros": distroIcon = "endeavouros-symbolic"; break; case "cachyos": distroIcon = "cachyos-symbolic"; break; case "nixos": distroIcon = "nixos-symbolic"; break; case "fedora": distroIcon = "fedora-symbolic"; break; case "linuxmint": case "ubuntu": case "zorin": case "popos": distroIcon = "ubuntu-symbolic"; break; case "debian": case "raspbian": case "kali": distroIcon = "debian-symbolic"; break; case "funtoo": case "gentoo": distroIcon = "gentoo-symbolic"; break; default: distroIcon = "linux-symbolic"; break; } if (textOsRelease.toLowerCase().includes("nyarch")) { distroIcon = "nyarch-symbolic" } if (logo.trim().length === 0) { logo = distroIcon } } } Process { id: getUsername command: ["whoami"] stdout: SplitParser { onRead: data => { root.username = data.trim() } } } Process { id: getDesktopEnvironment running: true command: ["bash", "-c", "echo $XDG_CURRENT_DESKTOP,$WAYLAND_DISPLAY"] stdout: StdioCollector { id: deCollector onStreamFinished: { const [desktop, wayland] = deCollector.text.split(",") root.desktopEnvironment = desktop.trim() root.windowingSystem = wayland.trim().length > 0 ? "Wayland" : "X11" // Are there others? 🤔 } } } FileView { id: fileOsRelease path: "/etc/os-release" } } ================================================ FILE: dots/.config/quickshell/ii/services/TaskbarApps.qml ================================================ pragma Singleton import qs.modules.common import QtQuick import Quickshell import Quickshell.Wayland Singleton { id: root function isPinned(appId) { return Config.options.dock.pinnedApps.indexOf(appId) !== -1; } function togglePin(appId) { if (root.isPinned(appId)) { Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.filter(id => id !== appId) } else { Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.concat([appId]) } } property list apps: { var map = new Map(); // Pinned apps const pinnedApps = Config.options?.dock.pinnedApps ?? []; for (const appId of pinnedApps) { if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({ pinned: true, toplevels: [] })); } // Separator if (pinnedApps.length > 0) { map.set("SEPARATOR", { pinned: false, toplevels: [] }); } // Ignored apps const ignoredRegexStrings = Config.options?.dock.ignoredAppRegexes ?? []; const ignoredRegexes = ignoredRegexStrings.map(pattern => new RegExp(pattern, "i")); // Open windows for (const toplevel of ToplevelManager.toplevels.values) { if (ignoredRegexes.some(re => re.test(toplevel.appId))) continue; if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({ pinned: false, toplevels: [] })); map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel); } var values = []; for (const [key, value] of map) { values.push(appEntryComp.createObject(null, { appId: key, toplevels: value.toplevels, pinned: value.pinned })); } return values; } component TaskbarAppEntry: QtObject { id: wrapper required property string appId required property list toplevels required property bool pinned } Component { id: appEntryComp TaskbarAppEntry {} } } ================================================ FILE: dots/.config/quickshell/ii/services/TimerService.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.services import qs.modules.common import Quickshell import Quickshell.Io import QtQuick /** * Simple Pomodoro time manager. */ Singleton { id: root property int focusTime: Config.options.time.pomodoro.focus property int breakTime: Config.options.time.pomodoro.breakTime property int longBreakTime: Config.options.time.pomodoro.longBreak property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak property bool pomodoroRunning: Persistent.states.timer.pomodoro.running property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak property bool pomodoroLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); property int pomodoroLapDuration: pomodoroLongBreak ? longBreakTime : pomodoroBreak ? breakTime : focusTime // This is a binding that's to be kept property int pomodoroSecondsLeft: pomodoroLapDuration // Reasonable init value, to be changed property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle property bool stopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 property int stopwatchStart: Persistent.states.timer.stopwatch.start property var stopwatchLaps: Persistent.states.timer.stopwatch.laps // General Component.onCompleted: { if (!stopwatchRunning) stopwatchReset(); } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds return Math.floor(Date.now() / 1000); } function getCurrentTimeIn10ms() { // Stopwatch uses 10ms return Math.floor(Date.now() / 10); } // Pomodoro function refreshPomodoro() { // Work <-> break ? if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); // Send notification let notificationMessage; if (Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak)) { notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60)); } else { notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60)); } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); if (Config.options.sounds.pomodoro) { Audio.playSystemSound("alarm-clock-elapsed") } if (!pomodoroBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; } } pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start); } Timer { id: pomodoroTimer interval: 200 running: root.pomodoroRunning repeat: true onTriggered: refreshPomodoro() } function togglePomodoro() { Persistent.states.timer.pomodoro.running = !pomodoroRunning; if (Persistent.states.timer.pomodoro.running) { // Start/Resume Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration; } } function resetPomodoro() { Persistent.states.timer.pomodoro.running = false; Persistent.states.timer.pomodoro.isBreak = false; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.cycle = 0; refreshPomodoro(); } // Stopwatch function refreshStopwatch() { // Stopwatch stores time in 10ms stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart; } Timer { id: stopwatchTimer interval: 10 running: root.stopwatchRunning repeat: true onTriggered: refreshStopwatch() } function toggleStopwatch() { if (root.stopwatchRunning) stopwatchPause(); else stopwatchResume(); } function stopwatchPause() { Persistent.states.timer.stopwatch.running = false; } function stopwatchResume() { if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = []; Persistent.states.timer.stopwatch.running = true; Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; } function stopwatchReset() { stopwatchTime = 0; Persistent.states.timer.stopwatch.laps = []; Persistent.states.timer.stopwatch.running = false; } function stopwatchRecordLap() { Persistent.states.timer.stopwatch.laps.push(stopwatchTime); } } ================================================ FILE: dots/.config/quickshell/ii/services/Todo.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import qs.modules.common import Quickshell; import Quickshell.Io; import QtQuick; /** * Simple to-do list manager. * Each item is an object with "content" and "done" properties. */ Singleton { id: root property var filePath: Directories.todoPath property var list: [] function addItem(item) { list.push(item) // Reassign to trigger onListChanged root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } function addTask(desc) { const item = { "content": desc, "done": false, } addItem(item) } function markDone(index) { if (index >= 0 && index < list.length) { list[index].done = true // Reassign to trigger onListChanged root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } function markUnfinished(index) { if (index >= 0 && index < list.length) { list[index].done = false // Reassign to trigger onListChanged root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } function deleteItem(index) { if (index >= 0 && index < list.length) { list.splice(index, 1) // Reassign to trigger onListChanged root.list = list.slice(0) todoFileView.setText(JSON.stringify(root.list)) } } function refresh() { todoFileView.reload() } Component.onCompleted: { refresh() } FileView { id: todoFileView path: Qt.resolvedUrl(root.filePath) onLoaded: { const fileContents = todoFileView.text() root.list = JSON.parse(fileContents) console.log("[To Do] File loaded") } onLoadFailed: (error) => { if(error == FileViewError.FileNotFound) { console.log("[To Do] File not found, creating new file.") root.list = [] todoFileView.setText(JSON.stringify(root.list)) } else { console.log("[To Do] Error loading file: " + error) } } } } ================================================ FILE: dots/.config/quickshell/ii/services/Translation.qml ================================================ pragma Singleton import QtQuick import Quickshell import Quickshell.Io import qs.modules.common Singleton { id: root property var translations: ({}) property var generatedTranslations: ({}) property var availableLanguages: ["en_US"] property var availableGeneratedLanguages: [] property var allAvailableLanguages: { const combined = new Set([...root.availableLanguages, ...root.availableGeneratedLanguages]); return Array.from(combined).sort(); } property bool isScanning: scanLanguagesProcess.running property bool isLoading: false property string translationKeepSuffix: "/*keep*/" property string translationsDir: Quickshell.shellPath("translations") property string generatedTranslationsDir: Directories.shellConfig + "/translations" property string languageCode: { var configLang = Config?.options.language.ui ?? "auto"; if (configLang !== "auto") return configLang; return Qt.locale().name; } TranslationScanner { id: scanLanguagesProcess translationsDir: root.translationsDir onLanguagesScanned: (languages) => { root.availableLanguages = [...languages]; } } TranslationScanner { id: scanGeneratedLanguagesProcess translationsDir: root.generatedTranslationsDir onLanguagesScanned: (languages) => { root.availableGeneratedLanguages = [...languages]; } } onLanguageCodeChanged: { print("[Translation] Language changed to", root.languageCode); translationFileView.languageCode = root.languageCode; generatedTranslationFileView.languageCode = root.languageCode; translationFileView.reread(); generatedTranslationFileView.reread(); } TranslationReader { id: translationFileView translationsDir: root.translationsDir languageCode: root.languageCode onContentLoaded: (data) => { root.translations = data; root.isLoading = false; } } TranslationReader { id: generatedTranslationFileView translationsDir: root.generatedTranslationsDir languageCode: root.languageCode onContentLoaded: (data) => { root.generatedTranslations = data; root.isLoading = false; } } function tr(text) { // Special cases if (!text) return ""; var key = text.toString(); if (root.isLoading || (!root?.translations?.hasOwnProperty(key) && !root?.generatedTranslations?.hasOwnProperty(key))) return key; // Normal cases var translation = root.translations[key] || root.generatedTranslations[key] || key; // print(key, "-> [", root.translations[key], root.generatedTranslations[key], key, "] ->", translation); if (translation.endsWith(root.translationKeepSuffix)) { translation = translation.substring(0, translation.length - root.translationKeepSuffix.length).trim(); } return translation; } component TranslationScanner: Process { id: translationScanner required property string translationsDir signal languagesScanned(var languages) command: ["find", translationScanner.translationsDir, "-name", "*.json", "-exec", "basename", "{}", ".json", ";"] running: true stdout: StdioCollector { id: languagesCollector onStreamFinished: { const output = languagesCollector.text; const files = output.trim().split('\n').map(f => f.trim()); translationScanner.languagesScanned(files); } } onExited: (exitCode, exitStatus) => { if (exitCode !== 0) { translationScanner.languagesScanned(["en_US"]); } } } component TranslationReader: FileView { id: translationReader required property string translationsDir property string languageCode: root.languageCode signal contentLoaded(var data) function reread() { // Proper reload in case the file was incorrect before translationReader.path = ""; translationReader.path = `${translationReader.translationsDir}/${translationReader.languageCode}.json`; translationReader.reload(); } path: "" onLoaded: { var textContent = ""; try { textContent = text(); var jsonData = JSON.parse(textContent); translationReader.contentLoaded(jsonData); } catch (e) { console.log("[Translation] Failed to load translations:", e); translationReader.contentLoaded({}); } } onLoadFailed: error => { translationReader.contentLoaded({}); } } } ================================================ FILE: dots/.config/quickshell/ii/services/TrayService.qml ================================================ pragma Singleton import qs.modules.common import QtQuick import Quickshell import Quickshell.Services.SystemTray Singleton { id: root property bool smartTray: Config.options.tray.filterPassive property list itemsInUserList: SystemTray.items.values.filter(i => (Config.options.tray.pinnedItems.includes(i.id) && (!smartTray || i.status !== Status.Passive))) property list itemsNotInUserList: SystemTray.items.values.filter(i => (!Config.options.tray.pinnedItems.includes(i.id) && (!smartTray || i.status !== Status.Passive))) property bool invertPins: Config.options.tray.invertPinnedItems property list pinnedItems: invertPins ? itemsNotInUserList : itemsInUserList property list unpinnedItems: invertPins ? itemsInUserList : itemsNotInUserList function getTooltipForItem(item) { var result = item.tooltipTitle.length > 0 ? item.tooltipTitle : (item.title.length > 0 ? item.title : item.id); if (item.tooltipDescription.length > 0) result += " • " + item.tooltipDescription; if (Config.options.tray.showItemId) result += "\n[" + item.id + "]"; return result; } // Pinning function pin(itemId) { var pins = Config.options.tray.pinnedItems; if (pins.includes(itemId)) return; Config.options.tray.pinnedItems.push(itemId); } function unpin(itemId) { Config.options.tray.pinnedItems = Config.options.tray.pinnedItems.filter(id => id !== itemId); } function isPinned(itemId) { for (var i = 0; i < root.pinnedItems.length; i++) { if (root.pinnedItems[i].id === itemId) return true; } return false; } function togglePin(itemId) { var pins = Config.options.tray.pinnedItems; if (pins.includes(itemId)) { unpin(itemId) } else { pin(itemId) } } } ================================================ FILE: dots/.config/quickshell/ii/services/Updates.qml ================================================ pragma Singleton import qs.modules.common import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io /* * System updates service. Currently only supports Arch. */ Singleton { id: root property bool available: false property alias checking: checkUpdatesProc.running property int count: 0 readonly property bool updateAdvised: available && count > Config.options.updates.adviseUpdateThreshold readonly property bool updateStronglyAdvised: available && count > Config.options.updates.stronglyAdviseUpdateThreshold function load() {} function refresh() { if (!available) return; print("[Updates] Checking for system updates") checkUpdatesProc.running = true; } Timer { interval: Config.options.updates.checkInterval * 60 * 1000 repeat: true running: Config.ready && Config.options.updates.enableCheck onTriggered: { print("[Updates] Periodic update check due") root.refresh(); } } Process { id: checkAvailabilityProc running: Config.ready && Config.options.updates.enableCheck command: ["which", "checkupdates"] onExited: (exitCode, exitStatus) => { root.available = (exitCode === 0); root.refresh(); } } Process { id: checkUpdatesProc command: ["bash", "-c", "checkupdates | wc -l"] stdout: StdioCollector { onStreamFinished: { root.count = parseInt(text.trim()); } } } } ================================================ FILE: dots/.config/quickshell/ii/services/Wallpapers.qml ================================================ import qs.modules.common import qs.modules.common.models import qs.modules.common.functions import QtQuick import Qt.labs.folderlistmodel import Quickshell import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound /** * Provides a list of wallpapers and an "apply" action that calls the existing * switchwall.sh script. Pretty much a limited file browsing service. */ Singleton { id: root property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen-venv.sh` property string generateThumbnailsMagickScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh` property alias directory: folderModel.folder readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString()) property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`) property alias folderModel: folderModel // Expose for direct binding when needed property string searchQuery: "" readonly property list extensions: [ // TODO: add videos "jpg", "jpeg", "png", "webp", "avif", "bmp", "svg" ] property list wallpapers: [] // List of absolute file paths (without file://) readonly property bool thumbnailGenerationRunning: thumbgenProc.running property real thumbnailGenerationProgress: 0 signal changed() signal thumbnailGenerated(directory: string) signal thumbnailGeneratedFile(filePath: string) function load () {} // For forcing initialization function openFallbackPicker(darkMode = Appearance.m3colors.darkmode) { Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", darkMode ? "dark" : "light"]); } function apply(path, darkMode = Appearance.m3colors.darkmode) { if (!path || path.length === 0) return; Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, "--mode", darkMode ? "dark" : "light", "--image", path]); root.changed() } Process { id: selectProc property string filePath: "" property bool darkMode: Appearance.m3colors.darkmode function select(filePath, darkMode = Appearance.m3colors.darkmode) { selectProc.filePath = filePath selectProc.darkMode = darkMode selectProc.exec(["test", "-d", FileUtils.trimFileProtocol(filePath)]) } onExited: (exitCode, exitStatus) => { if (exitCode === 0) { setDirectory(selectProc.filePath); return; } root.apply(selectProc.filePath, selectProc.darkMode); } } function select(filePath, darkMode = Appearance.m3colors.darkmode) { selectProc.select(filePath, darkMode); } function randomFromCurrentFolder(darkMode = Appearance.m3colors.darkmode) { if (folderModel.count === 0) return; const randomIndex = Math.floor(Math.random() * folderModel.count); const filePath = folderModel.get(randomIndex, "filePath"); print("Randomly selected wallpaper:", filePath); root.select(filePath, darkMode); } Process { id: validateDirProc property string nicePath: "" function setDirectoryIfValid(path) { validateDirProc.nicePath = FileUtils.trimFileProtocol(path).replace(/\/+$/, "") if (/^\/*$/.test(validateDirProc.nicePath)) validateDirProc.nicePath = "/"; validateDirProc.exec([ "bash", "-c", `if [ -d "${validateDirProc.nicePath}" ]; then echo dir; elif [ -f "${validateDirProc.nicePath}" ]; then echo file; else echo invalid; fi` ]) } stdout: StdioCollector { onStreamFinished: { root.directory = Qt.resolvedUrl(validateDirProc.nicePath) const result = text.trim() if (result === "dir") { } else if (result === "file") { root.directory = Qt.resolvedUrl(FileUtils.parentDirectory(validateDirProc.nicePath)) } else { // Ignore } } } } function setDirectory(path) { validateDirProc.setDirectoryIfValid(path) } function navigateUp() { folderModel.navigateUp() } function navigateBack() { folderModel.navigateBack() } function navigateForward() { folderModel.navigateForward() } // Folder model FolderListModelWithHistory { id: folderModel folder: Qt.resolvedUrl(root.defaultFolder) caseSensitive: false nameFilters: root.extensions.map(ext => `*${searchQuery.split(" ").filter(s => s.length > 0).map(s => `*${s}*`)}*.${ext}`) showDirs: true showDotAndDotDot: false showOnlyReadable: true sortField: FolderListModel.Time sortReversed: false onCountChanged: { root.wallpapers = [] for (let i = 0; i < folderModel.count; i++) { const path = folderModel.get(i, "filePath") || FileUtils.trimFileProtocol(folderModel.get(i, "fileURL")) if (path && path.length) root.wallpapers.push(path) } } } // Thumbnail generation function generateThumbnail(size: string) { if (!["normal", "large", "x-large", "xx-large"].includes(size)) throw new Error("Invalid thumbnail size"); thumbgenProc.directory = root.directory thumbgenProc.running = false thumbgenProc.command = [ "bash", "-c", `${thumbgenScriptPath} --size ${size} --machine_progress -d ${FileUtils.trimFileProtocol(root.directory)} || ${generateThumbnailsMagickScriptPath} --size ${size} -d ${FileUtils.trimFileProtocol(root.directory)}`, ] // console.log("[Wallpapers] Updating thumbnails with command ", thumbgenProc.command.join(" ")) root.thumbnailGenerationProgress = 0 thumbgenProc.running = true } Process { id: thumbgenProc property string directory stdout: SplitParser { onRead: data => { // print("thumb gen proc:", data) let match = data.match(/PROGRESS (\d+)\/(\d+)/) if (match) { const completed = parseInt(match[1]) const total = parseInt(match[2]) root.thumbnailGenerationProgress = completed / total } match = data.match(/FILE (.+)/) if (match) { const filePath = match[1] root.thumbnailGeneratedFile(filePath) } } } onExited: (exitCode, exitStatus) => { // print("[Wallpapers] Thumbnail generation completed with exit code", exitCode) root.thumbnailGenerated(thumbgenProc.directory) } } IpcHandler { target: "wallpapers" function apply(path: string): void { root.apply(path); } } } ================================================ FILE: dots/.config/quickshell/ii/services/Weather.qml ================================================ pragma Singleton pragma ComponentBehavior: Bound import Quickshell import Quickshell.Io import QtQuick import QtPositioning import qs.modules.common Singleton { id: root // 10 minute readonly property int fetchInterval: Config.options.bar.weather.fetchInterval * 60 * 1000 readonly property string city: Config.options.bar.weather.city readonly property bool useUSCS: Config.options.bar.weather.useUSCS property bool gpsActive: Config.options.bar.weather.enableGPS onUseUSCSChanged: { root.getData(); } onCityChanged: { root.getData(); } property var location: ({ valid: false, lat: 0, lon: 0 }) property var data: ({ uv: 0, humidity: 0, sunrise: 0, sunset: 0, windDir: 0, wCode: 0, city: 0, wind: 0, precip: 0, visib: 0, press: 0, temp: 0, tempFeelsLike: 0, lastRefresh: 0, }) function refineData(data) { let temp = {}; temp.uv = data?.current?.uvIndex || 0; temp.humidity = (data?.current?.humidity || 0) + "%"; temp.sunrise = data?.astronomy?.sunrise || "0.0"; temp.sunset = data?.astronomy?.sunset || "0.0"; temp.windDir = data?.current?.winddir16Point || "N"; temp.wCode = data?.current?.weatherCode || "113"; temp.city = data?.location?.areaName[0]?.value || "City"; temp.temp = ""; temp.tempFeelsLike = ""; if (root.useUSCS) { temp.wind = (data?.current?.windspeedMiles || 0) + " mph"; temp.precip = (data?.current?.precipInches || 0) + " in"; temp.visib = (data?.current?.visibilityMiles || 0) + " m"; temp.press = (data?.current?.pressureInches || 0) + " psi"; temp.temp += (data?.current?.temp_F || 0); temp.tempFeelsLike += (data?.current?.FeelsLikeF || 0); temp.temp += "°F"; temp.tempFeelsLike += "°F"; } else { temp.wind = (data?.current?.windspeedKmph || 0) + " km/h"; temp.precip = (data?.current?.precipMM || 0) + " mm"; temp.visib = (data?.current?.visibility || 0) + " km"; temp.press = (data?.current?.pressure || 0) + " hPa"; temp.temp += (data?.current?.temp_C || 0); temp.tempFeelsLike += (data?.current?.FeelsLikeC || 0); temp.temp += "°C"; temp.tempFeelsLike += "°C"; } temp.lastRefresh = DateTime.time + " • " + DateTime.date; root.data = temp; } function getData() { let command = "curl -s wttr.in"; if (root.gpsActive && root.location.valid) { command += `/${root.location.lat},${root.location.long}`; } else { command += `/${formatCityName(root.city)}`; } // format as json command += "?format=j1"; command += " | "; // only take the current weather, location, asytronmy data command += "jq '{current: .current_condition[0], location: .nearest_area[0], astronomy: .weather[0].astronomy[0]}'"; fetcher.command[2] = command; fetcher.running = true; } function formatCityName(cityName) { return cityName.trim().split(/\s+/).join('+'); } Component.onCompleted: { if (!root.gpsActive) return; console.info("[WeatherService] Starting the GPS service."); positionSource.start(); } Process { id: fetcher command: ["bash", "-c", ""] stdout: StdioCollector { onStreamFinished: { if (text.length === 0) return; try { const parsedData = JSON.parse(text); root.refineData(parsedData); // console.info(`[ data: ${JSON.stringify(parsedData)}`); } catch (e) { console.error(`[WeatherService] ${e.message}`); } } } } PositionSource { id: positionSource updateInterval: root.fetchInterval onPositionChanged: { // update the location if the given location is valid // if it fails getting the location, use the last valid location if (position.latitudeValid && position.longitudeValid) { root.location.lat = position.coordinate.latitude; root.location.long = position.coordinate.longitude; root.location.valid = true; // console.info(`📍 Location: ${position.coordinate.latitude}, ${position.coordinate.longitude}`); root.getData(); // if can't get initialized with valid location deactivate the GPS } else { root.gpsActive = root.location.valid ? true : false; console.error("[WeatherService] Failed to get the GPS location."); } } onValidityChanged: { if (!positionSource.valid) { positionSource.stop(); root.location.valid = false; root.gpsActive = false; Quickshell.execDetached(["notify-send", Translation.tr("Weather Service"), Translation.tr("Cannot find a GPS service. Using the fallback method instead."), "-a", "Shell"]); console.error("[WeatherService] Could not aquire a valid backend plugin."); } } } Timer { running: !root.gpsActive repeat: true interval: root.fetchInterval triggeredOnStart: !root.gpsActive onTriggered: root.getData() } } ================================================ FILE: dots/.config/quickshell/ii/services/Ydotool.qml ================================================ pragma Singleton import qs.modules.common import Quickshell Singleton { id: root property int shiftMode: 0 // 0: off, 1: on, 2: lock property list shiftKeys: [42, 54] // Keycodes for Shift keys (left and right) property list altKeys: [56, 100] // Keycodes for Alt keys (left and right) property list ctrlKeys: [29, 97] // Keycodes for Ctrl keys (left and right) function releaseAllKeys() { const keycodes = Array.from(Array(249).keys()); Quickshell.execDetached([ "ydotool", "key", "--key-delay", "0", ...keycodes.map(keycode => `${keycode}:0`) ]) root.shiftMode = 0; // Reset shift mode } function releaseShiftKeys() { Quickshell.execDetached([ "ydotool", "key", "--key-delay", "0", ...root.shiftKeys.map(keycode => `${keycode}:0`) ]) root.shiftMode = 0; // Reset shift mode } function press(keycode) { Quickshell.execDetached([ "ydotool", "key", "--key-delay", "0", `${keycode}:1` ]); } function release(keycode) { Quickshell.execDetached([ "ydotool", "key", "--key-delay", "0", `${keycode}:0` ]); } } ================================================ FILE: dots/.config/quickshell/ii/services/ai/AiMessageData.qml ================================================ import QtQuick; /** * Represents a message in an AI conversation. (Kind of) follows the OpenAI API message structure. */ QtObject { property string role property string content property string rawContent property string fileMimeType property string fileUri property string localFilePath property string model property bool thinking: true property bool done: false property var annotations: [] property var annotationSources: [] property list searchQueries: [] property string functionName property var functionCall property string functionResponse property bool functionPending: false property bool visibleToUser: true } ================================================ FILE: dots/.config/quickshell/ii/services/ai/AiModel.qml ================================================ import QtQuick; /** * An AI model representation. * - name: Friendly name of the model * - icon: Icon name of the model * - description: Description of the model * - endpoint: Endpoint of the model * - model: Model code (like gpt-4.1 or gemini-2.5-flash) * - requires_key: Whether the model requires an API key * - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key. * - key_get_link: Link to get an API key * - key_get_description: Description of pricing and how to get an API key * - api_format: The API format of the model. Can be "openai" or "gemini". Default is "openai". * - extraParams: Extra parameters to be passed to the model. This is a JSON object. */ QtObject { property string name property string icon property string description property string homepage property string endpoint property string model property bool requires_key: true property string key_id property string key_get_link property string key_get_description property string api_format: "openai" property var tools property var extraParams: ({}) } ================================================ FILE: dots/.config/quickshell/ii/services/ai/ApiStrategy.qml ================================================ import QtQuick QtObject { function buildEndpoint(model: AiModel): string { throw new Error("Not implemented") } function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list, filePath: string) { throw new Error("Not implemented") } function buildAuthorizationHeader(apiKeyEnvVarName: string): string { throw new Error("Not implemented") } function parseResponseLine(line: string, message: AiMessageData) { throw new Error("Not implemented") } function onRequestFinished(message: AiMessageData): var { return {} } // Default: no special handling function reset() { } // Reset any internal state if needed function buildScriptFileSetup(filePath) { return "" } // Default: no setup function finalizeScriptContent(scriptContent: string): string { return scriptContent } // Optionally modify/finalize script } ================================================ FILE: dots/.config/quickshell/ii/services/ai/GeminiApiStrategy.qml ================================================ import QtQuick import qs.modules.common.functions as CF ApiStrategy { readonly property string apiKeyEnvVarName: "API_KEY" readonly property string fileUriVarName: "file_uri" readonly property string fileMimeTypeVarName: "MIME_TYPE" readonly property string fileUriSubstitutionString: "{{ fileUriVarName }}" readonly property string fileMimeTypeSubstitutionString: "{{ fileMimeTypeVarName }}" property string buffer: "" function buildEndpoint(model: AiModel): string { const result = model.endpoint + `?key=\$\{${root.apiKeyEnvVarName}\}` // console.log("[AI] Endpoint: " + result); return result; } function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list, filePath: string) { let contents = messages.map(message => { // console.log("[AI] Building request data for message:", JSON.stringify(message, null, 2)); const geminiApiRoleName = (message.role === "assistant") ? "model" : message.role; const usingSearch = tools[0]?.google_search !== undefined if (!usingSearch && message.functionCall != undefined && message.functionName.length > 0) { return { "role": geminiApiRoleName, "parts": [{ functionCall: { "name": message.functionName, } }] } } if (!usingSearch && message.functionResponse != undefined && message.functionName.length > 0) { return { "role": geminiApiRoleName, "parts": [{ functionResponse: { "name": message.functionName, "response": { "content": message.functionResponse } } }] } } return { "role": geminiApiRoleName, "parts": [ { text: message.rawContent }, ...(message.fileUri && message.fileUri.length > 0 ? [{ "file_data": { "mime_type": message.fileMimeType, "file_uri": message.fileUri } }] : []) ] } }) if (filePath && filePath.length > 0) { const trimmedFilePath = CF.FileUtils.trimFileProtocol(filePath); // Add file_data part to the last message's parts array contents[contents.length - 1].parts.unshift({ file_data: { mime_type: fileMimeTypeSubstitutionString, file_uri: fileUriSubstitutionString } }); } let baseData = { "contents": contents, "tools": tools, "system_instruction": { "parts": [{ text: systemPrompt }] }, "generationConfig": { "temperature": temperature, }, }; // print("Gemini API call payload:", JSON.stringify(baseData, null, 2)); return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; } function buildAuthorizationHeader(apiKeyEnvVarName: string): string { // Gemini doesn't use Authorization header, key is in URL return ""; } function parseResponseLine(line, message) { if (line.startsWith("[")) { buffer += line.slice(1).trim(); } else if (line === "]") { buffer += line.slice(0, -1).trim(); return parseBuffer(message); } else if (line.startsWith(",")) { return parseBuffer(message); } else { buffer += line.trim(); } return {}; } function parseBuffer(message) { // console.log("[Ai] Gemini buffer: ", buffer); let finished = false; try { if (buffer.length === 0) return {}; const dataJson = JSON.parse(buffer); // Uploaded file if (dataJson.uploadedFile) { message.fileUri = dataJson.uploadedFile.uri; message.fileMimeType = dataJson.uploadedFile.mimeType; return ({}) } // Error response handling if (dataJson.error) { const errorMsg = `**Error ${dataJson.error.code}**: ${dataJson.error.message}`; message.rawContent += errorMsg; message.content += errorMsg; return { finished: true }; } // No candidates? if (!dataJson.candidates) return {}; // Finished? if (dataJson.candidates[0]?.finishReason) { finished = true; } // Function call handling if (dataJson.candidates[0]?.content?.parts[0]?.functionCall) { const functionCall = dataJson.candidates[0]?.content?.parts[0]?.functionCall; message.functionName = functionCall.name; message.functionCall = functionCall.name; const newContent = `\n\n[[ Function: ${functionCall.name}(${JSON.stringify(functionCall.args, null, 2)}) ]]\n` message.rawContent += newContent; message.content += newContent; return { functionCall: { name: functionCall.name, args: functionCall.args }, finished: finished }; } // Normal text response const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text message.rawContent += responseContent; message.content += responseContent; // Handle annotations and metadata const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => { return { "type": "url_citation", "text": chunk?.web?.title, "url": chunk?.web?.uri, } }) ?? []; const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => { return { "type": "url_citation", "start_index": citation.segment?.startIndex, "end_index": citation.segment?.endIndex, "text": citation?.segment.text, "url": annotationSources[citation.groundingChunkIndices[0]]?.url, "sources": citation.groundingChunkIndices } }); message.annotationSources = annotationSources; message.annotations = annotations; message.searchQueries = dataJson.candidates[0]?.groundingMetadata?.webSearchQueries ?? []; // Usage metadata if (dataJson.usageMetadata) { return { tokenUsage: { input: dataJson.usageMetadata.promptTokenCount ?? -1, output: dataJson.usageMetadata.candidatesTokenCount ?? -1, total: dataJson.usageMetadata.totalTokenCount ?? -1 }, finished: finished }; } } catch (e) { console.log("[AI] Gemini: Could not parse buffer: ", e); message.rawContent += buffer; message.content += buffer; } finally { buffer = ""; } return { finished: finished }; } function onRequestFinished(message) { return parseBuffer(message); } function reset() { buffer = ""; } function buildScriptFileSetup(filePath) { const trimmedFilePath = CF.FileUtils.trimFileProtocol(filePath); let content = "" // print("file path:", filePath) // print("trimmed file path:", trimmedFilePath) // print("escaped file path:", CF.StringUtils.shellSingleQuoteEscape(trimmedFilePath)) content += `IMAGE_PATH='${CF.StringUtils.shellSingleQuoteEscape(trimmedFilePath)}'\n`; content += `${fileMimeTypeVarName}=$(file -b --mime-type "$IMAGE_PATH")\n`; content += 'NUM_BYTES=$(wc -c < "${IMAGE_PATH}")\n'; content += 'tmp_header_file="/tmp/quickshell/ai/upload-header.tmp"\n'; content += 'tmp_file_info_file="/tmp/quickshell/ai/file-info.json.tmp"\n'; // Initial resumable request defining metadata. // The upload url is in the response headers dump them to a file. content += 'curl "https://generativelanguage.googleapis.com/upload/v1beta/files"' + ` -H "x-goog-api-key: \$${apiKeyEnvVarName}"` + ' -D $tmp_header_file' + ' -H "X-Goog-Upload-Protocol: resumable"' + ' -H "X-Goog-Upload-Command: start"' + ' -H "X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}"' + ` -H "X-Goog-Upload-Header-Content-Type: \${${fileMimeTypeVarName}}"` + ' -H "Content-Type: application/json"' + ` -d "{'file': {'display_name': 'Image'}}" 2> /dev/null` + '\n'; // Get file upload header content += 'upload_url=$(grep -i "x-goog-upload-url: " "${tmp_header_file}" | cut -d" " -f2 | tr -d "\r")\n'; content += 'rm "${tmp_header_file}"\n'; // Upload the actual file content += 'curl "${upload_url}"' + ` -H "x-goog-api-key: \$${apiKeyEnvVarName}"` + ' -H "Content-Length: ${NUM_BYTES}"' + ' -H "X-Goog-Upload-Offset: 0"' + ' -H "X-Goog-Upload-Command: upload, finalize"' + ' --data-binary "@${IMAGE_PATH}" 2> /dev/null > "${tmp_file_info_file}"' + '\n'; content += `${fileUriVarName}=$(jq -r ".file.uri" "$tmp_file_info_file")\n` content += `printf "{\\"uploadedFile\\": {\\"uri\\": \\"$${fileUriVarName}\\", \\"mimeType\\": \\"$${fileMimeTypeVarName}\\"}}\\n,\\n"\n` return content } function finalizeScriptContent(scriptContent: string): string { return scriptContent.replace(fileMimeTypeSubstitutionString, `'"\$${fileMimeTypeVarName}"'`) .replace(fileUriSubstitutionString, `'"\$${fileUriVarName}"'`); } } ================================================ FILE: dots/.config/quickshell/ii/services/ai/MistralApiStrategy.qml ================================================ import QtQuick ApiStrategy { property bool isReasoning: false function buildEndpoint(model: AiModel): string { // console.log("[AI] Endpoint: " + model.endpoint); return model.endpoint; } function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list, filePath: string) { let baseData = { "model": model.model, "messages": [ {role: "system", content: systemPrompt}, ...messages.map(message => { const hasFunctionCall = message.functionCall != undefined && message.functionName.length > 0 let messageData = { "role": message.role, "content": message.rawContent, } if (hasFunctionCall) { if (message.functionResponse?.length > 0) { messageData.name = message.functionName; // Does the func call also need this name? or just the func output? messageData.role = "tool"; messageData.content = message.functionResponse; messageData.tool_call_id = message.functionCall.id } } return messageData }), ], "stream": true, "temperature": temperature, "tools": tools, }; // console.log("[AI] Request data: ", JSON.stringify(baseData, null, 2)); return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; } function buildAuthorizationHeader(apiKeyEnvVarName: string): string { return `-H "Authorization: Bearer \$\{${apiKeyEnvVarName}\}"`; } function parseResponseLine(line, message) { // Remove 'data: ' prefix if present and trim whitespace let cleanData = line.trim(); if (cleanData.startsWith("data:")) { cleanData = cleanData.slice(5).trim(); } // Handle special cases if (!cleanData || cleanData.startsWith(":")) return {}; if (cleanData === "[DONE]") { return { finished: true }; } // Real stuff try { const dataJson = JSON.parse(cleanData); // Error response handling if (dataJson.error) { const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`; message.rawContent += errorMsg; message.content += errorMsg; return { finished: true }; } let newContent = ""; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; // Function call if (dataJson.choices[0]?.delta?.tool_calls) { const functionCall = dataJson.choices[0].delta.tool_calls[0]; const functionName = functionCall.function.name; const functionArgs = JSON.parse(functionCall.function.arguments) || {}; // Args are given as string??? const functionId = functionCall.id; const newContent = `\n\n[[ Function: ${functionName}(${JSON.stringify(functionArgs, null, 2)}) ]]\n`; message.rawContent += newContent; message.content += newContent; message.functionName = functionName; message.functionCall = functionName; return { functionCall: { name: functionName, args: functionArgs, id: functionId } }; } // Thinking? if (responseContent && responseContent.length > 0) { if (isReasoning) { isReasoning = false; const endBlock = "\n\n\n\n"; message.content += endBlock; message.rawContent += endBlock; } newContent = responseContent; } else if (responseReasoning && responseReasoning.length > 0) { if (!isReasoning) { isReasoning = true; const startBlock = "\n\n\n\n"; message.rawContent += startBlock; message.content += startBlock; } newContent = responseReasoning; } // Text message.content += newContent; message.rawContent += newContent; // Usage metadata if (dataJson.usage) { return { tokenUsage: { input: dataJson.usage.prompt_tokens ?? -1, output: dataJson.usage.completion_tokens ?? -1, total: dataJson.usage.total_tokens ?? -1 } }; } if (`dataJson`.done) { return { finished: true }; } } catch (e) { console.log("[AI] Mistral: Could not parse line: ", e); message.rawContent += line; message.content += line; } return {}; } function onRequestFinished(message) { return {}; } function reset() { isReasoning = false; } } ================================================ FILE: dots/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml ================================================ import QtQuick ApiStrategy { property bool isReasoning: false function buildEndpoint(model: AiModel): string { // console.log("[AI] Endpoint: " + model.endpoint); return model.endpoint; } function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list, filePath: string) { let baseData = { "model": model.model, "messages": [ {role: "system", content: systemPrompt}, ...messages.map(message => { return { "role": message.role, "content": message.rawContent, } }), ], "stream": true, "tools": tools, "temperature": temperature, }; return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData; } function buildAuthorizationHeader(apiKeyEnvVarName: string): string { return `-H "Authorization: Bearer \$\{${apiKeyEnvVarName}\}"`; } function parseResponseLine(line, message) { // Remove 'data: ' prefix if present and trim whitespace let cleanData = line.trim(); if (cleanData.startsWith("data:")) { cleanData = cleanData.slice(5).trim(); } // console.log("[AI] OpenAI: Data:", cleanData); // Handle special cases if (!cleanData || cleanData.startsWith(":")) return {}; if (cleanData === "[DONE]") { return { finished: true }; } // Real stuff try { const dataJson = JSON.parse(cleanData); // Error response handling if (dataJson.error) { const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`; message.rawContent += errorMsg; message.content += errorMsg; return { finished: true }; } let newContent = ""; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content; if (responseContent && responseContent.length > 0) { if (isReasoning) { isReasoning = false; const endBlock = "\n\n\n\n"; message.content += endBlock; message.rawContent += endBlock; } newContent = responseContent; } else if (responseReasoning && responseReasoning.length > 0) { if (!isReasoning) { isReasoning = true; const startBlock = "\n\n\n\n"; message.rawContent += startBlock; message.content += startBlock; } newContent = responseReasoning; } message.content += newContent; message.rawContent += newContent; // Usage metadata if (dataJson.usage) { return { tokenUsage: { input: dataJson.usage.prompt_tokens ?? -1, output: dataJson.usage.completion_tokens ?? -1, total: dataJson.usage.total_tokens ?? -1 } }; } if (dataJson.done) { return { finished: true }; } } catch (e) { console.log("[AI] OpenAI: Could not parse line: ", e); message.rawContent += line; message.content += line; } return {}; } function onRequestFinished(message) { // OpenAI format doesn't need special finish handling return {}; } function reset() { isReasoning = false; } } ================================================ FILE: dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh ================================================ #!/usr/bin/env bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate "$SCRIPT_DIR/token_from_key.py" "$@" deactivate ================================================ FILE: dots/.config/quickshell/ii/services/gCloud/token_from_key.py ================================================ #!/usr/bin/env python3 import calendar import sys import json import google.auth.transport.requests import google.oauth2.service_account def get_token(json_str): try: # Load the string into a dictionary info = json.loads(json_str) # Initialize credentials creds = google.oauth2.service_account.Credentials.from_service_account_info(info) scoped_creds = creds.with_scopes(['https://www.googleapis.com/auth/cloud-platform']) # Refresh to get the access token request = google.auth.transport.requests.Request() scoped_creds.refresh(request) token = scoped_creds.token expiry = int(calendar.timegm(scoped_creds.expiry.utctimetuple())) print(json.dumps({ "token": token, "expiry": expiry })) except Exception as e: sys.stderr.write(f"Error: {str(e)}\n") sys.exit(1) if __name__ == "__main__": if len(sys.argv) < 2: sys.stderr.write("Usage: python3 get_token.py ''\n") sys.exit(1) get_token(sys.argv[1]) ================================================ FILE: dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl ================================================ #version 300 es precision highp float; in vec2 v_texcoord; uniform sampler2D tex; out vec4 fragColor; float overlayOpacityForBrightness(float x) { // Note: range 0 to 1 // Will a fancy curve help?... I'll have to experiment more at night // float y = pow(x, 2.0) * 0.75; // float y = (1.0 - exp(-x))*1.15; // float y = (1.0 - exp(-pow((x-0.15), 0.6)))*1.18; float y = x*0.75; return min(max(y, 0.001), 1.0); } void main() { // 1. Get the current pixel color vec4 pixColor = texture(tex, v_texcoord); // 2. Calculate average screen brightness vec3 totalRGB = vec3(0.0); float samples = 0.0; // We use a nested loop to create a 10x10 grid (100 samples) // This is dense enough to catch small icons/text but light enough to run fast. for(float x = 0.05; x < 1.0; x += 0.1) { for(float y = 0.05; y < 1.0; y += 0.1) { totalRGB += texture(tex, vec2(x, y)).rgb; samples++; } } vec3 avgColor = totalRGB / samples; float globalBrightness = dot(avgColor, vec3(0.2126, 0.7152, 0.0722)); // 3. Get the specific opacity for this brightness level float opacity = overlayOpacityForBrightness(globalBrightness); // 4. Apply the "black overlay" effect vec3 outColor = mix(pixColor.rgb, vec3(0.0), opacity); fragColor = vec4(outColor, pixColor.a); } ================================================ FILE: dots/.config/quickshell/ii/services/network/WifiAccessPoint.qml ================================================ import QtQuick QtObject { required property var lastIpcObject readonly property string ssid: lastIpcObject.ssid readonly property string bssid: lastIpcObject.bssid readonly property int strength: lastIpcObject.strength readonly property int frequency: lastIpcObject.frequency readonly property bool active: lastIpcObject.active readonly property string security: lastIpcObject.security readonly property bool isSecure: security.length > 0 property bool askingPassword: false } ================================================ FILE: dots/.config/quickshell/ii/settings.qml ================================================ //@ pragma UseQApplication //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic //@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000 // Adjust this to make the app smaller or larger //@ pragma Env QT_SCALE_FACTOR=1 import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import Quickshell import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions as CF ApplicationWindow { id: root property string firstRunFilePath: CF.FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`) property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" property real contentPadding: 8 property bool showNextTime: false property var pages: [ { name: Translation.tr("Quick"), icon: "instant_mix", component: "modules/settings/QuickConfig.qml" }, { name: Translation.tr("General"), icon: "browse", component: "modules/settings/GeneralConfig.qml" }, { name: Translation.tr("Bar"), icon: "toast", iconRotation: 180, component: "modules/settings/BarConfig.qml" }, { name: Translation.tr("Background"), icon: "texture", component: "modules/settings/BackgroundConfig.qml" }, { name: Translation.tr("Interface"), icon: "bottom_app_bar", component: "modules/settings/InterfaceConfig.qml" }, { name: Translation.tr("Services"), icon: "settings", component: "modules/settings/ServicesConfig.qml" }, { name: Translation.tr("Advanced"), icon: "construction", component: "modules/settings/AdvancedConfig.qml" }, { name: Translation.tr("About"), icon: "info", component: "modules/settings/About.qml" } ] property int currentPage: 0 visible: true onClosing: Qt.quit() title: "illogical-impulse Settings" Component.onCompleted: { MaterialThemeLoader.reapplyTheme() Config.readWriteDelay = 0 // Settings app always only sets one var at a time so delay isn't needed } minimumWidth: 750 minimumHeight: 500 width: 1100 height: 750 color: Appearance.m3colors.m3background ColumnLayout { anchors { fill: parent margins: contentPadding } Keys.onPressed: (event) => { if (event.modifiers === Qt.ControlModifier) { if (event.key === Qt.Key_PageDown) { root.currentPage = Math.min(root.currentPage + 1, root.pages.length - 1) event.accepted = true; } else if (event.key === Qt.Key_PageUp) { root.currentPage = Math.max(root.currentPage - 1, 0) event.accepted = true; } else if (event.key === Qt.Key_Tab) { root.currentPage = (root.currentPage + 1) % root.pages.length; event.accepted = true; } else if (event.key === Qt.Key_Backtab) { root.currentPage = (root.currentPage - 1 + root.pages.length) % root.pages.length; event.accepted = true; } } } Item { // Titlebar visible: Config.options?.windows.showTitlebar Layout.fillWidth: true Layout.fillHeight: false implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight) StyledText { id: titleText anchors { left: Config.options.windows.centerTitle ? undefined : parent.left horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined verticalCenter: parent.verticalCenter leftMargin: 12 } color: Appearance.colors.colOnLayer0 text: Translation.tr("Settings") font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.title variableAxes: Appearance.font.variableAxes.title } } RowLayout { // Window controls row id: windowControlsRow anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right RippleButton { buttonRadius: Appearance.rounding.full implicitWidth: 35 implicitHeight: 35 onClicked: root.close() contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: "close" iconSize: 20 } } } } RowLayout { // Window content with navigation rail and content pane Layout.fillWidth: true Layout.fillHeight: true spacing: contentPadding Item { id: navRailWrapper Layout.fillHeight: true Layout.margins: 5 implicitWidth: navRail.expanded ? 150 : fab.baseSize Behavior on implicitWidth { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } NavigationRail { // Window content with navigation rail and content pane id: navRail anchors { left: parent.left top: parent.top bottom: parent.bottom } spacing: 10 expanded: root.width > 900 NavigationRailExpandButton { focus: root.visible } FloatingActionButton { id: fab property bool justCopied: false iconText: justCopied ? "check" : "edit" buttonText: justCopied ? Translation.tr("Path copied") : Translation.tr("Config file") expanded: navRail.expanded downAction: () => { Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`); } altAction: () => { Quickshell.clipboardText = CF.FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`); fab.justCopied = true; revertTextTimer.restart() } Timer { id: revertTextTimer interval: 1500 onTriggered: { fab.justCopied = false; } } StyledToolTip { text: Translation.tr("Open the shell config file\nAlternatively right-click to copy path") } } NavigationRailTabArray { currentIndex: root.currentPage expanded: navRail.expanded Repeater { model: root.pages NavigationRailButton { required property var index required property var modelData toggled: root.currentPage === index onPressed: root.currentPage = index; expanded: navRail.expanded buttonIcon: modelData.icon buttonIconRotation: modelData.iconRotation || 0 buttonText: modelData.name showToggledHighlight: false } } } Item { Layout.fillHeight: true } } } Rectangle { // Content container Layout.fillWidth: true Layout.fillHeight: true color: Appearance.m3colors.m3surfaceContainerLow radius: Appearance.rounding.windowRounding - root.contentPadding Loader { id: pageLoader anchors.fill: parent opacity: 1.0 active: Config.ready Component.onCompleted: { source = root.pages[0].component } Connections { target: root function onCurrentPageChanged() { switchAnim.complete(); switchAnim.start(); } } SequentialAnimation { id: switchAnim NumberAnimation { target: pageLoader properties: "opacity" from: 1 to: 0 duration: 100 easing.type: Appearance.animation.elementMoveExit.type easing.bezierCurve: Appearance.animationCurves.emphasizedFirstHalf } ParallelAnimation { PropertyAction { target: pageLoader property: "source" value: root.pages[root.currentPage].component } PropertyAction { target: pageLoader property: "anchors.topMargin" value: 20 } } ParallelAnimation { NumberAnimation { target: pageLoader properties: "opacity" from: 0 to: 1 duration: 200 easing.type: Appearance.animation.elementMoveEnter.type easing.bezierCurve: Appearance.animationCurves.emphasizedLastHalf } NumberAnimation { target: pageLoader properties: "anchors.topMargin" to: 0 duration: 200 easing.type: Appearance.animation.elementMoveEnter.type easing.bezierCurve: Appearance.animationCurves.emphasizedLastHalf } } } } } } } } ================================================ FILE: dots/.config/quickshell/ii/shell.qml ================================================ //@ pragma UseQApplication //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic //@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000 // Remove two slashes below and adjust the value to change the UI scale ////@ pragma Env QT_SCALE_FACTOR=1 import "modules/common" import "services" import "panelFamilies" import QtQuick import QtQuick.Window import Quickshell import Quickshell.Io import Quickshell.Hyprland ShellRoot { id: root // Stuff for every panel family ReloadPopup {} Component.onCompleted: { MaterialThemeLoader.reapplyTheme() Hyprsunset.load() FirstRunExperience.load() ConflictKiller.load() Cliphist.refresh() Wallpapers.load() Updates.load() } // Panel families property list families: ["ii", "waffle"] function cyclePanelFamily() { const currentIndex = families.indexOf(Config.options.panelFamily) const nextIndex = (currentIndex + 1) % families.length Config.options.panelFamily = families[nextIndex] } component PanelFamilyLoader: LazyLoader { required property string identifier property bool extraCondition: true active: Config.ready && Config.options.panelFamily === identifier && extraCondition } PanelFamilyLoader { identifier: "ii" component: IllogicalImpulseFamily {} } PanelFamilyLoader { identifier: "waffle" component: WaffleFamily {} } // Shortcuts IpcHandler { target: "panelFamily" function cycle(): void { root.cyclePanelFamily() } } GlobalShortcut { name: "panelFamilyCycle" description: "Cycles panel family" onPressed: root.cyclePanelFamily() } } ================================================ FILE: dots/.config/quickshell/ii/translations/de_DE.json ================================================ { "Material cookie": "Material Cookie", "Style: Blurred": "Stil: Verschwommen", "Unknown device": "Unbekanntes Gerät", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Jederzeit später ändern mit /dark, /light, /wallpaper im Launcher\nFalls sich die Shell-Farben nicht ändern:\n 1. Öffne die rechte Seitenleiste mit Super+N\n 2. Klicke auf \"Hyprland & Quickshell neu laden\" in der oberen rechten Ecke", "No pending tasks": "Keine ausstehenden Aufgaben", "Positioning": "Positionierung", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Temperatur (Zufälligkeit) des Modells einstellen. Wertebereich zwischen 0 und 2 für Gemini, 0 bis 1 für andere Modelle. Standard ist 0,5.", "Critical warning": "Kritische Warnung", "Unknown Artist": "Unbekannter Künstler", "Web search": "Websuche", "Load prompt from %1": "Prompt von %1 laden", "Attach a file. Only works with Gemini.": "Datei anhängen. Funktioniert nur mit Gemini.", "Reboot": "Neustart", "API key:\n\n```txt\n%1\n```": "API-Schlüssel:\n\n```txt\n%1\n```", "Pinned on startup": "Beim Start angeheftet", "Right": "Rechts", "Reboot to firmware settings": "Neustart zu Firmware-Einstellungen", "Automatically hide": "Automatisch ausblenden", "Waiting for response...": "Warte auf Antwort...", "To Do": "Aufgaben", "Full": "Voll", "Select Language": "Sprache auswählen", "Password": "Passwort", "Bluetooth devices": "Bluetooth-Geräte", "Enable": "Aktivieren", "Elements": "Elemente", "Start": "Start", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Zufälliges SFW-Anime-Hintergrundbild von Konachan\nBild wird in ~/Pictures/Wallpapers gespeichert", "The popular one | Best quantity, but quality can vary wildly": "Der beliebte | Beste Menge, aber Qualität kann stark variieren", "System uptime:": "Systemlaufzeit:", "illogical-impulse Welcome": "illogical-impulse Willkommen", "Code saved to file": "Code in Datei gespeichert", "Info": "Info", "Preferred wallpaper zoom (%)": "Bevorzugter Hintergrundbild-Zoom (%)", "Time": "Zeit", "Help & Support": "Hilfe & Support", "Bubble": "Blase", "Large images | God tier quality, no NSFW.": "Große Bilder | Höchste Qualität, kein NSFW.", "Dark": "Dunkel", "Center clock": "Zentrale Uhr", "Search, calculate or run": "Suchen, berechnen oder ausführen", "Region height": "Regionshöhe", "Load chat": "Chat laden", "Gives the model search capabilities (immediately)": "Gibt dem Modell Suchfähigkeiten (sofort)", "Depends on workspace": "Hängt vom Arbeitsbereich ab", "Blurred style": "Verschwommener Stil", "Screenshot tool": "Screenshot-Tool", "Enter password": "Passwort eingeben", "Search the web": "Im Web suchen", "Local only": "Nur lokal", "at": "um", "Math": "Mathematik", "Consider plugging in your device": "Erwäge, dein Gerät anzuschließen", "Workspaces shown": "Angezeigte Arbeitsbereiche", "Place the corners to trigger at the bottom": "Platziere die Ecken zum Auslösen am unteren Rand", "No API key\nSet it with /key YOUR_API_KEY": "Kein API-Schlüssel\nSetze ihn mit /key DEIN_API_SCHLUESSEL", "Auto (System)": "Auto (System)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Pfeiltasten zum Navigieren, Enter zum Auswählen\nEsc oder irgendwo klicken zum Abbrechen", "Critically low battery": "Kritisch niedrige Batterie", "Open editor": "Editor öffnen", "%1 notifications": "%1 Benachrichtigungen", "Region width": "Regionsbreite", "Max allowed increase": "Maximal erlaubter Anstieg", "Enable translator": "Übersetzer aktivieren", "Constantly rotate": "Ständig rotieren", "Automatically suspends the system when battery is low": "Suspendiert das System automatisch bei niedrigem Batteriestand", "Cannot find a GPS service. Using the fallback method instead.": "GPS-Dienst nicht gefunden. Verwende stattdessen die Fallback-Methode.", "Qt apps": "Qt-Apps", "Color picker": "Farbwähler", "Interface": "Schnittstelle", "Tint app icons": "App-Symbole einfärben", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Wähle die Sprache für die Benutzeroberfläche.\n\"Auto\" verwendet deine System-Lokalisierung.", "Show quote": "Zitat anzeigen", "Local Ollama model | %1": "Lokales Ollama-Modell | %1", "Show clock": "Uhr anzeigen", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Verwendung: %1superpaste ANZAHL[i]\nGib i an, wenn du Bilder möchtest\nBeispiele:\n%1superpaste 4i für die letzten 4 Bilder\n%1superpaste 7 für die letzten 7 Einträge", "Audio": "Audio", "Corner style": "Eckenstil", "No media": "Keine Medien", "Unknown function call: %1": "Unbekannter Funktionsaufruf: %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Online | %1s Modell | Liefert schnelle, reaktionsschnelle und gut formatierte Antworten. Nachteile: nicht sehr eifrig, Dinge zu tun; könnte unbekannte Funktionsaufrufe erfinden", "Volume": "Lautstärke", "Medium": "Mittel", "Copy code": "Code kopieren", "Exceeded max allowed": "Maximaler Wert überschritten", "Keep right sidebar loaded": "Rechte Seitenleiste geladen halten", "Left": "Links", "High": "Hoch", "Rect": "Rechteck", "Lap": "Runde", "Clear": "Löschen", "Screen snip": "Bildschirmausschnitt", "Reset": "Zurücksetzen", "Back": "Zurück", "Dark/Light toggle": "Dunkel/Hell umschalten", "12h am/pm": "12h am/pm", "Download complete": "Download abgeschlossen", "Enable blur": "Unschärfe aktivieren", "Second hand": "Sekundenzeiger", "Bar & screen": "Leiste & Bildschirm", "Discharging:": "Entladung:", "Up %1": "Hoch %1", "Low": "Niedrig", "Hour hand": "Stundenzeiger", "Clear chat history": "Chat-Verlauf löschen", "Fruit Salad": "Fruchtsalat", "%1 Safe Storage": "%1 Sicherer Speicher", "Hibernate": "Ruhezustand", "Delete": "Löschen", "OK": "OK", "Settings": "Einstellungen", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Dies ist normalerweise sicher und wird ohnehin für deinen Browser und die AI-Seitenleiste benötigt\nHauptsächlich nützlich für diejenigen, die eine Sperre beim Start verwenden, anstatt eines Display-Managers, der das übernimmt (GDM, SDDM, etc.)", "Use Hyprlock (instead of Quickshell)": "Hyprlock verwenden (anstatt Quickshell)", "Crosshair code (in Valorant's format)": "Fadenkreuz-Code (im Valorant-Format)", "Silent": "Stumm", "Useless buttons": "Nutzlose Buttons", "Hover to reveal": "Bewegen zum Anzeigen", "Wallpaper & Colors": "Hintergrundbild & Farben", "Auto": "Auto", "Visibility": "Sichtbarkeit", "Shell & utilities": "Shell & Utilities", "Hollow": "Hohl", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "System-Dateiauswahl verwenden\nRechtsklick, um dies zum Standardverhalten zu machen", "On-screen display": "Bildschirmanzeige", "Dotfiles": "Dotfiles", "Search wallpapers": "Hintergrundbilder suchen", "Mic toggle": "Mikrofon umschalten", "Input": "Eingabe", "Also unlock keyring": "Auch Schlüsselbund entsperren", "Configuration": "Konfiguration", "Keep system awake": "System wach halten", "Unknown command:": "Unbekannter Befehl:", "Anime boorus": "Anime-Boorus", "To Do:": "Aufgaben:", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Verwendet Gemini, um das Hintergrundbild zu kategorisieren und wählt dann ein Preset basierend darauf aus.\nDu musst zuerst den Gemini-API-Schlüssel in der linken Seitenleiste festlegen.\nBilder werden für die Leistung herunterskaliert, aber nur zur Sicherheit,\nwähle keine Hintergrundbilder mit sensiblen Informationen aus.", "Bottom": "Unten", "Clear the current list of images": "Aktuelle Bilderliste löschen", "Sunrise": "Sonnenaufgang", "Show app icons": "App-Symbole anzeigen", "Format": "Format", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Stelle sicher, dass dein Player MPRIS-Unterstützung hat\noder versuche, die doppelte Player-Filterung zu deaktivieren", "Pause": "Pause", "Desktop": "Desktop", "Conflicts with the shell's system tray implementation": "Kollidiert mit der System-Tray-Implementierung der Shell", "Your package manager is running": "Dein Paket-Manager läuft", "Conflicts with the shell's notification implementation": "Kollidiert mit der Benachrichtigungsimplementierung der Shell", "Unknown Album": "Unbekanntes Album", "Pick wallpaper image on your system": "Hintergrundbild auf deinem System auswählen", "Used:": "Verwendet:", "Cheat sheet": "Kurzreferenz", "Clock style": "Uhr-Stil", "No audio source": "Keine Audioquelle", "Paired": "Gepaart", "Documentation": "Dokumentation", "No": "Nein", "Pills": "Pillen", "Thought": "Gedanke", "When this is off you'll have to click": "Wenn dies aus ist, musst du klicken", "Select output device": "Ausgabegerät auswählen", "Logout": "Abmelden", "Tip: Close a window with Super+Q": "Tipp: Fenster mit Super+Q schließen", "Finished tasks will go here": "Fertige Aufgaben werden hier angezeigt", "Terminal: Harmony (%)": "Terminal: Harmonie (%)", "Corner open": "Ecke öffnen", "Shell conflicts killer": "Shell-Konflikt-Killer", "Clean stuff | Excellent quality, no NSFW": "Saubere Sachen | Ausgezeichnete Qualität, kein NSFW", "Scroll to change volume": "Scrollen zum Ändern der Lautstärke", "Wind": "Wind", "API key is set\nChange with /key YOUR_API_KEY": "API-Schlüssel ist gesetzt\nÄndern mit /key DEIN_API_SCHLUESSEL", "Neutral": "Neutral", "12h AM/PM": "12h AM/PM", "Number show delay when pressing Super (ms)": "Anzeigeverzögerung für Zahlen beim Drücken von Super (ms)", "Fill": "Füllen", "Always show numbers": "Zahlen immer anzeigen", "Dot": "Punkt", "Provider set to": "Anbieter gesetzt auf", "Unknown Title": "Unbekannter Titel", "Anime": "Anime", "Refreshing (manually triggered)": "Aktualisiere (manuell ausgelöst)", "Dock": "Dock", "Require password to power off/restart": "Passwort für Ausschalten/Neustart erforderlich", "Line": "Linie", "Weather": "Wetter", "All-rounder | Good quality, decent quantity": "Allrounder | Gute Qualität, anständige Menge", "Scale (%)": "Skalierung (%)", "Copy": "Kopieren", "Usage": "Verwendung", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Tippe /key, um mit Online-Modellen zu beginnen\nStrg+O, um die Seitenleiste zu erweitern\nStrg+P, um die Seitenleiste in ein Fenster zu lösen", "Set the tool to use for the model.": "Das zu verwendende Tool für das Modell festlegen.", "Disable tools": "Tools deaktivieren", "Connect": "Verbinden", "Allow NSFW": "NSFW erlauben", "Registration failed. Please inspect manually with the warp-cli command": "Registrierung fehlgeschlagen. Bitte manuell mit dem Befehl warp-cli überprüfen", "Time to full:": "Zeit bis voll:", "Session": "Sitzung", "Services": "Dienste", "Nothing here!": "Nichts hier!", "Overview": "Übersicht", "Random: osu! seasonal": "Zufällig: osu! saisonal", "If you want to somehow use fingerprint unlock...": "Wenn du irgendwie die Fingerabdruck-Entsperrung verwenden möchtest...", "Minute hand": "Minutenzeiger", "Notifications": "Benachrichtigungen", "Enable if you want clocks to show seconds accurately": "Aktivieren, wenn Uhren Sekunden genau anzeigen sollen", "Timer": "Timer", "Quote settings": "Zitat-Einstellungen", "System prompt": "System-Prompt", "Classic": "Klassisch", "Close": "Schließen", "Disconnect": "Trennen", "Go to source (%1)": "Zur Quelle gehen (%1)", "EasyEffects | Right-click to configure": "EasyEffects | Rechtsklick zum Konfigurieren", "Forget": "Vergessen", "Output": "Ausgabe", "Date style": "Datums-Stil", "System": "System", "Usage: %1tool TOOL_NAME": "Verwendung: %1tool TOOL_NAME", "Workspaces": "Arbeitsbereiche", "Calendar": "Kalender", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Anleitung**: Bei Mistral-Konto anmelden, zu Schlüsseln in der Seitenleiste gehen, auf Neuen Schlüssel erstellen klicken", "Volume limit": "Lautstärkegrenze", "Sunset": "Sonnenuntergang", "Dial style": "Zifferblatt-Stil", "Hi there! First things first...": "Hallo! Erst mal das Wichtigste...", "Save chat to %1": "Chat nach %1 speichern", "Security": "Sicherheit", "Total token count\nInput: %1\nOutput: %2": "Gesamte Token-Anzahl\nEingabe: %1\nAusgabe: %2", "Cancel wallpaper selection": "Hintergrundbild-Auswahl abbrechen", "Please charge!\nAutomatic suspend triggers at %1": "Bitte aufladen!\nAutomatische Suspendierung bei %1", "Terminal: Harmonize threshold": "Terminal: Harmonisiere Schwellenwert", "Be patient...": "Geduld...", "Utility buttons": "Utility-Buttons", "Tonal Spot": "Tonal Spot", "Prevents abrupt increments and restricts volume limit": "Verhindert abrupte Erhöhungen und begrenzt die Lautstärkegrenze", "Set the current API provider": "Aktuellen API-Anbieter festlegen", "Connection failed. Please inspect manually with the warp-cli command": "Verbindung fehlgeschlagen. Bitte manuell mit dem Befehl warp-cli überprüfen", "Networking": "Netzwerk", "Tint icons": "Symbole einfärben", "Low battery": "Niedrige Batterie", "Make icons pinned by default": "Symbole standardmäßig anheften", "Get the next page of results": "Nächste Seite der Ergebnisse abrufen", "Invalid API provider. Supported: \n-": "Ungültiger API-Anbieter. Unterstützt: \n-", "Show \"Locked\" text": "\"Gesperrt\"-Text anzeigen", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Preis**: kostenlos. Datennutzungsrichtlinie variiert je nach deinen OpenRouter-Kontoeinstellungen.\n\n**Anleitung**: Bei OpenRouter-Konto anmelden, zu Schlüsseln im oberen rechten Menü gehen, auf API-Schlüssel erstellen klicken", "Not visible to model": "Nicht sichtbar für Modell", "Lock screen": "Bildschirm sperren", "Save to Downloads": "In Downloads speichern", "Expressive": "Ausdrucksvoll", "Suspend at": "Suspendieren bei", "Jump to current month": "Zum aktuellen Monat springen", "Bold": "Fett", "Waifus only | Excellent quality, limited quantity": "Nur Waifus | Ausgezeichnete Qualität, begrenzte Menge", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Klicken zum Umschalten zwischen Hell/Dunkel-Modus\n(wird angewendet, wenn Hintergrundbild gewählt wird)", "Visualize region": "Region visualisieren", "Quote": "Zitat", "Sleep": "Schlaf", "Hit \"/\" to search": "Drücke \"/\" zum Suchen", "Hug": "Umarmung", "Report a Bug": "Fehler melden", "Precipitation": "Niederschlag", "Crosshair": "Fadenkreuz", "Model set to %1": "Modell gesetzt auf %1", "Rows": "Zeilen", "Top": "Oben", "Long break": "Lange Pause", "Superpaste": "Superpaste", "Screen round corner": "Bildschirm abgerundete Ecke", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Online | Googles Modell\nNeueres Modell, das langsamer ist als sein Vorgänger, aber höhere Qualität liefern sollte", "Rainbow": "Regenbogen", "Weeb": "Weeb", "Large language models": "Große Sprachmodelle", "Online models disallowed\n\nControlled by `policies.ai` config option": "Online-Modelle nicht erlaubt\n\nKontrolliert durch `policies.ai` Konfigurationsoption", "Policies": "Richtlinien", "Temperature must be between 0 and 2": "Temperatur muss zwischen 0 und 2 liegen", "Automatic suspend": "Automatische Suspendierung", "Extra wallpaper zoom (%)": "Zusätzlicher Hintergrundbild-Zoom (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | Rechtsklick zum Konfigurieren", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Preis**: Kostenloser Tarif mit begrenzten Raten verfügbar. Siehe https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Anleitung**: Erstelle einen GitHub Personal Access Token mit Models-Berechtigung, dann hier als API-Schlüssel festlegen\n\n**Hinweis**: Um dies zu verwenden, musst du den Temperatur-Parameter auf 1 setzen", "Edit directory": "Verzeichnis bearbeiten", "Action": "Aktion", "Search": "Suchen", "Tip: right-clicking a group\nalso expands it": "Tipp: Rechtsklick auf eine Gruppe\nerweitert sie ebenfalls", "Bar": "Leiste", "Show regions of potential interest": "Regionen von potenziellem Interesse anzeigen", "Clipboard": "Zwischenablage", "Stopwatch": "Stoppuhr", "Enter text to translate...": "Text zum Übersetzen eingeben...", "App": "App", "Sides": "Seiten", "No active player": "Kein aktiver Player", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Nicht alle Optionen sind in dieser App verfügbar. Du solltest auch die Konfigurationsdatei überprüfen, indem du auf den \"Konfigurationsdatei\"-Button in der oberen linken Ecke klickst oder %1 manuell öffnest.", "There might be a download in progress": "Möglicherweise läuft gerade ein Download", "Math result": "Mathematik-Ergebnis", "Fidelity": "Wiedergabetreue", "Prefixes": "Präfixe", "Terminal": "Terminal", "Incorrect password": "Falsches Passwort", "Line-separated": "Zeilengetrennt", "Always": "Immer", "☕ Break: %1 minutes": "☕ Pause: %1 Minuten", "Depends on sidebars": "Hängt von Seitenleisten ab", "Tool set to: %1": "Tool gesetzt auf: %1", "Save chat": "Chat speichern", "Crosshair overlay": "Fadenkreuz-Overlay", "Keybinds": "Tastenkürzel", "Launch": "Starten", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Könnte besser sein, wenn du viele Tippfehler machst,\naber Ergebnisse können seltsam sein und funktionieren möglicherweise nicht mit Akronymen\n(z.B. könnte \"GIMP\" dir nicht das Malprogramm geben)", "Choose model": "Modell wählen", "Base URL": "Basis-URL", "Float": "Schweben", "Wallpaper parallax": "Hintergrundbild-Parallaxe", "Invalid arguments. Must provide `command`.": "Ungültige Argumente. Muss `command` bereitstellen.", "Fully charged": "Vollständig geladen", "Earbang protection": "Ohrschutz", "Low warning": "Niedrige Warnung", "Advanced": "Erweitert", "Scroll to change brightness": "Scrollen zum Ändern der Helligkeit", "Loaded the following system prompt\n\n---\n\n%1": "Folgenden System-Prompt geladen\n\n---\n\n%1", "Show next time": "Nächstes Mal anzeigen", "Current tool: %1\nSet it with %2tool TOOL": "Aktuelles Tool: %1\nSetze es mit %2tool TOOL", "Unread indicator: show count": "Ungelesen-Indikator: Anzahl anzeigen", "Press Super+G to toggle appearance": "Drücke Super+G zum Umschalten des Erscheinungsbilds", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Das hat nicht funktioniert. Tipps:\n- Überprüfe deine Tags und NSFW-Einstellungen\n- Wenn du keinen Tag im Kopf hast, tippe eine Seitennummer", "Dots": "Dots", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Lautstärkemixer", "Config file": "Konfigurationsdatei", "API key set for %1": "API-Schlüssel gesetzt für %1", "Online via %1 | %2's model": "Online über %1 | %2s Modell", "Shell command": "Shell-Befehl", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Solche Regionen könnten Bilder oder Teile des Bildschirms sein, die eine gewisse Eingrenzung haben.\nMöglicherweise nicht immer genau.\nDies wird mit einem lokal ausgeführten Bildverarbeitungsalgorithmus durchgeführt und keine KI wird verwendet.", "Reload Hyprland & Quickshell": "Hyprland & Quickshell neu laden", "Resources": "Ressourcen", "Brightness": "Helligkeit", "Unknown": "Unbekannt", "Polling interval (ms)": "Abfrageintervall (ms)", "Lock": "Sperren", "Thinking": "Denken", "Approve": "Genehmigen", "Unfinished": "Unvollständig", "Random: Konachan": "Zufällig: Konachan", "Connected": "Verbunden", "Wallpaper safety enforced": "Hintergrundbild-Sicherheit erzwungen", "Invalid arguments. Must provide `key` and `value`.": "Ungültige Argumente. Muss `key` und `value` bereitstellen.", "24h": "24h", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Ermöglicht es, Seitenleisten durch Klicken oder Bewegen über Bildschirmecken zu öffnen, unabhängig von der Leistenposition", "Bar style": "Leisten-Stil", "Load:": "Last:", "Open file link": "Dateilink öffnen", "Ignored if terminal theming is not enabled": "Ignoriert, wenn Terminal-Theming nicht aktiviert ist", "Shutdown": "Herunterfahren", "Hour marks": "Stundenmarkierungen", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Zufälliger osu! saisonaler Hintergrund\nBild wird in ~/Pictures/Wallpapers gespeichert", "Online | Google's model\nFast, can perform searches for up-to-date information": "Online | Googles Modell\nSchnell, kann Suchen nach aktuellen Informationen durchführen", "Current model: %1\nSet it with %2model MODEL": "Aktuelles Modell: %1\nSetze es mit %2model MODELL", "Select input device": "Eingabegerät auswählen", "Connect to Wi-Fi": "Mit WLAN verbinden", "... and %1 more": "... und %1 weitere", "Cookie clock settings": "Cookie-Uhr-Einstellungen", "Brightness and volume": "Helligkeit und Lautstärke", "Choose file": "Datei wählen", "Invalid model. Supported: \n```": "Ungültiges Modell. Unterstützt: \n```", "Task Manager": "Task-Manager", "Charging:": "Lädt:", "Illegal increment": "Ungültige Erhöhung", "Total:": "Gesamt:", "or": "oder", "Battery": "Batterie", "Timeout duration (if not defined by notification) (ms)": "Timeout-Dauer (wenn nicht durch Benachrichtigung definiert) (ms)", "Cancel": "Abbrechen", "Locked": "Gesperrt", "Temperature: %1": "Temperatur: %1", "Hover to trigger": "Bewegen zum Auslösen", "Command rejected by user": "Befehl vom Benutzer abgelehnt", "User agent (for services that require it)": "User-Agent (für Dienste, die es benötigen)", "Saved to %1": "Gespeichert nach %1", "Emojis": "Emojis", "Color generation": "Farbgenerierung", "Welcome app": "Willkommens-App", "Humidity": "Luftfeuchtigkeit", "Page %1": "Seite %1", "Feels like %1": "Fühlt sich an wie %1", "Distro": "Distro", "Transparency": "Transparenz", "%1 • %2 tasks": "%1 • %2 Aufgaben", "Markdown test": "Markdown-Test", "Invalid tool. Supported tools:\n- %1": "Ungültiges Tool. Unterstützte Tools:\n- %1", "No notifications": "Keine Benachrichtigungen", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Der Hentai eine | Große Menge, viel NSFW, Qualität variiert stark", "Bluetooth": "Bluetooth", "Resume": "Fortsetzen", "Work safety": "Arbeitssicherheit", "Temperature\nChange with /temp VALUE": "Temperatur\nÄndern mit /temp WERT", "Terminal: Foreground boost (%)": "Terminal: Vordergrund-Verstärkung (%)", "Night Light | Right-click to toggle Auto mode": "Nachtlicht | Rechtsklick zum Umschalten des Auto-Modus", "Closet": "Schrank", "Yes": "Ja", "Columns": "Spalten", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Um einen API-Schlüssel zu setzen, übergebe ihn mit dem Befehl %4\n\nUm den Schlüssel anzuzeigen, übergebe \"get\" mit dem Befehl
\n\n### Für %1:\n\n**Link**: %2\n\n%3", "Kill conflicting programs?": "Konfliktierende Programme beenden?", "For storing API keys and other sensitive information": "Zum Speichern von API-Schlüsseln und anderen sensiblen Informationen", "Reject": "Ablehnen", "Set API key": "API-Schlüssel setzen", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Hinweise für Zerochan:\n- Du musst eine Farbe eingeben\n- Setze deinen Zerochan-Benutzernamen in der Konfigurationsoption `sidebar.booru.zerochan.username`. Du [könntest gesperrt werden, wenn du das nicht tust](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Content": "Inhalt", "Pomodoro": "Pomodoro", "Vertical": "Vertikal", "Pick a wallpaper": "Hintergrundbild wählen", "Load chat from %1": "Chat von %1 laden", "Launch on startup": "Beim Start starten", "Add": "Hinzufügen", "Style: general": "Stil: allgemein", "Use Levenshtein distance-based algorithm instead of fuzzy": "Levenshtein-Distanz-basierten Algorithmus anstatt Fuzzy verwenden", "Shell & utilities theming must also be enabled": "Shell & Utilities-Theming muss ebenfalls aktiviert sein", "Workspace": "Arbeitsbereich", "Translator": "Übersetzer", "Free:": "Frei:", "🌿 Long break: %1 minutes": "🌿 Lange Pause: %1 Minuten", "Value scroll": "Wert-Scrollen", "Bar position": "Leisten-Position", "Language": "Sprache", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Aktueller API-Endpunkt: %1\nSetze ihn mit %2mode ANBIETER", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Denke daran, dass man auf den meisten Geräten immer die Ein-/Aus-Taste gedrückt halten kann, um einen erzwungenen Ausschaltvorgang durchzuführen\nDies macht es nur ein kleines bisschen schwieriger, dass Unfälle passieren", "AI": "KI", "Task description": "Aufgabenbeschreibung", "Add task": "Aufgabe hinzufügen", "Donate": "Spenden", "Disable NSFW content": "NSFW-Inhalt deaktivieren", "Set the system prompt for the model.": "Den System-Prompt für das Modell festlegen.", "Done": "Fertig", "Focus": "Fokus", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "Shell-Konfigurationsdatei öffnen.\nFalls der Button nicht funktioniert oder nicht in deinem bevorzugten Editor öffnet,\nkannst du ~/.config/illogical-impulse/config.json manuell öffnen", "View Markdown source": "Markdown-Quelle anzeigen", "Border": "Rahmen", "Temperature set to %1": "Temperatur gesetzt auf %1", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Online | Googles Modell\nGoogles modernstes Mehrzweckmodell, das sich bei Programmierung und komplexen Denkaufgaben auszeichnet.", "Message the model... \"%1\" for commands": "Nachricht an das Modell... \"%1\" für Befehle", "Translation goes here...": "Übersetzung kommt hier hin...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Wenn aktiviert, hält den Inhalt der rechten Seitenleiste geladen, um die Verzögerung beim Öffnen zu reduzieren,\nzum Preis von etwa 15 MB konstantem RAM-Verbrauch. Die Bedeutung der Verzögerung hängt von der Leistung deines Systems ab.\nEin benutzerdefiniertes Kernel wie linux-cachyos könnte helfen", "For desktop wallpapers | Good quality": "Für Desktop-Hintergrundbilder | Gute Qualität", "🔴 Focus: %1 minutes": "🔴 Fokus: %1 Minuten", "The current system prompt is\n\n---\n\n%1": "Der aktuelle System-Prompt ist\n\n---\n\n%1", "About": "Über", "Quick": "Schnell", "General": "Allgemein", "UV Index": "UV-Index", "Force dark mode in terminal": "Dunklen Modus im Terminal erzwingen", "Drag or click a region • LMB: Copy • RMB: Edit": "Region ziehen oder klicken • LMB: Kopieren • RMB: Bearbeiten", "%1 characters": "%1 Zeichen", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Preis**: kostenlos. Daten werden für Training verwendet.\n\n**Anleitung**: Bei Google-Konto anmelden, AI Studio erlauben, Google Cloud-Projekt zu erstellen oder was auch immer es fragt, zurückgehen und auf API-Schlüssel abrufen klicken", "Monochrome": "Monochrom", "Details": "Details", "Issues": "Probleme", "Keyboard toggle": "Tastatur umschalten", "Might look ass. Unsupported.": "Könnte schlecht aussehen. Nicht unterstützt.", "Download": "Herunterladen", "%1 does not require an API key": "%1 erfordert keinen API-Schlüssel", "Style & wallpaper": "Stil & Hintergrundbild", "Second precision": "Sekunden-Präzision", "Group style": "Gruppen-Stil", "Break": "Pause", "Run": "Ausführen", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Viel Spaß! Du kannst die Willkommens-App jederzeit mit Super+Shift+Alt+/ wieder öffnen. Um die Einstellungs-App zu öffnen, drücke Super+I", "Interface Language": "Schnittstellen-Sprache", "Game mode": "Spielmodus", "Usage: %1save CHAT_NAME": "Verwendung: %1save CHAT_NAME", "Thin": "Dünn", "Light": "Hell", "When not fullscreen": "Wenn nicht Vollbild", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Befehle, Konfigurationen bearbeiten, suchen.\nBenötigt einen zusätzlichen Zug, um in den Suchmodus zu wechseln, falls nötig", "Privacy Policy": "Datenschutzerklärung", "Timeout (ms)": "Timeout (ms)", "Allow NSFW content": "NSFW-Inhalt erlauben", "Edit": "Bearbeiten", "Digits in the middle": "Ziffern in der Mitte", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Online | Googles Modell\nEin Gemini 2.5 Flash-Modell, optimiert für Kosteneffizienz und hohen Durchsatz.", "Weather Service": "Wetterdienst", "Background": "Hintergrund", "Pick random from this folder": "Zufällig aus diesem Ordner wählen", "Pressure": "Druck", "Save": "Speichern", "Run command": "Befehl ausführen", "Time to empty:": "Zeit bis leer:", "Place at bottom": "Unten platzieren", "Switched to search mode. Continue with the user's request.": "Zum Suchmodus gewechselt. Mit der Anfrage des Benutzers fortfahren.", "Performance Profile toggle": "Leistungsprofil umschalten", "Sidebars": "Seitenleisten", "Usage: %1load CHAT_NAME": "Verwendung: %1load CHAT_NAME", "Auto styling with Gemini": "Automatisches Styling mit Gemini", "Simple digital": "Einfach digital", "No API key set for %1": "Kein API-Schlüssel gesetzt für %1", "Enter tags, or \"%1\" for commands": "Tags eingeben oder \"%1\" für Befehle", "%1 queries pending": "%1 Abfragen ausstehend", "Discussions": "Diskussionen", "Tray": "Tray", "Numbers": "Zahlen", "Intelligence": "Intelligenz", "Open network portal": "Netzwerkportal öffnen", "No further instruction provided": "Keine weiteren Anweisungen bereitgestellt", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Sprache nicht aufgeführt oder unvollständige Übersetzungen?\nDu kannst wählen, Übersetzungen dafür mit Gemini zu generieren.\n1. Öffne die linke Seitenleiste mit Super+A, setze Modell auf Gemini (falls noch nicht)\n2. Tippe /key, drücke Enter und folge den Anweisungen\n3. Tippe /key DEIN_API_SCHLUESSEL\n4. Tippe den Locale-Code deiner Sprache unten ein und drücke Generieren", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Locale-Code, z.B. fr_FR, de_DE, zh_CN...", "Select language": "Sprache auswählen", "Generate translation with Gemini": "Übersetzung mit Gemini generieren", "Generating...\nDon't close this window!": "Generiere...\nSchließe dieses Fenster nicht!", "Generate\nTypically takes 2 minutes": "Generieren\nDauert typischerweise 2 Minuten", "Use system file picker": "System-Dateiauswahl verwenden", "Wallpaper selector": "Hintergrundbild-Auswahl", "but force at absolute corner": "aber erzwinge an absoluter Ecke", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Wenn die vorherige Option aus und diese an ist,\nkannst du immer noch über das Ende der Ecke bewegen, um die Seitenleiste zu öffnen,\nund der verbleibende Bereich kann für Lautstärke/Helligkeit-Scrollen verwendet werden", "Copy path": "Pfad kopieren", "Windows": "Fenster", "Regenerate": "Neu generieren", "Microphone": "Mikrofon", "Unmuted": "Nicht stumm", "System sound": "System-Sound", "Enable now": "Jetzt aktivieren", "Night Light": "Nachtlicht", "Show aim lines": "Ziellinien anzeigen", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Warum das cool ist:\nFür Werte ungleich 0 wird es nicht ausgelöst, wenn du die\nBildschirmecke entlang der horizontalen Kante erreichst, aber es wird ausgelöst, wenn\ndu es entlang der vertikalen Kante tust", "Please charge!\nAutomatic suspend triggers at %1%": "Bitte aufladen!\nAutomatische Suspendierung bei %1%", "Example use case: eroge on one workspace, dark Discord window on another": "Beispiel-Anwendungsfall: Eroge auf einem Arbeitsbereich, dunkles Discord-Fenster auf einem anderen", "Couldn't recognize music": "Musik konnte nicht erkannt werden", "Automatic": "Automatisch", "Hint target regions": "Zielregionen-Hinweise", "Devices": "Geräte", "Eye protection": "Augenschutz", "Japanese": "Japanisch", "Layers": "Ebenen", "Listening...": "Hören...", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "LMB zum Aktivieren/Deaktivieren\nRMB zum Umschalten der Größe\nScrollen zum Tauschen der Position", "Identify Music": "Musik erkennen", "Quick toggles": "Schnellumschalter", "Hide sussy/anime wallpapers": "Verdächtige/Anime-Hintergrundbilder ausblenden", "Android": "Android", "Show": "Anzeigen", "Muted": "Stumm", "Audio input | Right-click for volume mixer & device selector": "Audio-Eingabe | Rechtsklick für Lautstärkemixer & Geräteauswahl", "Region selector (screen snipping/Google Lens)": "Regionsauswahl (Bildschirmausschnitt/Google Lens)", "Total duration timeout (s)": "Gesamtdauer-Timeout (s)", "Music Recognition": "Musikerkennung", "Night Light | Right-click to configure": "Nachtlicht | Rechtsklick zum Konfigurieren", "Anti-flashbang (experimental)": "Anti-Flashbang (experimentell)", "Digital clock settings": "Digitale Uhr-Einstellungen", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Könnten Bilder oder Teile des Bildschirms sein, die eine gewisse Eingrenzung haben.\nMöglicherweise nicht immer genau.\nDies wird mit einem lokal ausgeführten Bildverarbeitungsalgorithmus durchgeführt und keine KI wird verwendet.", "Polling interval (m)": "Abfrageintervall (m)", "Inactive": "Inaktiv", "Authentication": "Authentifizierung", "Full warning": "Volle Warnung", "Power Profile": "Energieprofil", "Content region": "Inhaltsregion", "Internet": "Internet", "Record": "Aufnehmen", "Circle selection": "Kreisauswahl", "Edit quick toggles": "Schnellumschalter bearbeiten", "Virtual Keyboard": "Virtuelle Tastatur", "Music Recognized": "Musik erkannt", "EasyEffects": "EasyEffects", "Make sure you have songrec installed": "Stelle sicher, dass songrec installiert ist", "Dark Mode": "Dunkler Modus", "No device": "Kein Gerät", "Animate time change": "Zeitänderung animieren", "It may take a few seconds to update": "Es kann einige Sekunden dauern, bis es aktualisiert wird", "Polling interval (s)": "Abfrageintervall (s)", "Perhaps what you're listening to is too niche": "Vielleicht ist das, was du hörst, zu speziell", "Fahrenheit unit": "Fahrenheit-Einheit", "Sliders": "Schieberegler", "Roman": "Römisch", "Number style": "Zahlen-Stil", "Intensity": "Intensität", "Google Lens": "Google Lens", "Circle": "Kreis", "Hide clipboard images copied from sussy sources": "Zwischenablage-Bilder von verdächtigen Quellen ausblenden", "Scroll to Bottom": "Nach unten scrollen", "Enabled": "Aktiviert", "Nothing": "Nichts", "Audio input": "Audio-Eingabe", "with vertical offset": "mit vertikalem Versatz", "Padding": "Abstand", "Please unplug the charger": "Bitte Ladegerät abziehen", "Show notifications": "Benachrichtigungen anzeigen", "Path copied": "Pfad kopiert", "On-screen keyboard": "Bildschirmtastatur", "City name": "Stadtname", "Click to cycle through power profiles": "Klicken zum Durchwechseln der Energieprofile", "Recognize music | Right-click to toggle source": "Musik erkennen | Rechtsklick zum Umschalten der Quelle", "Use old sine wave cookie implementation": "Alte Sinuswellen-Cookie-Implementierung verwenden", "Rectangular selection": "Rechteckauswahl", "Audio output": "Audio-Ausgabe", "Applications": "Anwendungen", "Circle to Search": "Kreis zum Suchen", "Audio output | Right-click for volume mixer & device selector": "Audio-Ausgabe | Rechtsklick für Lautstärkemixer & Geräteauswahl", "Enable GPS based location": "GPS-basierte Standortbestimmung aktivieren", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Du musst zuerst deinen Gemini-API-Schlüssel eingeben.\nTippe /key in der Seitenleiste für Anweisungen.", "Sounds": "Sounds", "Active": "Aktiv", "Keep awake": "Wach halten", "Auto,": "Auto,", "Normal": "Normal", "Force hover open at absolute corner": "Hover-Öffnen an absoluter Ecke erzwingen", "Open the shell config file\nAlternatively right-click to copy path": "Shell-Konfigurationsdatei öffnen\nAlternativ Rechtsklick zum Kopieren des Pfads", "Recognize music": "Musik erkennen", "Stroke width": "Strichstärke", "Use varying shapes for password characters": "Verschiedene Formen für Passwort-Zeichen verwenden", "Battery full": "Batterie voll" } ================================================ FILE: dots/.config/quickshell/ii/translations/en_US.json ================================================ { "Material cookie": "Material cookie", "Style: Blurred": "Style: Blurred", "Unknown device": "Unknown device", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner", "No pending tasks": "No pending tasks", "Positioning": "Positioning", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.", "Critical warning": "Critical warning", "Unknown Artist": "Unknown Artist", "Web search": "Web search", "Load prompt from %1": "Load prompt from %1", "Attach a file. Only works with Gemini.": "Attach a file. Only works with Gemini.", "Reboot": "Reboot", "API key:\n\n```txt\n%1\n```": "API key:\n\n```txt\n%1\n```", "Pinned on startup": "Pinned on startup", "Right": "Right", "Reboot to firmware settings": "Reboot to firmware settings", "Automatically hide": "Automatically hide", "Waiting for response...": "Waiting for response...", "To Do": "To Do", "Full": "Full", "Select Language": "Select Language", "Password": "Password", "Bluetooth devices": "Bluetooth devices", "Enable": "Enable", "Elements": "Elements", "Start": "Start", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers", "The popular one | Best quantity, but quality can vary wildly": "The popular one | Best quantity, but quality can vary wildly", "System uptime:": "System uptime:", "illogical-impulse Welcome": "illogical-impulse Welcome", "Code saved to file": "Code saved to file", "Info": "Info", "Preferred wallpaper zoom (%)": "Preferred wallpaper zoom (%)", "Time": "Time", "Help & Support": "Help & Support", "Bubble": "Bubble", "Large images | God tier quality, no NSFW.": "Large images | God tier quality, no NSFW.", "Dark": "Dark", "Center clock": "Center clock", "Search, calculate or run": "Search, calculate or run", "Region height": "Region height", "Load chat": "Load chat", "Gives the model search capabilities (immediately)": "Gives the model search capabilities (immediately)", "Depends on workspace": "Depends on workspace", "Blurred style": "Blurred style", "Screenshot tool": "Screenshot tool", "Enter password": "Enter password", "Search the web": "Search the web", "Local only": "Local only", "at": "at", "Math": "Math", "Consider plugging in your device": "Consider plugging in your device", "Workspaces shown": "Workspaces shown", "Place the corners to trigger at the bottom": "Place the corners to trigger at the bottom", "No API key\nSet it with /key YOUR_API_KEY": "No API key\nSet it with /key YOUR_API_KEY", "Auto (System)": "Auto (System)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel", "Critically low battery": "Critically low battery", "Open editor": "Open editor", "%1 notifications": "%1 notifications", "Region width": "Region width", "Max allowed increase": "Max allowed increase", "Enable translator": "Enable translator", "Constantly rotate": "Constantly rotate", "Automatically suspends the system when battery is low": "Automatically suspends the system when battery is low", "Cannot find a GPS service. Using the fallback method instead.": "Cannot find a GPS service. Using the fallback method instead.", "Qt apps": "Qt apps", "Color picker": "Color picker", "Interface": "Interface", "Tint app icons": "Tint app icons", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Select the language for the user interface.\n\"Auto\" will use your system's locale.", "Show quote": "Show quote", "Local Ollama model | %1": "Local Ollama model | %1", "Show clock": "Show clock", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries", "Audio": "Audio", "Corner style": "Corner style", "No media": "No media", "Unknown function call: %1": "Unknown function call: %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls", "Volume": "Volume", "Gamma": "Gamma", "Medium": "Medium", "Copy code": "Copy code", "Exceeded max allowed": "Exceeded max allowed", "Keep right sidebar loaded": "Keep right sidebar loaded", "Left": "Left", "High": "High", "Rect": "Rect", "Lap": "Lap", "Clear": "Clear", "Screen snip": "Screen snip", "Reset": "Reset", "Back": "Back", "Dark/Light toggle": "Dark/Light toggle", "12h am/pm": "12h am/pm", "Download complete": "Download complete", "Enable blur": "Enable blur", "Second hand": "Second hand", "Bar & screen": "Bar & screen", "Discharging:": "Discharging:", "Up %1": "Up %1", "Low": "Low", "Hour hand": "Hour hand", "Clear chat history": "Clear chat history", "Fruit Salad": "Fruit Salad", "%1 Safe Storage": "%1 Safe Storage", "Hibernate": "Hibernate", "Delete": "Delete", "OK": "OK", "Settings": "Settings", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)", "Use Hyprlock (instead of Quickshell)": "Use Hyprlock (instead of Quickshell)", "Crosshair code (in Valorant's format)": "Crosshair code (in Valorant's format)", "Silent": "Silent", "Useless buttons": "Useless buttons", "Hover to reveal": "Hover to reveal", "Wallpaper & Colors": "Wallpaper & Colors", "Auto": "Auto", "Visibility": "Visibility", "Shell & utilities": "Shell & utilities", "Hollow": "Hollow", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "Use the system file picker instead\nRight-click to make this the default behavior", "On-screen display": "On-screen display", "Dotfiles": "Dotfiles", "Search wallpapers": "Search wallpapers", "Mic toggle": "Mic toggle", "Input": "Input", "Also unlock keyring": "Also unlock keyring", "Configuration": "Configuration", "Keep system awake": "Keep system awake", "Unknown command:": "Unknown command:", "Anime boorus": "Anime boorus", "To Do:": "To Do:", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.", "Bottom": "Bottom", "Clear the current list of images": "Clear the current list of images", "Sunrise": "Sunrise", "Show app icons": "Show app icons", "Format": "Format", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Make sure your player has MPRIS support\nor try turning off duplicate player filtering", "Pause": "Pause", "Desktop": "Desktop", "Conflicts with the shell's system tray implementation": "Conflicts with the shell's system tray implementation", "Your package manager is running": "Your package manager is running", "Conflicts with the shell's notification implementation": "Conflicts with the shell's notification implementation", "Unknown Album": "Unknown Album", "Pick wallpaper image on your system": "Pick wallpaper image on your system", "Used:": "Used:", "Cheat sheet": "Cheat sheet", "Clock style": "Clock style", "No audio source": "No audio source", "Paired": "Paired", "Documentation": "Documentation", "No": "No", "Pills": "Pills", "Thought": "Thought", "When this is off you'll have to click": "When this is off you'll have to click", "Select output device": "Select output device", "Logout": "Logout", "Tip: Close a window with Super+Q": "Tip: Close a window with Super+Q", "Finished tasks will go here": "Finished tasks will go here", "Terminal: Harmony (%)": "Terminal: Harmony (%)", "Corner open": "Corner open", "Shell conflicts killer": "Shell conflicts killer", "Clean stuff | Excellent quality, no NSFW": "Clean stuff | Excellent quality, no NSFW", "Scroll to change volume": "Scroll to change volume", "Wind": "Wind", "API key is set\nChange with /key YOUR_API_KEY": "API key is set\nChange with /key YOUR_API_KEY", "Neutral": "Neutral", "12h AM/PM": "12h AM/PM", "Number show delay when pressing Super (ms)": "Number show delay when pressing Super (ms)", "Fill": "Fill", "Always show numbers": "Always show numbers", "Dot": "Dot", "Provider set to": "Provider set to", "Unknown Title": "Unknown Title", "Anime": "Anime", "Refreshing (manually triggered)": "Refreshing (manually triggered)", "Dock": "Dock", "Require password to power off/restart": "Require password to power off/restart", "Line": "Line", "Weather": "Weather", "All-rounder | Good quality, decent quantity": "All-rounder | Good quality, decent quantity", "Scale (%)": "Scale (%)", "Copy": "Copy", "Usage": "Usage", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window", "Set the tool to use for the model.": "Set the tool to use for the model.", "Disable tools": "Disable tools", "Connect": "Connect", "Allow NSFW": "Allow NSFW", "Registration failed. Please inspect manually with the warp-cli command": "Registration failed. Please inspect manually with the warp-cli command", "Time to full:": "Time to full:", "Session": "Session", "Services": "Services", "Nothing here!": "Nothing here!", "Overview": "Overview", "Random: osu! seasonal": "Random: osu! seasonal", "If you want to somehow use fingerprint unlock...": "If you want to somehow use fingerprint unlock...", "Minute hand": "Minute hand", "Notifications": "Notifications", "Enable if you want clocks to show seconds accurately": "Enable if you want clocks to show seconds accurately", "Timer": "Timer", "Quote settings": "Quote settings", "System prompt": "System prompt", "Classic": "Classic", "Close": "Close", "Disconnect": "Disconnect", "Go to source (%1)": "Go to source (%1)", "EasyEffects | Right-click to configure": "EasyEffects | Right-click to configure", "Forget": "Forget", "Output": "Output", "Date style": "Date style", "System": "System", "Usage: %1tool TOOL_NAME": "Usage: %1tool TOOL_NAME", "Workspaces": "Workspaces", "Calendar": "Calendar", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key", "Volume limit": "Volume limit", "Sunset": "Sunset", "Dial style": "Dial style", "Hi there! First things first...": "Hi there! First things first...", "Save chat to %1": "Save chat to %1", "Security": "Security", "Total token count\nInput: %1\nOutput: %2": "Total token count\nInput: %1\nOutput: %2", "Cancel wallpaper selection": "Cancel wallpaper selection", "Please charge!\nAutomatic suspend triggers at %1": "Please charge!\nAutomatic suspend triggers at %1", "Terminal: Harmonize threshold": "Terminal: Harmonize threshold", "Be patient...": "Be patient...", "Utility buttons": "Utility buttons", "Tonal Spot": "Tonal Spot", "Prevents abrupt increments and restricts volume limit": "Prevents abrupt increments and restricts volume limit", "Set the current API provider": "Set the current API provider", "Connection failed. Please inspect manually with the warp-cli command": "Connection failed. Please inspect manually with the warp-cli command", "Networking": "Networking", "Tint icons": "Tint icons", "Low battery": "Low battery", "Make icons pinned by default": "Make icons pinned by default", "Get the next page of results": "Get the next page of results", "Invalid API provider. Supported: \n-": "Invalid API provider. Supported: \n-", "Show \"Locked\" text": "Show \"Locked\" text", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key", "Not visible to model": "Not visible to model", "Lock screen": "Lock screen", "Save to Downloads": "Save to Downloads", "Expressive": "Expressive", "Suspend at": "Suspend at", "Jump to current month": "Jump to current month", "Bold": "Bold", "Waifus only | Excellent quality, limited quantity": "Waifus only | Excellent quality, limited quantity", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Click to toggle light/dark mode\n(applied when wallpaper is chosen)", "Visualize region": "Visualize region", "Quote": "Quote", "Sleep": "Sleep", "Hit \"/\" to search": "Hit \"/\" to search", "Hug": "Hug", "Report a Bug": "Report a Bug", "Precipitation": "Precipitation", "Crosshair": "Crosshair", "Model set to %1": "Model set to %1", "Rows": "Rows", "Top": "Top", "Long break": "Long break", "Superpaste": "Superpaste", "Screen round corner": "Screen round corner", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers", "Rainbow": "Rainbow", "Weeb": "Weeb", "Large language models": "Large language models", "Online models disallowed\n\nControlled by `policies.ai` config option": "Online models disallowed\n\nControlled by `policies.ai` config option", "Policies": "Policies", "Temperature must be between 0 and 2": "Temperature must be between 0 and 2", "Automatic suspend": "Automatic suspend", "Extra wallpaper zoom (%)": "Extra wallpaper zoom (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | Right-click to configure", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1", "Edit directory": "Edit directory", "Action": "Action", "Search": "Search", "Tip: right-clicking a group\nalso expands it": "Tip: right-clicking a group\nalso expands it", "Bar": "Bar", "Show regions of potential interest": "Show regions of potential interest", "Clipboard": "Clipboard", "Stopwatch": "Stopwatch", "Enter text to translate...": "Enter text to translate...", "App": "App", "Sides": "Sides", "No active player": "No active player", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.", "There might be a download in progress": "There might be a download in progress", "Math result": "Math result", "Fidelity": "Fidelity", "Prefixes": "Prefixes", "Terminal": "Terminal", "Incorrect password": "Incorrect password", "Line-separated": "Line-separated", "Always": "Always", "☕ Break: %1 minutes": "☕ Break: %1 minutes", "Depends on sidebars": "Depends on sidebars", "Tool set to: %1": "Tool set to: %1", "Save chat": "Save chat", "Crosshair overlay": "Crosshair overlay", "Keybinds": "Keybinds", "Launch": "Launch", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)", "Choose model": "Choose model", "Base URL": "Base URL", "Float": "Float", "Wallpaper parallax": "Wallpaper parallax", "Invalid arguments. Must provide `command`.": "Invalid arguments. Must provide `command`.", "Fully charged": "Fully charged", "Earbang protection": "Earbang protection", "Low warning": "Low warning", "Advanced": "Advanced", "Scroll to change brightness": "Scroll to change brightness", "Loaded the following system prompt\n\n---\n\n%1": "Loaded the following system prompt\n\n---\n\n%1", "Show next time": "Show next time", "Current tool: %1\nSet it with %2tool TOOL": "Current tool: %1\nSet it with %2tool TOOL", "Unread indicator: show count": "Unread indicator: show count", "Press Super+G to toggle appearance": "Press Super+G to toggle appearance", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number", "Dots": "Dots", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Volume mixer", "Config file": "Config file", "API key set for %1": "API key set for %1", "Online via %1 | %2's model": "Online via %1 | %2's model", "Shell command": "Shell command", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.", "Reload Hyprland & Quickshell": "Reload Hyprland & Quickshell", "Resources": "Resources", "Brightness": "Brightness", "Unknown": "Unknown", "Polling interval (ms)": "Polling interval (ms)", "Lock": "Lock", "Thinking": "Thinking", "Approve": "Approve", "Unfinished": "Unfinished", "Random: Konachan": "Random: Konachan", "Connected": "Connected", "Wallpaper safety enforced": "Wallpaper safety enforced", "Invalid arguments. Must provide `key` and `value`.": "Invalid arguments. Must provide `key` and `value`.", "24h": "24h", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position", "Bar style": "Bar style", "Load:": "Load:", "Open file link": "Open file link", "Ignored if terminal theming is not enabled": "Ignored if terminal theming is not enabled", "Shutdown": "Shutdown", "Hour marks": "Hour marks", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers", "Online | Google's model\nFast, can perform searches for up-to-date information": "Online | Google's model\nFast, can perform searches for up-to-date information", "Current model: %1\nSet it with %2model MODEL": "Current model: %1\nSet it with %2model MODEL", "Select input device": "Select input device", "Connect to Wi-Fi": "Connect to Wi-Fi", "... and %1 more": "... and %1 more", "Cookie clock settings": "Cookie clock settings", "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing": "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing", "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.": "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.", "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons": "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons", "Can't be turned on when using 'Numbers' dial style for aesthetic reasons": "Can't be turned on when using 'Numbers' dial style for aesthetic reasons", "Brightness and volume": "Brightness and volume", "Choose file": "Choose file", "Invalid model. Supported: \n```": "Invalid model. Supported: \n```", "Task Manager": "Task Manager", "Charging:": "Charging:", "Illegal increment": "Illegal increment", "Total:": "Total:", "or": "or", "Battery": "Battery", "Timeout duration (if not defined by notification) (ms)": "Timeout duration (if not defined by notification) (ms)", "Cancel": "Cancel", "Locked": "Locked", "Temperature: %1": "Temperature: %1", "Hover to trigger": "Hover to trigger", "Command rejected by user": "Command rejected by user", "User agent (for services that require it)": "User agent (for services that require it)", "Saved to %1": "Saved to %1", "Emojis": "Emojis", "Color generation": "Color generation", "Welcome app": "Welcome app", "Humidity": "Humidity", "Page %1": "Page %1", "Feels like %1": "Feels like %1", "Distro": "Distro", "Transparency": "Transparency", "%1 • %2 tasks": "%1 • %2 tasks", "Markdown test": "Markdown test", "Invalid tool. Supported tools:\n- %1": "Invalid tool. Supported tools:\n- %1", "No notifications": "No notifications", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "The hentai one | Great quantity, a lot of NSFW, quality varies wildly", "Bluetooth": "Bluetooth", "Resume": "Resume", "Work safety": "Work safety", "Temperature\nChange with /temp VALUE": "Temperature\nChange with /temp VALUE", "Terminal: Foreground boost (%)": "Terminal: Foreground boost (%)", "Night Light | Right-click to toggle Auto mode": "Night Light | Right-click to toggle Auto mode", "Closet": "Closet", "Yes": "Yes", "Columns": "Columns", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3", "Kill conflicting programs?": "Kill conflicting programs?", "For storing API keys and other sensitive information": "For storing API keys and other sensitive information", "Reject": "Reject", "Set API key": "Set API key", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Content": "Content", "Pomodoro": "Pomodoro", "Vertical": "Vertical", "Pick a wallpaper": "Pick a wallpaper", "Load chat from %1": "Load chat from %1", "Launch on startup": "Launch on startup", "Add": "Add", "Style: general": "Style: general", "Use Levenshtein distance-based algorithm instead of fuzzy": "Use Levenshtein distance-based algorithm instead of fuzzy", "Shell & utilities theming must also be enabled": "Shell & utilities theming must also be enabled", "Workspace": "Workspace", "Translator": "Translator", "Free:": "Free:", "🌿 Long break: %1 minutes": "🌿 Long break: %1 minutes", "Value scroll": "Value scroll", "Bar position": "Bar position", "Language": "Language", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Current API endpoint: %1\nSet it with %2mode PROVIDER", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen", "AI": "AI", "Task description": "Task description", "Add task": "Add task", "Donate": "Donate", "Disable NSFW content": "Disable NSFW content", "Set the system prompt for the model.": "Set the system prompt for the model.", "Done": "Done", "Focus": "Focus", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json", "View Markdown source": "View Markdown source", "Border": "Border", "Temperature set to %1": "Temperature set to %1", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.", "Message the model... \"%1\" for commands": "Message the model... \"%1\" for commands", "Translation goes here...": "Translation goes here...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help", "For desktop wallpapers | Good quality": "For desktop wallpapers | Good quality", "🔴 Focus: %1 minutes": "🔴 Focus: %1 minutes", "The current system prompt is\n\n---\n\n%1": "The current system prompt is\n\n---\n\n%1", "About": "About", "Quick": "Quick", "General": "General", "UV Index": "UV Index", "Force dark mode in terminal": "Force dark mode in terminal", "Drag or click a region • LMB: Copy • RMB: Edit": "Drag or click a region • LMB: Copy • RMB: Edit", "%1 characters": "%1 characters", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key", "Monochrome": "Monochrome", "Details": "Details", "Issues": "Issues", "Keyboard toggle": "Keyboard toggle", "Might look ass. Unsupported.": "Might look ass. Unsupported.", "Download": "Download", "%1 does not require an API key": "%1 does not require an API key", "Style & wallpaper": "Style & wallpaper", "Second precision": "Second precision", "Group style": "Group style", "Break": "Break", "Run": "Run", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I", "Interface Language": "Interface Language", "Game mode": "Game mode", "Usage: %1save CHAT_NAME": "Usage: %1save CHAT_NAME", "Thin": "Thin", "Light": "Light", "When not fullscreen": "When not fullscreen", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed", "Privacy Policy": "Privacy Policy", "Timeout (ms)": "Timeout (ms)", "Allow NSFW content": "Allow NSFW content", "Edit": "Edit", "Digits in the middle": "Digits in the middle", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.", "Weather Service": "Weather Service", "Background": "Background", "Pick random from this folder": "Pick random from this folder", "Pressure": "Pressure", "Save": "Save", "Run command": "Run command", "Time to empty:": "Time to empty:", "Place at bottom": "Place at bottom", "Switched to search mode. Continue with the user's request.": "Switched to search mode. Continue with the user's request.", "Performance Profile toggle": "Performance Profile toggle", "Sidebars": "Sidebars", "Usage: %1load CHAT_NAME": "Usage: %1load CHAT_NAME", "Auto styling with Gemini": "Auto styling with Gemini", "Simple digital": "Simple digital", "No API key set for %1": "No API key set for %1", "Enter tags, or \"%1\" for commands": "Enter tags, or \"%1\" for commands", "%1 queries pending": "%1 queries pending", "Discussions": "Discussions", "Tray": "Tray", "Numbers": "Numbers", "Intelligence": "Intelligence", "Open network portal": "Open network portal", "No further instruction provided": "No further instruction provided", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Locale code, e.g. fr_FR, de_DE, zh_CN...", "Select language": "Select language", "Generate translation with Gemini": "Generate translation with Gemini", "Generating...\nDon't close this window!": "Generating...\nDon't close this window!", "Generate\nTypically takes 2 minutes": "Generate\nTypically takes 2 minutes", "Use system file picker": "Use system file picker", "Wallpaper selector": "Wallpaper selector", "but force at absolute corner": "but force at absolute corner", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll", "Copy path": "Copy path", "Windows": "Windows", "Regenerate": "Regenerate", "Microphone": "Microphone", "Unmuted": "Unmuted", "System sound": "System sound", "Enable now": "Enable now", "Night Light": "Night Light", "Show aim lines": "Show aim lines", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge", "Please charge!\nAutomatic suspend triggers at %1%": "Please charge!\nAutomatic suspend triggers at %1%", "Example use case: eroge on one workspace, dark Discord window on another": "Example use case: eroge on one workspace, dark Discord window on another", "Couldn't recognize music": "Couldn't recognize music", "Automatic": "Automatic", "Hint target regions": "Hint target regions", "Devices": "Devices", "Eye protection": "Eye protection", "Japanese": "Japanese", "Layers": "Layers", "Listening...": "Listening...", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "LMB to enable/disable\nRMB to toggle size\nScroll to swap position", "Identify Music": "Identify Music", "Quick toggles": "Quick toggles", "Hide sussy/anime wallpapers": "Hide sussy/anime wallpapers", "Android": "Android", "Show": "Show", "Muted": "Muted", "Audio input | Right-click for volume mixer & device selector": "Audio input | Right-click for volume mixer & device selector", "Region selector (screen snipping/Google Lens)": "Region selector (screen snipping/Google Lens)", "Total duration timeout (s)": "Total duration timeout (s)", "Music Recognition": "Music Recognition", "Night Light | Right-click to configure": "Night Light | Right-click to configure", "Anti-flashbang (experimental)": "Anti-flashbang (experimental)", "Digital clock settings": "Digital clock settings", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.", "Polling interval (m)": "Polling interval (m)", "Inactive": "Inactive", "Authentication": "Authentication", "Full warning": "Full warning", "Power Profile": "Power Profile", "Content region": "Content region", "Internet": "Internet", "Record": "Record", "Circle selection": "Circle selection", "Edit quick toggles": "Edit quick toggles", "Virtual Keyboard": "Virtual Keyboard", "Music Recognized": "Music Recognized", "EasyEffects": "EasyEffects", "Make sure you have songrec installed": "Make sure you have songrec installed", "Dark Mode": "Dark Mode", "No device": "No device", "Animate time change": "Animate time change", "It may take a few seconds to update": "It may take a few seconds to update", "Polling interval (s)": "Polling interval (s)", "Perhaps what you're listening to is too niche": "Perhaps what you're listening to is too niche", "Fahrenheit unit": "Fahrenheit unit", "Sliders": "Sliders", "Roman": "Roman", "Number style": "Number style", "Intensity": "Intensity", "Google Lens": "Google Lens", "Circle": "Circle", "Hide clipboard images copied from sussy sources": "Hide clipboard images copied from sussy sources", "Scroll to Bottom": "Scroll to Bottom", "Enabled": "Enabled", "Nothing": "Nothing", "Audio input": "Audio input", "with vertical offset": "with vertical offset", "Padding": "Padding", "Please unplug the charger": "Please unplug the charger", "Show notifications": "Show notifications", "Path copied": "Path copied", "On-screen keyboard": "On-screen keyboard", "City name": "City name", "Click to cycle through power profiles": "Click to cycle through power profiles", "Recognize music | Right-click to toggle source": "Recognize music | Right-click to toggle source", "Use old sine wave cookie implementation": "Use old sine wave cookie implementation", "Rectangular selection": "Rectangular selection", "Audio output": "Audio output", "Applications": "Applications", "Circle to Search": "Circle to Search", "Audio output | Right-click for volume mixer & device selector": "Audio output | Right-click for volume mixer & device selector", "Enable GPS based location": "Enable GPS based location", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.", "Sounds": "Sounds", "Active": "Active", "Keep awake": "Keep awake", "Auto,": "Auto,", "Normal": "Normal", "Force hover open at absolute corner": "Force hover open at absolute corner", "Open the shell config file\nAlternatively right-click to copy path": "Open the shell config file\nAlternatively right-click to copy path", "Recognize music": "Recognize music", "Stroke width": "Stroke width", "Use varying shapes for password characters": "Use varying shapes for password characters", "Battery full": "Battery full", "Pin": "Pin", "Unpin": "Unpin" } ================================================ FILE: dots/.config/quickshell/ii/translations/es_MX.json ================================================ { "Dark/Light toggle": "Alternar oscuro/claro", "There might be a download in progress. Check your Downloads folder.": "Puede haber una descarga en curso. Revisa tu carpeta de Descargas.", "Authentication": "Autenticación", "More comfortable viewing at night": "Visualización más cómoda de noche", "Disconnect": "Desconectar", "Sunrise": "Amanecer", "Show notifications": "Mostrar notificaciones", "Video Recording Path": "Ruta de grabación de video", "Input device": "Dispositivo de entrada", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Cuando la opción anterior está desactivada y esta está activada,\naún puedes pasar el cursor por el extremo de la esquina para abrir la barra lateral,\ny el área restante puede usarse para desplazar el volumen/brillo", "Save chat": "Guardar chat", "Bar style": "Estilo de barra", "Monospace font": "Fuente monoespaciada", "Show hidden icons": "Mostrar iconos ocultos", "Work safety": "Seguridad laboral", "Page %1": "Página %1", "Illegal increment": "Incremento no permitido", "New desktop": "Nuevo escritorio", "Adapts the display (physical screen) brightness

Pros: Less expensive, retains colors
Cons: Not immediately responsive

Adjusts display brightness after each Hyprland IPC event": "Adapta el brillo del monitor (pantalla física)

Ventajas: Menos costoso, conserva los colores
Desventajas: No responde de inmediato

Ajusta el brillo del monitor tras cada evento IPC de Hyprland", "Font family name (e.g., JetBrains Mono NF)": "Nombre de familia de fuente (p. ej., JetBrains Mono NF)", "Open": "Abrir", "Jump to current month": "Ir al mes actual", "Shut down": "Apagar", "Normal": "Normal", "Fonts": "Fuentes", "Widgets": "Widgets", "Set the tool to use for the model.": "Establece la herramienta a usar con el modelo.", "Bar": "Barra", "Font width and roundness settings are only available for some fonts like Google Sans Flex": "Los ajustes de ancho y redondez de fuente solo están disponibles para algunas fuentes como Google Sans Flex", "Hour hand": "Manecilla de hora", "Use macOS-like symbols for mods keys": "Usar símbolos estilo macOS para teclas modificadoras", "Unpin from Start": "Desanclar del inicio", "Connected": "Conectado", "All": "Todo", "Invalid tool. Supported tools:\n- %1": "Herramienta no válida. Herramientas compatibles:\n- %1", "Timer": "Temporizador", "Bottom": "Abajo", "Connect": "Conectar", "Sunset": "Atardecer", "View Markdown source": "Ver fuente Markdown", "Cannot find a GPS service. Using the fallback method instead.": "No se encontró un servicio GPS. Usando el método alternativo.", "Minute hand": "Manecilla de minutos", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "No todas las opciones están disponibles en esta aplicación. También deberías revisar el archivo de configuración haciendo clic en el botón \"Archivo de configuración\" en la esquina superior izquierda o abriendo %1 manualmente.", "Click to unmute": "Clic para activar el sonido", "Useless buttons": "Botones inútiles", "Command rejected by user": "Comando rechazado por el usuario", "Lock": "Bloquear", "Expressive font": "Fuente expresiva", "Security": "Seguridad", "Sound input": "Entrada de sonido", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "En línea | Modelo de Google\nModelo más reciente, más lento que su predecesor pero con respuestas de mayor calidad", "Roman": "Romano", "Style: general": "Estilo: general", "Numbers font": "Fuente de números", "Internet": "Internet", "Date style": "Estilo de fecha", "When this is off you'll have to click": "Cuando está desactivado tendrás que hacer clic", "Corner style": "Estilo de esquina", "Rows": "Filas", "Tool set to: %1": "Herramienta establecida en: %1", "Online models disallowed\n\nControlled by `policies.ai` config option": "Modelos en línea no permitidos\n\nControlado por la opción de configuración `policies.ai`", "Number show delay when pressing Super (ms)": "Retraso para mostrar número al presionar Super (ms)", "Keep right sidebar loaded": "Mantener la barra lateral derecha cargada", "Base URL": "URL base", "Cheat sheet": "Hoja de trucos", "Recognize music": "Reconocer música", "Region width": "Ancho de región", "Wallpaper & Colors": "Fondo de pantalla y colores", "Sounds": "Sonidos", "About": "Acerca de", "Dial style": "Estilo de esfera", "Classic": "Clásico", "Translation goes here...": "La traducción va aquí...", "Widget: Weather": "Widget: Clima", "Copy": "Copiar", "Services": "Servicios", "Dark": "Oscuro", "The current system prompt is\n\n---\n\n%1": "El prompt de sistema actual es\n\n---\n\n%1", "Refreshing (manually triggered)": "Actualizando (activado manualmente)", "Qt apps": "Apps Qt", "Corner open": "Apertura por esquina", "Reload Hyprland & Quickshell": "Recargar Hyprland y Quickshell", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notas para Zerochan:\n- Debes ingresar un color\n- Configura tu nombre de usuario de Zerochan en la opción `sidebar.booru.zerochan.username`. ¡[Podrías ser baneado si no lo haces](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "No": "No", "Mic toggle": "Alternar micrófono", "Snipping area": "Área de recorte", "Waifus only | Excellent quality, limited quantity": "Solo waifus | Excelente calidad, cantidad limitada", "Yes": "Sí", "Restart": "Reiniciar", "Thin": "Delgado", "File Explorer": "Explorador de archivos", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Uso: %1superpaste NUM_DE_ENTRADAS[i]\nAgrega i cuando quieras imágenes\nEjemplos:\n%1superpaste 4i para las últimas 4 imágenes\n%1superpaste 7 para las últimas 7 entradas", "Font roundness": "Redondez de fuente", "Click to cycle through power profiles": "Clic para cambiar entre perfiles de energía", "Split buttons": "Botones divididos", "Bar & screen": "Barra y pantalla", "Pin to Start": "Anclar al inicio", "Used for displaying numbers": "Usada para mostrar números", "Center clock": "Reloj centrado", "Audio": "Audio", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Selecciona el idioma de la interfaz.\n\"Auto\" usará la configuración regional de tu sistema.", "Unknown Album": "Álbum desconocido", "Your package manager is running": "Tu gestor de paquetes está en ejecución", "Swap": "Intercambio", "Total token count\nInput: %1\nOutput: %2": "Total de tokens\nEntrada: %1\nSalida: %2", "Volume limit": "Límite de volumen", "Sign out": "Cerrar sesión", "Tonal Spot": "Tono puntual", "Apps": "Aplicaciones", "Privacy Policy": "Política de privacidad", "Media": "Multimedia", "%1 Safe Storage": "%1 Almacenamiento seguro", "Network": "Red", "Inactive": "Inactivo", "Save chat to %1": "Guardar chat en %1", "RAM": "RAM", "No media": "Sin multimedia", "Invalid arguments. Must provide `command`.": "Argumentos no válidos. Se debe proporcionar `command`.", "Bluetooth": "Bluetooth", "Least busy": "Menos ocupado", "When not fullscreen": "Cuando no está en pantalla completa", "Used for general UI text": "Usada para texto general de la interfaz", "Prefixes": "Prefijos", "Weeb": "Weeb", "Bluetooth devices": "Dispositivos Bluetooth", "Anime": "Anime", "Tray": "Bandeja", "Top": "Arriba", "Policies": "Políticas", "Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages": "Obtén las últimas funciones y mejoras de seguridad con\nla actualización más reciente.\n\n%1 paquetes", "Pressure": "Presión", "Math": "Matemáticas", "Enter tags, or \"%1\" for commands": "Ingresa etiquetas, o \"%1\" para comandos", "Enable GPS based location": "Activar ubicación por GPS", "Super key symbol": "Símbolo de la tecla Super", "Load chat": "Cargar chat", "Hibernate": "Hibernar", "Config file": "Archivo de\nconfiguración", "Stroke width": "Grosor de trazo", "Reading font": "Fuente de lectura", "Close all windows": "Cerrar todas las ventanas", "Workspace": "Espacio de trabajo", "Clock style (locked)": "Estilo de reloj (bloqueado)", "Right": "Derecha", "Night Light": "Luz nocturna", "12h am/pm": "12h am/pm", "Couldn't recognize music": "No se pudo reconocer la música", "Fidelity": "Fidelidad", "Settings": "Configuración", "Hover to reveal": "Pasar el cursor para revelar", "Polling interval (s)": "Intervalo de consulta (s)", "Load prompt from %1": "Cargar prompt desde %1", "Web": "Web", "Dots": "Puntos", "Rect": "Rectángulo", "Be patient...": "Ten paciencia...", "Free:": "Libre:", "Allow NSFW content": "Permitir contenido NSFW", "Sound effects": "Efectos de sonido", "Language": "Idioma", "Circle to Search": "Círculo para buscar", "Transparency": "Transparencia", "Networking": "Redes", "Large language models": "Modelos de lenguaje", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Para establecer una clave API, pásala con el comando %4\n\nPara ver la clave, pasa \"get\" con el comando
\n\n### Para %1:\n\n**Enlace**: %2\n\n%3", "%1\nInternet access": "%1\nAcceso a Internet", "Ignored if terminal theming is not enabled": "Se ignora si el tema de terminal no está activado", "e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc": "p. ej. 󰘴 para Ctrl, 󰘵 para Alt, 󰘶 para Shift, etc.", "Close (Esc)": "Cerrar (Esc)", "Break": "Descanso", "Require password to power off/restart": "Requerir contraseña para apagar/reiniciar", "Generate translation with Gemini": "Generar traducción con Gemini", "Battery": "Batería", "at": "a las", "Use symbols for mouse": "Usar símbolos para el ratón", "Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")": "Mostrar modificadores y teclas con varias teclas (p. ej., \"Ctrl + A\" en lugar de \"Ctrl A\" o \"󰘴 + A\" en lugar de \"󰘴 A\")", "Long break": "Descanso largo", "No active player": "Sin reproductor activo", "Overlay: Crosshair": "Superposición: Mira", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Por qué es útil:\nPara valores distintos de 0, no se activará al llegar a la\nesquina de la pantalla por el borde horizontal, pero sí\nal hacerlo por el borde vertical", "Brightness and volume": "Brillo y volumen", "Scroll to Bottom": "Desplazarse al final", "Always show numbers": "Mostrar siempre los números", "Brightness": "Brillo", "Choose file": "Elegir archivo", "Local only": "Solo local", "Keybinds": "Atajos de teclado", "Go to source (%1)": "Ir a la fuente (%1)", "Do you want to allow this app to make changes to your device?": "¿Quieres permitir que esta aplicación realice cambios en tu dispositivo?", "Disable tools": "Desactivar herramientas", "Reboot to firmware settings": "Reiniciar a configuración de firmware", "Draggable": "Arrastrable", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Asegúrate de que tu reproductor tenga soporte MPRIS\no intenta desactivar el filtrado de reproductores duplicados", "Balance brightness based on content": "Equilibrar el brillo según el contenido", "GitHub": "GitHub", "Info": "Información", "Clear the current list of images": "Limpiar la lista actual de imágenes", "Pinned on startup": "Anclado al inicio", "Color picker": "Selector de color", "Automatic": "Automático", "City name": "Nombre de ciudad", "e.g. 󱊫 for F1, 󱊶 for F12": "p. ej. 󱊫 para F1, 󱊶 para F12", "Top-down": "De arriba hacia abajo", "Font family name (e.g., Space Grotesk)": "Nombre de familia de fuente (p. ej., Space Grotesk)", "Output": "Salida", "Advanced": "Avanzado", "Second precision": "Precisión de segundos", "To Do": "Por hacer", "Automatically hide": "Ocultar automáticamente", "Quick": "Rápido", "Keybind font size": "Tamaño de fuente de atajos", "Hi there! First things first...": "¡Hola! Primero lo primero...", "Music Recognition": "Reconocimiento de música", "Listening...": "Escuchando...", "Show": "Mostrar", "Center icons": "Centrar iconos", "Dot": "Punto", "Incorrect password": "Contraseña incorrecta", "Unmuted": "Sin silencio", "On-screen keyboard": "Teclado en pantalla", "Brightness adjustment": "Ajuste de brillo", "Workspaces shown": "Espacios de trabajo mostrados", "Donate": "Donar", "Recognize text": "Reconocer texto", "Content": "Contenido", "Enter password": "Ingresar contraseña", "Han chars": "Caracteres Han", "Please unplug the charger": "Por favor desconecta el cargador", "☕ Break: %1 minutes": "☕ Descanso: %1 minutos", "Delete": "Eliminar", "API key:\n\n```txt\n%1\n```": "Clave API:\n\n```txt\n%1\n```", "Android": "Android", "Done": "Listo", "Registration failed. Please inspect manually with the warp-cli command": "El registro falló. Por favor inspecciona manualmente con el comando warp-cli", "Enable blur": "Activar desenfoque", "Wind": "Viento", "Value scroll": "Desplazamiento de valor", "Not visible to model": "No visible para el modelo", "Exceeded max allowed": "Se superó el máximo permitido", "Report a Bug": "Reportar un error", "Speakers (%1): %2": "Altavoces (%1): %2", "Enable if you want clocks to show seconds accurately": "Activa esto si quieres que los relojes muestren los segundos con precisión", "Not secured": "No asegurado", "Used:": "Usado:", "Depends on sidebars": "Depende de las barras\nlaterales", "Second hand": "Segundero", "Reboot": "Reiniciar", "Reject": "Rechazar", "For storing API keys and other sensitive information": "Para almacenar claves API y otra información sensible", "Not connected": "No conectado", "Critically low battery": "Batería críticamente baja", "Show this window on all desktops": "Mostrar esta ventana en todos los escritorios", "Time": "Hora", "Sliders": "Controles deslizantes", "Enabled": "Activado", "For desktop wallpapers | Good quality": "Para fondos de escritorio | Buena calidad", "Left to right": "De izquierda a derecha", "Quick toggles": "Interruptores rápidos", "Details": "Detalles", "Audio output | Right-click for volume mixer & device selector": "Salida de audio | Clic derecho para mezclador de volumen y selector de dispositivo", "Use the system file picker instead\nRight-click to make this the default behavior": "Usar el selector de archivos del sistema en su lugar\nClic derecho para establecer esto como comportamiento predeterminado", "Thought": "Pensamiento", "Volume": "Volumen", "Emojis": "Emojis", "Time to full:": "Tiempo para carga completa:", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Pueden ser imágenes o partes de la pantalla con algún contenido delimitado.\nPuede no ser siempre preciso.\nEsto se realiza con un algoritmo de procesamiento de imágenes local, sin IA.", "Perhaps what you're listening to is too niche": "Quizás lo que estás escuchando es demasiado de nicho", "Command": "Comando", "Wallpaper selector": "Selector de fondo de pantalla", "Muted": "Silenciado", "Sleep": "Suspender", "Gives the model search capabilities (immediately)": "Proporciona al modelo capacidades de búsqueda (de inmediato)", "Crosshair code (in Valorant's format)": "Código de mira (en formato de Valorant)", "Used for headings and titles": "Usada para encabezados y títulos", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Usa Gemini para categorizar el fondo de pantalla y luego selecciona un preset basado en eso.\nNecesitarás establecer la clave API de Gemini en la barra lateral izquierda primero.\nLas imágenes se reducen para mejorar el rendimiento, pero por seguridad,\nno selecciones fondos con información sensible.", "Night Light | Right-click to toggle Auto mode": "Luz nocturna | Clic derecho para alternar modo automático", "Change password": "Cambiar contraseña", "Unknown Title": "Título desconocido", "Numbers": "Números", "24h": "24h", "No applications": "Sin aplicaciones", "Force hover open at absolute corner": "Forzar apertura al pasar por la esquina exacta", "of %1": "de %1", "Code saved to file": "Código guardado en archivo", "Content region": "Región de contenido", "Invalid model. Supported: \n```": "Modelo no válido. Compatibles: \n```", "Font family name": "Nombre de familia de fuente", "Please charge!\nAutomatic suspend triggers at %1%": "¡Por favor carga el dispositivo!\nLa suspensión automática se activa al %1%", "Manage accounts": "Administrar cuentas", "Open editor": "Abrir editor", "Font used for Nerd Font icons": "Fuente usada para iconos de Nerd Font", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Comandos, editar configuraciones, buscar.\nRequiere un turno extra para cambiar a modo búsqueda si es necesario", "Record": "Grabar", "Used for decorative/expressive text": "Usada para texto decorativo/expresivo", "Visualize region": "Visualizar región", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "¿Idioma no listado o traducciones incompletas?\nPuedes elegir generar traducciones con Gemini.\n1. Abre la barra lateral izquierda con Super+A, establece el modelo en Gemini (si no lo está ya)\n2. Escribe /key, presiona Enter y sigue las instrucciones\n3. Escribe /key TU_CLAVE_API\n4. Escribe el código de idioma abajo y presiona Generar", "Saving...": "Guardando...", "Monochrome": "Monocromático", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Fondo estacional aleatorio de osu!\nLa imagen se guarda en ~/Pictures/Wallpapers", "Polkit": "Polkit", "Manage my account": "Administrar mi cuenta", "Edit quick toggles": "Editar interruptores rápidos", "Unknown function call: %1": "Llamada de función desconocida: %1", "Clipboard": "Portapapeles", "Dims screen content as needed.

Pros: Immediately responsive
Cons: Expensive and can hurt color accuracy

Uses a Hyprland screen shader": "Atenúa el contenido de la pantalla según sea necesario.

Ventajas: Responde de inmediato
Desventajas: Costoso y puede afectar la precisión del color

Usa un shader de pantalla de Hyprland", "No new notifications": "Sin nuevas notificaciones", "Volume mixer": "Mezclador de volumen", "Force dark mode in terminal": "Forzar modo oscuro en la terminal", "Save paths": "Guardar rutas", "Copy path": "Copiar ruta", "Anime boorus": "Boorus de anime", "Open network portal": "Abrir portal de red", "Quick markup (Ctrl+E)": "Marcado rápido (Ctrl+E)", "Large images | God tier quality, no NSFW.": "Imágenes grandes | Calidad excepcional, sin NSFW.", "Polling interval (ms)": "Intervalo de consulta (ms)", "Model set to %1": "Modelo establecido en %1", "Identify Music": "Identificar música", "Circle selection": "Selección circular", "Dark Mode": "Modo oscuro", "Make icons pinned by default": "Anclar iconos de forma predeterminada", "User agent (for services that require it)": "Agente de usuario (para servicios que lo requieren)", "Use varying shapes for password characters": "Usar formas variadas para los caracteres de contraseña", "Search, calculate or run": "Buscar, calcular o ejecutar", "More Bluetooth settings": "Más ajustes de Bluetooth", "Output device": "Dispositivo de salida", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Fondo de anime SFW aleatorio de Konachan\nLa imagen se guarda en ~/Pictures/Wallpapers", "Rectangle": "Rectángulo", "Tip: right-clicking a group\nalso expands it": "Consejo: hacer clic derecho en un grupo\ntambién lo expande", "Pin to taskbar": "Anclar a la barra de tareas", "Show date": "Mostrar fecha", "Battery: %1%2": "Batería: %1%2", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Endpoint API actual: %1\nEstablécelo con %2mode PROVEEDOR", "Columns": "Columnas", "Recognize music | Right-click to toggle source": "Reconocer música | Clic derecho para cambiar fuente", "Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"": "Reemplaza 󱕐 por \"Desplazar ↓\", 󱕑 \"Desplazar ↑\", L󰍽 \"Clic izq.\", R󰍽 \"Clic der.\", 󱕒 \"Desplazar ↑/↓\" y ⇞/⇟ por \"Pág. ↑/↓\"", "Choose model": "Elegir modelo", "Approve": "Aprobar", "Configuration": "Configuración", "Hit \"/\" to search": "Presiona \"/\" para buscar", "Regenerate": "Regenerar", "Move right": "Mover a la derecha", "Image source": "Fuente de imagen", "Dock": "Dock", "Open recordings folder": "Abrir carpeta de grabaciones", "Pin": "Anclar", "Click to mute": "Clic para silenciar", "Search for apps": "Buscar aplicaciones", "Connection failed. Please inspect manually with the warp-cli command": "La conexión falló. Por favor inspecciona manualmente con el comando warp-cli", "Show only when locked": "Mostrar solo cuando está bloqueado", "Sides": "Lados", "Edit directory": "Editar directorio", "You can also manually edit cheatsheet.superKey": "También puedes editar manualmente cheatsheet.superKey", "Color generation": "Generación de color", "Unpin": "Desanclar", "Font weight": "Peso de fuente", "Reset": "Restablecer", "Math result": "Resultado matemático", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "¡Disfruta! Puedes volver a abrir la aplicación de bienvenida en cualquier momento con Super+Shift+Alt+/. Para abrir la configuración, presiona Super+I", "Notifications": "Notificaciones", "Save to Downloads": "Guardar en Descargas", "Command-line-invoked Action": "Acción invocada por línea de comandos", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Teclas de flecha para navegar, Enter para seleccionar\nEsc o clic en cualquier lugar para cancelar", "Paired": "Vinculado", "Game mode": "Modo juego", "Emoji": "Emoji", "Health:": "Salud:", "Anti-flashbang (experimental)": "Anti-destello (experimental)", "Turn on from sunset to sunrise": "Activar del atardecer al amanecer", "Close": "Cerrar", "Workspaces": "Espacios de trabajo", "Record region": "Grabar región", "Shell & utilities theming must also be enabled": "El tema de shell y utilidades también debe estar activado", "Superpaste": "Superpegar", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Primero necesitas ingresar tu clave API de Gemini.\nEscribe /key en la barra lateral para ver las instrucciones.", "Enable update checks": "Activar verificación de actualizaciones", "Select Language": "Seleccionar idioma", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Código de idioma, p. ej. fr_FR, de_DE, zh_CN...", "Desktop %1": "Escritorio %1", "Keep awake": "Mantener activo", "Hollow": "Hueco", "Group style": "Estilo de grupo", "It may take a few seconds to update": "Puede tardar unos segundos en actualizarse", "Unknown command:": "Comando desconocido:", "Action": "Acción", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Establece la temperatura (aleatoriedad) del modelo. Los valores van de 0 a 2 para Gemini, de 0 a 1 para otros modelos. El valor predeterminado es 0.5.", "Style: Blurred": "Estilo: Difuminado", "Parallax": "Desplazamiento", "Bubble": "Burbuja", "Saved to %1": "Guardado en %1", "Current tool: %1\nSet it with %2tool TOOL": "Herramienta actual: %1\nEstablécela con %2tool HERRAMIENTA", "The popular one | Best quantity, but quality can vary wildly": "El popular | Mayor cantidad, pero la calidad puede variar mucho", "Sidebars": "Barras laterales", "Description font size": "Tamaño de fuente de descripción", "Close window": "Cerrar ventana", "Calendar": "Calendario", "%1 mins": "%1 min", "Automatically suspends the system when battery is low": "Suspende automáticamente el sistema cuando la batería está baja", "Enable": "Activar", "Show aim lines": "Mostrar líneas de mira", "Overview": "Vista general", "Enable opening zoom animation": "Activar animación de zoom al abrir", "Copy code": "Copiar código", "Path copied": "Ruta copiada", "Audio output": "Salida de audio", "Open file link": "Abrir enlace de archivo", "Tooltips": "Información emergente", "Move left": "Mover a la izquierda", "Sound output": "Salida de sonido", "Windows": "Ventanas", "Fahrenheit unit": "Unidad Fahrenheit", "Night Light | Right-click to configure": "Luz nocturna | Clic derecho para configurar", "Set API key": "Establecer clave API", "Markdown test": "Prueba de Markdown", "Pills": "Píldoras", "Font family name (e.g., Google Sans Flex)": "Nombre de familia de fuente (p. ej., Google Sans Flex)", "illogical-impulse": "illogical-impulse", "... and %1 more": "... y %1 más", "Shell & utilities": "Shell y utilidades", "Provider set to": "Proveedor establecido en", "Main font": "Fuente principal", "Dotfiles": "Dotfiles", "Current model: %1\nSet it with %2model MODEL": "Modelo actual: %1\nEstablécelo con %2model MODELO", "Nerd font icons": "Iconos de Nerd Font", "Style & wallpaper": "Estilo y fondo de pantalla", "Scroll to change brightness": "Desplazar para cambiar el brillo", "Input": "Entrada", "Intensity": "Intensidad", "🔴 Focus: %1 minutes": "🔴 Enfoque: %1 minutos", "Lock screen": "Pantalla de bloqueo", "Password": "Contraseña", "with vertical offset": "con desplazamiento vertical", "Fill": "Relleno", "Generating...\nDon't close this window!": "Generando...\n¡No cierres esta ventana!", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "En línea | Modelo de %1 | Proporciona respuestas rápidas, ágiles y bien formateadas. Desventajas: no muy dispuesto a hacer cosas; puede inventar llamadas de función desconocidas", "Feels like %1": "Sensación térmica %1", "Welcome app": "App de bienvenida", "Start": "Iniciar", "Digital clock settings": "Configuración del reloj digital", "Expressive": "Expresivo", "Auto,": "Auto,", "Task View": "Vista de tareas", "Bottom-up": "De abajo hacia arriba", "Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar": "Escribe /key para comenzar con modelos en línea\nCtrl+O para expandir la barra lateral\nCtrl+P para anclar la barra lateral\nCtrl+D para desanclar la barra lateral", "Wallpaper safety enforced": "Seguridad de fondo de pantalla aplicada", "Automatic suspend": "Suspensión automática", "Music Recognized": "Música reconocida", "Cookie": "Cookie", "UV Index": "Índice UV", "API key is set\nChange with /key YOUR_API_KEY": "Clave API establecida\nCámbiala con /key TU_CLAVE_API", "Show next time": "Mostrar la próxima vez", "Most busy": "Más ocupado", "Finished tasks will go here": "Las tareas completadas aparecerán aquí", "EasyEffects": "EasyEffects", "Issues": "Problemas", "Load:": "Carga:", "Hug": "Abrazo", "(Plugged in)": "(Conectado)", "Discussions": "Discusiones", "Enter text to translate...": "Ingresa texto para traducir...", "Low battery": "Batería baja", "Positioning": "Posicionamiento", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Te permite abrir las barras laterales haciendo clic o pasando el cursor por las esquinas de la pantalla independientemente de la posición de la barra", "Resources": "Recursos", "Load chat from %1": "Cargar chat desde %1", "Clear all": "Limpiar todo", "OK": "Aceptar", "Tip: Close a window with Super+Q": "Consejo: Cierra una ventana con Super+Q", "Clean stuff | Excellent quality, no NSFW": "Contenido limpio | Excelente calidad, sin NSFW", "Save": "Guardar", "Productivity": "Productividad", "Disable NSFW content": "Desactivar contenido NSFW", "Locked": "Bloqueado", "Nothing here!": "¡Nada aquí!", "Temperature: %1": "Temperatura: %1", "Hour marks": "Marcas de hora", "Web search": "Búsqueda web", "No pending tasks": "Sin tareas pendientes", "Circle": "Círculo", "API key set for %1": "Clave API establecida para %1", "Unknown device": "Dispositivo desconocido", "Scroll to change volume": "Desplazar para cambiar el volumen", "Generate\nTypically takes 2 minutes": "Generar\nGeneralmente tarda 2 minutos", "Allow NSFW": "Permitir NSFW", "Stopwatch": "Cronómetro", "Hint target regions": "Resaltar regiones objetivo", "Text extractor": "Extractor de texto", "Open the shell config file\nAlternatively right-click to copy path": "Abrir el archivo de configuración del shell\nAlternativamente, clic derecho para copiar la ruta", "No API key set for %1": "No hay clave API establecida para %1", "Critical warning": "Advertencia crítica", "Animate time change": "Animar cambio de hora", "Screen snip": "Captura de pantalla", "CPU": "CPU", "Nothing": "Nada", "Focus": "Enfoque", "Run": "Ejecutar", "Temperature must be between 0 and 2": "La temperatura debe estar entre 0 y 2", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Puede funcionar mejor si cometes muchos errores tipográficos,\npero los resultados pueden ser extraños y puede que no funcione con acrónimos\n(p. ej., \"GIMP\" puede que no te dé el programa de edición)", "Documentation": "Documentación", "Screenshot Path (leave empty to just copy)": "Ruta de captura de pantalla (deja vacío para solo copiar)", "Help & Support": "Ayuda y soporte", "All-rounder | Good quality, decent quantity": "Versátil | Buena calidad, cantidad decente", "On": "Activado", "Earbang protection": "Protección contra destellos de audio", "Rainbow": "Arcoíris", "Download": "Descargar", "Cloudflare WARP": "Cloudflare WARP", "Audio input | Right-click for volume mixer & device selector": "Entrada de audio | Clic derecho para mezclador de volumen y selector de dispositivo", "Random: Konachan": "Aleatorio: Konachan", "Usage: %1tool TOOL_NAME": "Uso: %1tool NOMBRE_HERRAMIENTA", "Use symbols for function keys": "Usar símbolos para teclas de función", "Lap": "Vuelta", "Active": "Activo", "Local Ollama model | %1": "Modelo local de Ollama | %1", "Shell command": "Comando de shell", "Hide clipboard images copied from sussy sources": "Ocultar imágenes del portapapeles copiadas de fuentes sospechosas", "Creativity": "Creatividad", "Select language": "Seleccionar idioma", "Usage": "Uso", "Show \"Locked\" text": "Mostrar texto \"Bloqueado\"", "Adjust the color temperature": "Ajustar la temperatura de color", "Move to front": "Mover al frente", "Pick a wallpaper": "Elegir un fondo de pantalla", "Logout": "Cerrar sesión", "Unknown": "Desconocido", "If you want to somehow use fingerprint unlock...": "Si quieres usar desbloqueo por huella digital de alguna manera...", "Precipitation": "Precipitación", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Cámbialo cuando quieras con /dark, /light, /wallpaper en el lanzador\nSi los colores del shell no cambian:\n 1. Abre la barra lateral derecha con Super+N\n 2. Haz clic en \"Recargar Hyprland y Quickshell\" en la esquina superior derecha", "Digital": "Digital", "Full": "Completo", "Microphone": "Micrófono", "Clock style": "Estilo de reloj", "Used for code and terminal": "Usada para código y terminal", "Prevents abrupt increments and restricts volume limit": "Previene incrementos abruptos y restringe el límite de volumen", "Download complete": "Descarga completa", "Connect to Wi-Fi": "Conectar a Wi-Fi", "Use adaptive alignment": "Usar alineación adaptativa", "System prompt": "Prompt de sistema", "Enjoy your empty sidebar...": "Disfruta tu barra lateral vacía...", "Fruit Salad": "Ensalada de frutas", "Overlay: Floating Image": "Superposición: Imagen flotante", "Auto styling with Gemini": "Estilo automático con Gemini", "Anti-flashbang": "Anti-destello", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Font family name (e.g., Readex Pro)": "Nombre de familia de fuente (p. ej., Readex Pro)", "Pause": "Pausar", "Interface": "Interfaz", "Number style": "Estilo de número", "Set the current API provider": "Establecer el proveedor de API actual", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Recuerda que en la mayoría de dispositivos siempre puedes mantener presionado el botón de encendido para forzar el apagado\nEsto solo hace que sea un poco más difícil que ocurran accidentes", "Weather Service": "Servicio meteorológico", "Vertical": "Vertical", "Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep": "Escribe algo aquí...\nUsa '-' para crear viñetas copiables, así:\n\nOveja traviesa\n- 4x Losa\n- 1x Bote\n- 4x Polvo de redstone\n- 1x Pistón pegajoso\n- 1x Varilla del End\n- 4x Repetidor de redstone\n- 1x Antorcha de redstone\n- 1x Oveja", "Edit": "Editar", "Add task": "Agregar tarea", "Depends on workspace": "Depende del espacio\nde trabajo", "Use old sine wave cookie implementation": "Usar implementación antigua de cookie en onda sinusoidal", "Darken screen": "Oscurecer pantalla", "Border": "Borde", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Esto generalmente es seguro y necesario para tu navegador y la barra lateral de IA de todas formas\nPrincipalmente útil para quienes usan bloqueo al inicio en lugar de un gestor de pantalla que lo hace (GDM, SDDM, etc.)", "Shell conflicts killer": "Eliminador de conflictos del shell", "End session": "Terminar sesión", "Best match": "Mejor coincidencia", "Online | Google's model\nPro-level intelligence at the speed and pricing of Flash.": "En línea | Modelo de Google\nInteligencia de nivel Pro a la velocidad y precio de Flash.", "Font family": "Familia de fuente", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "El de hentai | Gran cantidad, mucho NSFW, la calidad varía mucho", "Notes": "Notas", "Actions": "Acciones", "Task description": "Descripción de tarea", "Eye protection": "Protección ocular", "Image search": "Búsqueda de imágenes", "Saved": "Guardado", "Neutral": "Neutro", "Conflicts with the shell's notification implementation": "Conflicto con la implementación de notificaciones del shell", "Resume": "Reanudar", "Low warning": "Advertencia baja", "Task Manager": "Administrador de tareas", "Aligns the date and quote to left, center or right depending on its position on the screen.": "Alinea la fecha y la cita a la izquierda, al centro o a la derecha según su posición en la pantalla.", "Utilities & Tools": "Utilidades y herramientas", "Snip": "Recortar", "Time to empty:": "Tiempo para agotar:", "%1 | Right-click to configure": "%1 | Clic derecho para configurar", "Consider plugging in your device": "Considera conectar tu dispositivo", "%1 does not require an API key": "%1 no requiere una clave API", "Cancel wallpaper selection": "Cancelar selección de fondo de pantalla", "See fewer": "Ver menos", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Clic para alternar modo claro/oscuro\n(se aplica al elegir el fondo de pantalla)", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "Clic izq. para activar/desactivar\nClic der. para alternar tamaño\nDesplazar para cambiar posición", "Window": "Ventana", "Cancel": "Cancelar", "Unknown Artist": "Artista desconocido", "Press Super+G to open the overlay and pin the crosshair": "Presiona Super+G para abrir la superposición y anclar la mira", "Set the system prompt for the model.": "Establece el prompt de sistema para el modelo.", "Format": "Formato", "Unpin from taskbar": "Desanclar de la barra de tareas", "Charging:": "Cargando:", "System updates (Arch only)": "Actualizaciones del sistema (solo Arch)", "Line": "Línea", "Quote": "Cita", "Interface Language": "Idioma de la interfaz", "Terminal: Harmonize threshold": "Terminal: Umbral de armonización", "System uptime:": "Tiempo de actividad del sistema:", "Enable now": "Activar ahora", "Virtual Keyboard": "Teclado virtual", "Click to show": "Clic para mostrar", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Cuando está activado, mantiene el contenido de la barra lateral derecha cargado para reducir el retraso al abrir,\na costa de un uso constante de RAM de aproximadamente 15 MB. La importancia del retraso depende del rendimiento de tu sistema.\nUsar un kernel personalizado como linux-cachyos podría ayudar", "Secured": "Asegurado", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Precio**: gratis. Los datos se usan para entrenamiento.\n\n**Instrucciones**: Inicia sesión en tu cuenta de Google, permite que AI Studio cree un proyecto de Google Cloud o lo que solicite, regresa y haz clic en Obtener clave API", "Forget": "Olvidar", "Loaded the following system prompt\n\n---\n\n%1": "Se cargó el siguiente prompt de sistema\n\n---\n\n%1", "Attach a file. Only works with Gemini.": "Adjuntar un archivo. Solo funciona con Gemini.", "Temperature set to %1": "Temperatura establecida en %1", "Desktop": "Escritorio", "Preferred wallpaper zoom (%)": "Zoom preferido del fondo de pantalla (%)", "Set FPS limit": "Establecer límite de FPS", "Line-separated": "Separado por líneas", "Also unlock keyring": "También desbloquear el llavero", "Pick random from this folder": "Elegir aleatoriamente de esta carpeta", "Font width": "Ancho de fuente", "Enter a valid number": "Ingresa un número válido", "Medium": "Mediano", "Search with Google Lens": "Buscar con Google Lens", "Wi-Fi": "Wi-Fi", "Padding": "Relleno interior", "Layers": "Capas", "Float": "Flotante", "Hover to trigger": "Pasar el cursor para activar", "Other": "Otro", "Focusing": "Enfocando", "Session": "Sesión", "Timeout duration (if not defined by notification) (ms)": "Duración del tiempo de espera (si no está definido por la notificación) (ms)", "Local account": "Cuenta local", "Invalid arguments. Must provide `key` and `value`.": "Argumentos no válidos. Se deben proporcionar `key` y `value`.", "Closet": "De clóset", "Used for reading large blocks of text": "Usada para leer bloques grandes de texto", "Polling interval (m)": "Intervalo de consulta (m)", "Kill conflicting programs?": "¿Terminar programas en conflicto?", "Light": "Claro", "Translator": "Traductor", "Visibility": "Visibilidad", "Copy region (LMB) or annotate (RMB)": "Copiar región (clic izq.) o anotar (clic der.)", "Performance Profile toggle": "Alternar perfil de rendimiento", "Distro": "Distro", "Add": "Agregar", "Timeout (ms)": "Tiempo de espera (ms)", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Eso no funcionó. Consejos:\n- Revisa tus etiquetas y configuración NSFW\n- Si no tienes una etiqueta en mente, escribe un número de página", "Temperature\nChange with /temp VALUE": "Temperatura\nCámbiala con /temp VALOR", "Digits in the middle": "Dígitos en el centro", "Bold": "Negrita", "Pomodoro": "Pomodoro", "AI": "IA", "Content adjustment": "Ajuste de contenido", "No API key\nSet it with /key YOUR_API_KEY": "Sin clave API\nEstablécela con /key TU_CLAVE_API", "Hide sussy/anime wallpapers": "Ocultar fondos de pantalla sospechosos/anime", "Always": "Siempre", "Overlay: General": "Superposición: General", "Background": "Fondo", "Usage: %1save CHAT_NAME": "Uso: %1save NOMBRE_CHAT", "Terminal": "Terminal", "Left": "Izquierda", "Shutdown": "Apagado", "or": "o", "Discharging:": "Descargando:", "🌿 Long break: %1 minutes": "🌿 Descanso largo: %1 minutos", "Use Levenshtein distance-based algorithm instead of fuzzy": "Usar algoritmo basado en distancia de Levenshtein en lugar de búsqueda difusa", "Auto (System)": "Auto (Sistema)", "Use system file picker": "Usar selector de archivos del sistema", "Up %1": "Encendido %1", "%1 characters": "%1 caracteres", "Invalid API provider. Supported: \n-": "Proveedor de API no válido. Compatibles: \n-", "Humidity": "Humedad", "Use Hyprlock (instead of Quickshell)": "Usar Hyprlock (en lugar de Quickshell)", "Search wallpapers": "Buscar fondos de pantalla", "Total duration timeout (s)": "Tiempo de espera total (s)", "Check interval (mins)": "Intervalo de verificación (min)", "Rectangular selection": "Selección rectangular", "%1 • %2 tasks": "%1 • %2 tareas", "Terminal: Harmony (%)": "Terminal: Armonía (%)", "Title font": "Fuente de título", "Fully charged": "Completamente cargado", "+%1 notifications": "+%1 notificaciones", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Instrucciones**: Inicia sesión en tu cuenta de Mistral, ve a Claves en la barra lateral, haz clic en Crear nueva clave", "Unknown Application": "Aplicación desconocida", "Power Profile": "Perfil de energía", "Place at bottom": "Colocar en la parte inferior", "Extra wallpaper zoom (%)": "Zoom adicional del fondo de pantalla (%)", "Unfinished": "Sin terminar", "Silent": "Silencioso", "On-screen display": "Visualización en pantalla", "Clear chat history": "Limpiar historial de chat", "%1 notifications": "%1 notificaciones", "EasyEffects | Right-click to configure": "EasyEffects | Clic derecho para configurar", "Cookie clock settings": "Configuración del reloj Cookie", "Widget: Clock": "Widget: Reloj", "Keep system awake": "Mantener el sistema activo", "Right to left": "De derecha a izquierda", "Elements": "Elementos", "Off": "Desactivado", "Region selector (screen snipping/Google Lens)": "Selector de región (recorte de pantalla/Google Lens)", "Search": "Buscar", "Max allowed increase": "Incremento máximo\npermitido", "Total:": "Total:", "12h AM/PM": "12h AM/PM", "Thinking": "Pensando", "Random: osu! seasonal": "Aleatorio: estacional de osu!", "Constantly rotate": "Rotar constantemente", "Scale (%)": "Escala (%)", "Audio input": "Entrada de audio", "Conflicts with the shell's system tray implementation": "Conflicto con la implementación de la bandeja del sistema del shell", "General": "General", "Place the corners to trigger at the bottom": "Colocar las esquinas de activación en la parte inferior", "Google Lens": "Google Lens", "App": "Aplicación", "System sound": "Sonido del sistema", "No further instruction provided": "No se proporcionaron instrucciones adicionales", "Enable translator": "Activar traductor", "Auto": "Auto", "Switched to search mode. Continue with the user's request.": "Cambiado a modo búsqueda. Continúa con la solicitud del usuario.", "Screen round corner": "Esquina redondeada de pantalla", "Terminal: Foreground boost (%)": "Terminal: Impulso de primer plano (%)", "Show app icons": "Mostrar iconos de aplicaciones", "Full warning": "Advertencia de carga completa", "More volume settings": "Más ajustes de volumen", "Region height": "Altura de región", "Intelligence": "Inteligencia", "Unread indicator: show count": "Indicador de no leídos: mostrar conteo", "Last refresh: %1": "Última actualización: %1", "Launch on startup": "Iniciar al arranque", "Keyboard toggle": "Alternar teclado", "Bar position": "Posición de la barra", "Back": "Atrás", "Pick wallpaper image on your system": "Elegir imagen de fondo de pantalla de tu sistema", "Get the next page of results": "Obtener la siguiente página de resultados", "illogical-impulse Welcome": "Bienvenida de illogical-impulse", "Message the model... \"%1\" for commands": "Envía un mensaje al modelo... \"%1\" para comandos", "Make sure you have songrec installed": "Asegúrate de tener songrec instalado", "Commands": "Comandos", "Pinned": "Anclado", "Tint icons": "Colorear iconos", "Battery full": "Batería llena", "To Do:": "Por hacer:", "Tint app icons": "Colorear iconos de aplicaciones", "Weather": "Clima", "Usage: %1load CHAT_NAME": "Uso: %1load NOMBRE_CHAT", "Font size": "Tamaño de fuente", "System": "Sistema", "More Internet settings": "Más ajustes de Internet", "Utility buttons": "Botones de utilidad" } ================================================ FILE: dots/.config/quickshell/ii/translations/fr_FR.json ================================================ { "Material cookie": "Material cookie", "Style: Blurred": "Style : Flouté", "Unknown device": "Appareil inconnu", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Modifiez à tout moment avec /dark, /light, /wallpaper dans le lanceur\nSi les couleurs du shell ne changent pas :\n 1. Ouvrez le panneau latéral droit avec Super+N\n 2. Cliquez sur \"Recharger Hyprland & Quickshell\" dans le coin supérieur droit", "No pending tasks": "Aucune tâche en attente", "Positioning": "Positionnement", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Définir la température (aléatoire) du modèle. Les valeurs vont de 0 à 2 pour Gemini, de 0 à 1 pour les autres modèles. La valeur par défaut est 0,5.", "Critical warning": "Avertissement critique", "Unknown Artist": "Artiste inconnu", "Web search": "Recherche web", "Load prompt from %1": "Charger l'invite depuis %1", "Attach a file. Only works with Gemini.": "Joindre un fichier. Fonctionne uniquement avec Gemini.", "Reboot": "Redémarrer", "API key:\n\n```txt\n%1\n```": "Clé API :\n\n```txt\n%1\n```", "Pinned on startup": "Épinglé au démarrage", "Right": "Droite", "Reboot to firmware settings": "Redémarrer dans les paramètres du firmware", "Automatically hide": "Masquer automatiquement", "Waiting for response...": "En attente d'une réponse...", "To Do": "À faire", "Full": "Plein", "Select Language": "Sélectionner la langue", "Password": "Mot de passe", "Bluetooth devices": "Appareils Bluetooth", "Enable": "Activer", "Elements": "Éléments", "Start": "Démarrer", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Fond d'écran anime aléatoire (SFW) de Konachan\nL'image est sauvegardée dans ~/Pictures/Wallpapers", "The popular one | Best quantity, but quality can vary wildly": "Le plus populaire | Meilleure quantité, mais la qualité peut varier énormément", "System uptime:": "Temps de fonctionnement :", "illogical-impulse Welcome": "Bienvenue illogical-impulse", "Code saved to file": "Code sauvegardé dans le fichier", "Info": "Info", "Preferred wallpaper zoom (%)": "Zoom préféré du fond d'écran (%)", "Time": "Heure", "Help & Support": "Aide et support", "Bubble": "Bulle", "Large images | God tier quality, no NSFW.": "Grandes images | Qualité excellente, sans contenu NSFW.", "Dark": "Sombre", "Center clock": "Horloge centrée", "Search, calculate or run": "Rechercher, calculer ou exécuter", "Region height": "Hauteur de la région", "Load chat": "Charger la conversation", "Gives the model search capabilities (immediately)": "Donne au modèle des capacités de recherche (immédiatement)", "Depends on workspace": "Dépend de l'espace de travail", "Blurred style": "Style flouté", "Screenshot tool": "Outil de capture d'écran", "Enter password": "Entrer le mot de passe", "Search the web": "Rechercher sur le web", "Local only": "Local uniquement", "at": "à", "Math": "Calcul", "Consider plugging in your device": "Pensez à brancher votre appareil", "Workspaces shown": "Espaces de travail affichés", "Place the corners to trigger at the bottom": "Placer les coins de déclenchement en bas", "No API key\nSet it with /key YOUR_API_KEY": "Aucune clé API\nDéfinissez-la avec /key VOTRE_CLÉ_API", "Auto (System)": "Auto (Système)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Touches fléchées pour naviguer, Entrée pour sélectionner\nÉchap ou clic n'importe où pour annuler", "Critically low battery": "Batterie critique", "Open editor": "Ouvrir l'éditeur", "%1 notifications": "%1 notifications", "Region width": "Largeur de la région", "Max allowed increase": "Augmentation maximale autorisée", "Enable translator": "Activer le traducteur", "Constantly rotate": "Rotation constante", "Automatically suspends the system when battery is low": "Suspend automatiquement le système lorsque la batterie est faible", "Cannot find a GPS service. Using the fallback method instead.": "Impossible de trouver un service GPS. Utilisation de la méthode de secours.", "Qt apps": "Applications Qt", "Color picker": "Sélecteur de couleur", "Interface": "Interface", "Tint app icons": "Teinter les icônes d'application", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Sélectionner la langue de l'interface utilisateur.\n\"Auto\" utilisera les paramètres régionaux de votre système.", "Show quote": "Afficher la citation", "Local Ollama model | %1": "Modèle Ollama local | %1", "Show clock": "Afficher l'horloge", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Utilisation : %1superpaste NB_ENTRÉES[i]\nAjoutez i pour les images\nExemples :\n%1superpaste 4i pour les 4 dernières images\n%1superpaste 7 pour les 7 dernières entrées", "Audio": "Audio", "Corner style": "Style des coins", "No media": "Aucun média", "Unknown function call: %1": "Appel de fonction inconnu : %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "En ligne | Modèle de %1 | Fournit des réponses rapides, réactives et bien formatées. Inconvénients : pas très enclin à agir ; peut inventer des appels de fonctions inconnus", "Volume": "Volume", "Gamma": "Gamma", "Medium": "Moyen", "Copy code": "Copier le code", "Exceeded max allowed": "Dépassement du maximum autorisé", "Keep right sidebar loaded": "Garder le panneau latéral droit chargé", "Left": "Gauche", "High": "Élevé", "Rect": "Rect", "Lap": "Tour", "Clear": "Effacer", "Screen snip": "Capture d'écran", "Reset": "Réinitialiser", "Back": "Retour", "Dark/Light toggle": "Basculer sombre/clair", "12h am/pm": "12h am/pm", "Download complete": "Téléchargement terminé", "Enable blur": "Activer le flou", "Second hand": "Trotteuse", "Bar & screen": "Barre et écran", "Discharging:": "Décharge :", "Up %1": "Actif depuis %1", "Low": "Faible", "Hour hand": "Aiguille des heures", "Clear chat history": "Effacer l'historique de conversation", "Fruit Salad": "Salade de fruits", "%1 Safe Storage": "%1 Stockage sécurisé", "Hibernate": "Hibernation", "Delete": "Supprimer", "OK": "OK", "Settings": "Paramètres", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "C'est généralement sûr et nécessaire pour votre navigateur et la barre latérale IA\nSurtout utile pour ceux qui utilisent le verrouillage au démarrage au lieu d'un gestionnaire d'affichage (GDM, SDDM, etc.)", "Use Hyprlock (instead of Quickshell)": "Utiliser Hyprlock (au lieu de Quickshell)", "Crosshair code (in Valorant's format)": "Code du réticule (au format Valorant)", "Silent": "Silencieux", "Useless buttons": "Boutons inutiles", "Hover to reveal": "Survoler pour révéler", "Wallpaper & Colors": "Fond d'écran et couleurs", "Auto": "Auto", "Visibility": "Visibilité", "Shell & utilities": "Shell et utilitaires", "Hollow": "Creux", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "Utiliser le sélecteur de fichiers système\nClic droit pour en faire le comportement par défaut", "On-screen display": "Affichage à l'écran", "Dotfiles": "Dotfiles", "Search wallpapers": "Rechercher des fonds d'écran", "Mic toggle": "Basculer le micro", "Input": "Entrée", "Also unlock keyring": "Déverrouiller également le trousseau", "Configuration": "Configuration", "Keep system awake": "Empêcher la mise en veille", "Unknown command:": "Commande inconnue :", "Anime boorus": "Boorus anime", "To Do:": "À faire :", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Utilise Gemini pour catégoriser le fond d'écran puis choisit un préréglage basé sur celui-ci.\nVous devrez d'abord définir la clé API Gemini dans le panneau latéral gauche.\nLes images sont réduites pour les performances, mais par précaution,\nne sélectionnez pas de fonds d'écran contenant des informations sensibles.", "Bottom": "Bas", "Clear the current list of images": "Effacer la liste d'images actuelle", "Sunrise": "Lever du soleil", "Show app icons": "Afficher les icônes d'application", "Format": "Format", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Assurez-vous que votre lecteur prend en charge MPRIS\nou essayez de désactiver le filtrage des lecteurs dupliqués", "Pause": "Pause", "Desktop": "Bureau", "Conflicts with the shell's system tray implementation": "Conflit avec l'implémentation de la barre système du shell", "Your package manager is running": "Votre gestionnaire de paquets est en cours d'exécution", "Conflicts with the shell's notification implementation": "Conflit avec l'implémentation des notifications du shell", "Unknown Album": "Album inconnu", "Pick wallpaper image on your system": "Choisir une image de fond d'écran sur votre système", "Used:": "Utilisé :", "Cheat sheet": "Aide-mémoire", "Clock style": "Style d'horloge", "No audio source": "Aucune source audio", "Paired": "Jumelé", "Documentation": "Documentation", "No": "Non", "Pills": "Pilules", "Thought": "Pensée", "When this is off you'll have to click": "Lorsque cette option est désactivée, vous devrez cliquer", "Select output device": "Sélectionner le périphérique de sortie", "Logout": "Déconnexion", "Tip: Close a window with Super+Q": "Astuce : Fermer une fenêtre avec Super+Q", "Finished tasks will go here": "Les tâches terminées apparaîtront ici", "Terminal: Harmony (%)": "Terminal : Harmonie (%)", "Corner open": "Ouverture par coin", "Shell conflicts killer": "Suppresseur de conflits du shell", "Clean stuff | Excellent quality, no NSFW": "Contenu propre | Excellente qualité, sans NSFW", "Scroll to change volume": "Défiler pour changer le volume", "Wind": "Vent", "API key is set\nChange with /key YOUR_API_KEY": "Clé API définie\nModifiez avec /key VOTRE_CLÉ_API", "Neutral": "Neutre", "12h AM/PM": "12h AM/PM", "Number show delay when pressing Super (ms)": "Délai d'affichage des numéros à l'appui de Super (ms)", "Fill": "Remplissage", "Always show numbers": "Toujours afficher les numéros", "Dot": "Point", "Provider set to": "Fournisseur défini sur", "Unknown Title": "Titre inconnu", "Anime": "Anime", "Refreshing (manually triggered)": "Actualisation (déclenchée manuellement)", "Dock": "Dock", "Require password to power off/restart": "Exiger un mot de passe pour éteindre/redémarrer", "Line": "Ligne", "Weather": "Météo", "All-rounder | Good quality, decent quantity": "Polyvalent | Bonne qualité, quantité correcte", "Scale (%)": "Échelle (%)", "Copy": "Copier", "Usage": "Utilisation", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Tapez /key pour commencer avec les modèles en ligne\nCtrl+O pour agrandir le panneau latéral\nCtrl+P pour détacher le panneau latéral dans une fenêtre", "Set the tool to use for the model.": "Définir l'outil à utiliser pour le modèle.", "Disable tools": "Désactiver les outils", "Connect": "Connecter", "Allow NSFW": "Autoriser le NSFW", "Registration failed. Please inspect manually with the warp-cli command": "Inscription échouée. Veuillez inspecter manuellement avec la commande warp-cli", "Time to full:": "Temps pour charger complètement :", "Session": "Session", "Services": "Services", "Nothing here!": "Rien ici !", "Overview": "Aperçu", "Random: osu! seasonal": "Aléatoire : osu! saisonnier", "If you want to somehow use fingerprint unlock...": "Si vous souhaitez utiliser le déverrouillage par empreinte digitale...", "Minute hand": "Aiguille des minutes", "Notifications": "Notifications", "Enable if you want clocks to show seconds accurately": "Activer pour que les horloges affichent les secondes avec précision", "Timer": "Minuteur", "Quote settings": "Paramètres de citation", "System prompt": "Invite système", "Classic": "Classique", "Close": "Fermer", "Disconnect": "Déconnecter", "Go to source (%1)": "Aller à la source (%1)", "EasyEffects | Right-click to configure": "EasyEffects | Clic droit pour configurer", "Forget": "Oublier", "Output": "Sortie", "Date style": "Style de date", "System": "Système", "Usage: %1tool TOOL_NAME": "Utilisation : %1tool NOM_OUTIL", "Workspaces": "Espaces de travail", "Calendar": "Calendrier", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Instructions** : Connectez-vous à votre compte Mistral, allez dans Clés dans la barre latérale, cliquez sur Créer une nouvelle clé", "Volume limit": "Limite de volume", "Sunset": "Coucher du soleil", "Dial style": "Style du cadran", "Hi there! First things first...": "Bonjour ! Commençons par l'essentiel...", "Save chat to %1": "Sauvegarder la conversation dans %1", "Security": "Sécurité", "Total token count\nInput: %1\nOutput: %2": "Nombre total de tokens\nEntrée : %1\nSortie : %2", "Cancel wallpaper selection": "Annuler la sélection du fond d'écran", "Please charge!\nAutomatic suspend triggers at %1": "Veuillez brancher !\nLa suspension automatique se déclenche à %1", "Terminal: Harmonize threshold": "Terminal : Seuil d'harmonisation", "Be patient...": "Soyez patient...", "Utility buttons": "Boutons utilitaires", "Tonal Spot": "Tonal Spot", "Prevents abrupt increments and restricts volume limit": "Empêche les augmentations brusques et limite le volume", "Set the current API provider": "Définir le fournisseur API actuel", "Connection failed. Please inspect manually with the warp-cli command": "Connexion échouée. Veuillez inspecter manuellement avec la commande warp-cli", "Networking": "Réseau", "Tint icons": "Teinter les icônes", "Low battery": "Batterie faible", "Make icons pinned by default": "Épingler les icônes par défaut", "Get the next page of results": "Obtenir la page suivante de résultats", "Invalid API provider. Supported: \n-": "Fournisseur API invalide. Pris en charge : \n-", "Show \"Locked\" text": "Afficher le texte \"Verrouillé\"", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Tarification** : gratuit. La politique d'utilisation des données varie selon les paramètres de votre compte OpenRouter.\n\n**Instructions** : Connectez-vous à votre compte OpenRouter, allez dans Clés dans le menu en haut à droite, cliquez sur Créer une clé API", "Not visible to model": "Non visible par le modèle", "Lock screen": "Écran de verrouillage", "Save to Downloads": "Sauvegarder dans Téléchargements", "Expressive": "Expressif", "Suspend at": "Suspendre à", "Jump to current month": "Aller au mois actuel", "Bold": "Gras", "Waifus only | Excellent quality, limited quantity": "Waifus seulement | Excellente qualité, quantité limitée", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Cliquer pour basculer mode clair/sombre\n(appliqué lors du choix du fond d'écran)", "Visualize region": "Visualiser la région", "Quote": "Citation", "Sleep": "Veille", "Hit \"/\" to search": "Appuyer sur \"/\" pour rechercher", "Hug": "Câlin", "Report a Bug": "Signaler un bug", "Precipitation": "Précipitations", "Crosshair": "Réticule", "Model set to %1": "Modèle défini sur %1", "Rows": "Lignes", "Top": "Haut", "Long break": "Longue pause", "Superpaste": "Superpaste", "Screen round corner": "Arrondi des coins de l'écran", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "En ligne | Modèle de Google\nModèle plus récent, plus lent que son prédécesseur mais offrant des réponses de meilleure qualité", "Rainbow": "Arc-en-ciel", "Weeb": "Weeb", "Large language models": "Grands modèles de langage", "Online models disallowed\n\nControlled by `policies.ai` config option": "Modèles en ligne désactivés\n\nContrôlé par l'option de configuration `policies.ai`", "Policies": "Politiques", "Temperature must be between 0 and 2": "La température doit être comprise entre 0 et 2", "Automatic suspend": "Suspension automatique", "Extra wallpaper zoom (%)": "Zoom supplémentaire du fond d'écran (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | Clic droit pour configurer", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Tarification** : Niveau gratuit disponible avec des limites. Voir https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions** : Générez un jeton d'accès personnel GitHub avec la permission Models, puis définissez-le comme clé API ici\n\n**Note** : Pour l'utiliser, vous devrez définir le paramètre de température à 1", "Edit directory": "Modifier le répertoire", "Action": "Action", "Search": "Rechercher", "Tip: right-clicking a group\nalso expands it": "Astuce : un clic droit sur un groupe\nl'expand également", "Bar": "Barre", "Show regions of potential interest": "Afficher les régions d'intérêt potentiel", "Clipboard": "Presse-papiers", "Stopwatch": "Chronomètre", "Enter text to translate...": "Entrez le texte à traduire...", "App": "Application", "Sides": "Côtés", "No active player": "Aucun lecteur actif", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Toutes les options ne sont pas disponibles dans cette application. Vous devriez également vérifier le fichier de configuration en cliquant sur le bouton \"Fichier de config\" dans le coin supérieur gauche ou en ouvrant %1 manuellement.", "There might be a download in progress": "Un téléchargement est peut-être en cours", "Math result": "Résultat mathématique", "Fidelity": "Fidélité", "Prefixes": "Préfixes", "Terminal": "Terminal", "Incorrect password": "Mot de passe incorrect", "Line-separated": "Séparé par des lignes", "Always": "Toujours", "☕ Break: %1 minutes": "☕ Pause : %1 minutes", "Depends on sidebars": "Dépend des panneaux latéraux", "Tool set to: %1": "Outil défini sur : %1", "Save chat": "Sauvegarder la conversation", "Crosshair overlay": "Superposition du réticule", "Keybinds": "Raccourcis clavier", "Launch": "Lancer", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Peut être meilleur si vous faites beaucoup de fautes de frappe,\nmais les résultats peuvent être étranges et ne pas fonctionner avec les acronymes\n(ex : \"GIMP\" pourrait ne pas vous donner le programme de dessin)", "Choose model": "Choisir le modèle", "Base URL": "URL de base", "Float": "Flottant", "Wallpaper parallax": "Parallaxe du fond d'écran", "Invalid arguments. Must provide `command`.": "Arguments invalides. Vous devez fournir `command`.", "Fully charged": "Complètement chargé", "Earbang protection": "Protection contre les brusques hausses de volume", "Low warning": "Avertissement faible", "Advanced": "Avancé", "Scroll to change brightness": "Défiler pour changer la luminosité", "Loaded the following system prompt\n\n---\n\n%1": "L'invite système suivante a été chargée\n\n---\n\n%1", "Show next time": "Afficher la prochaine fois", "Current tool: %1\nSet it with %2tool TOOL": "Outil actuel : %1\nDéfinissez-le avec %2tool OUTIL", "Unread indicator: show count": "Indicateur non lu : afficher le nombre", "Press Super+G to toggle appearance": "Appuyez sur Super+G pour basculer l'apparence", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Cela n'a pas fonctionné. Conseils :\n- Vérifiez vos tags et paramètres NSFW\n- Si vous n'avez pas de tag en tête, tapez un numéro de page", "Dots": "Points", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Mixeur de volume", "Config file": "Fichier de configuration", "API key set for %1": "Clé API définie pour %1", "Online via %1 | %2's model": "En ligne via %1 | Modèle de %2", "Shell command": "Commande shell", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Ces régions peuvent être des images ou des parties de l'écran ayant une certaine délimitation.\nPas toujours précis.\nCela est fait avec un algorithme de traitement d'image exécuté localement, sans IA.", "Reload Hyprland & Quickshell": "Recharger Hyprland et Quickshell", "Resources": "Ressources", "Brightness": "Luminosité", "Unknown": "Inconnu", "Polling interval (ms)": "Intervalle de sondage (ms)", "Lock": "Verrouiller", "Thinking": "Réflexion", "Approve": "Approuver", "Unfinished": "Non terminé", "Random: Konachan": "Aléatoire : Konachan", "Connected": "Connecté", "Wallpaper safety enforced": "Sécurité du fond d'écran activée", "Invalid arguments. Must provide `key` and `value`.": "Arguments invalides. Vous devez fournir `key` et `value`.", "24h": "24h", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Permet d'ouvrir les panneaux latéraux en cliquant ou en survolant les coins de l'écran, quelle que soit la position de la barre", "Bar style": "Style de barre", "Load:": "Charge :", "Open file link": "Ouvrir le lien du fichier", "Ignored if terminal theming is not enabled": "Ignoré si le thème du terminal n'est pas activé", "Shutdown": "Éteindre", "Hour marks": "Marques des heures", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Fond saisonnier osu! aléatoire\nL'image est sauvegardée dans ~/Pictures/Wallpapers", "Online | Google's model\nFast, can perform searches for up-to-date information": "En ligne | Modèle de Google\nRapide, peut effectuer des recherches pour des informations actualisées", "Current model: %1\nSet it with %2model MODEL": "Modèle actuel : %1\nDéfinissez-le avec %2model MODÈLE", "Select input device": "Sélectionner le périphérique d'entrée", "Connect to Wi-Fi": "Se connecter au Wi-Fi", "... and %1 more": "... et %1 de plus", "Cookie clock settings": "Paramètres de l'horloge cookie", "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing": "Semble un peu plus doux et plus cohérent avec différents nombres de côtés,\nmais le morphing est moins impressionnant", "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.": "Fait tourner l'horloge en permanence. C'est extrêmement gourmand en ressources\n(attendez-vous à 50% d'utilisation sur Intel UHD Graphics) et donc peu pratique.", "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons": "Ne peut être activé qu'avec le style de cadran 'Points' ou 'Plein' pour des raisons esthétiques", "Can't be turned on when using 'Numbers' dial style for aesthetic reasons": "Ne peut pas être activé avec le style de cadran 'Chiffres' pour des raisons esthétiques", "Brightness and volume": "Luminosité et volume", "Choose file": "Choisir un fichier", "Invalid model. Supported: \n```": "Modèle invalide. Pris en charge : \n```", "Task Manager": "Gestionnaire de tâches", "Charging:": "En charge :", "Illegal increment": "Incrément illégal", "Total:": "Total :", "or": "ou", "Battery": "Batterie", "Timeout duration (if not defined by notification) (ms)": "Durée d'expiration (si non définie par la notification) (ms)", "Cancel": "Annuler", "Locked": "Verrouillé", "Temperature: %1": "Température : %1", "Hover to trigger": "Survoler pour déclencher", "Command rejected by user": "Commande rejetée par l'utilisateur", "User agent (for services that require it)": "Agent utilisateur (pour les services qui l'exigent)", "Saved to %1": "Sauvegardé dans %1", "Emojis": "Emojis", "Color generation": "Génération de couleurs", "Welcome app": "Application de bienvenue", "Humidity": "Humidité", "Page %1": "Page %1", "Feels like %1": "Ressenti %1", "Distro": "Distro", "Transparency": "Transparence", "%1 • %2 tasks": "%1 • %2 tâches", "Markdown test": "Test Markdown", "Invalid tool. Supported tools:\n- %1": "Outil invalide. Outils pris en charge :\n- %1", "No notifications": "Aucune notification", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Le hentai | Grande quantité, beaucoup de NSFW, la qualité varie énormément", "Bluetooth": "Bluetooth", "Resume": "Reprendre", "Work safety": "Sécurité au travail", "Temperature\nChange with /temp VALUE": "Température\nModifiez avec /temp VALEUR", "Terminal: Foreground boost (%)": "Terminal : Boost du premier plan (%)", "Night Light | Right-click to toggle Auto mode": "Veilleuse | Clic droit pour basculer le mode Auto", "Closet": "Garde-robe", "Yes": "Oui", "Columns": "Colonnes", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Pour définir une clé API, passez-la avec la commande %4\n\nPour afficher la clé, passez \"get\" avec la commande
\n\n### Pour %1 :\n\n**Lien** : %2\n\n%3", "Kill conflicting programs?": "Tuer les programmes en conflit ?", "For storing API keys and other sensitive information": "Pour stocker les clés API et autres informations sensibles", "Reject": "Rejeter", "Set API key": "Définir la clé API", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notes pour Zerochan :\n- Vous devez entrer une couleur\n- Définissez votre nom d'utilisateur zerochan dans l'option de configuration `sidebar.booru.zerochan.username`. Vous [pourriez être banni si vous ne le faites pas](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.) !", "Content": "Contenu", "Pomodoro": "Pomodoro", "Vertical": "Vertical", "Pick a wallpaper": "Choisir un fond d'écran", "Load chat from %1": "Charger la conversation depuis %1", "Launch on startup": "Lancer au démarrage", "Add": "Ajouter", "Style: general": "Style : général", "Use Levenshtein distance-based algorithm instead of fuzzy": "Utiliser l'algorithme basé sur la distance de Levenshtein au lieu du flou", "Shell & utilities theming must also be enabled": "Le thème shell et utilitaires doit également être activé", "Workspace": "Espace de travail", "Translator": "Traducteur", "Free:": "Libre :", "🌿 Long break: %1 minutes": "🌿 Longue pause : %1 minutes", "Value scroll": "Défilement de valeur", "Bar position": "Position de la barre", "Language": "Langue", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Point de terminaison API actuel : %1\nDéfinissez-le avec %2mode FOURNISSEUR", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Rappel : sur la plupart des appareils, on peut toujours maintenir le bouton d'alimentation pour forcer l'extinction\nCela rend juste un peu plus difficile les accidents", "AI": "IA", "Task description": "Description de la tâche", "Add task": "Ajouter une tâche", "Donate": "Faire un don", "Disable NSFW content": "Désactiver le contenu NSFW", "Set the system prompt for the model.": "Définir l'invite système pour le modèle.", "Done": "Terminé", "Focus": "Focus", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "Ouvrir le fichier de configuration du shell.\nSi le bouton ne fonctionne pas ou ne s'ouvre pas dans votre éditeur préféré,\nvous pouvez ouvrir manuellement ~/.config/illogical-impulse/config.json", "View Markdown source": "Voir la source Markdown", "Border": "Bordure", "Temperature set to %1": "Température définie sur %1", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "En ligne | Modèle de Google\nLe modèle polyvalent de pointe de Google, excellant dans les tâches de codage et de raisonnement complexe.", "Message the model... \"%1\" for commands": "Envoyer un message au modèle... \"%1\" pour les commandes", "Translation goes here...": "La traduction apparaît ici...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Lorsqu'activé, garde le contenu du panneau latéral droit chargé pour réduire le délai d'ouverture,\nau coût d'environ 15 Mo de RAM constamment utilisée. L'importance du délai dépend des performances de votre système.\nUtiliser un noyau personnalisé comme linux-cachyos pourrait aider", "For desktop wallpapers | Good quality": "Pour les fonds d'écran bureau | Bonne qualité", "🔴 Focus: %1 minutes": "🔴 Focus : %1 minutes", "The current system prompt is\n\n---\n\n%1": "L'invite système actuelle est\n\n---\n\n%1", "About": "À propos", "Quick": "Rapide", "General": "Général", "UV Index": "Indice UV", "Force dark mode in terminal": "Forcer le mode sombre dans le terminal", "Drag or click a region • LMB: Copy • RMB: Edit": "Glisser ou cliquer sur une région • LMB : Copier • RMB : Modifier", "%1 characters": "%1 caractères", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Tarification** : gratuit. Données utilisées pour l'entraînement.\n\n**Instructions** : Connectez-vous à votre compte Google, autorisez AI Studio à créer un projet Google Cloud ou ce qu'il demande, revenez et cliquez sur Obtenir une clé API", "Monochrome": "Monochrome", "Details": "Détails", "Issues": "Problèmes", "Keyboard toggle": "Basculer le clavier", "Might look ass. Unsupported.": "L'apparence pourrait être mauvaise. Non pris en charge.", "Download": "Télécharger", "%1 does not require an API key": "%1 ne nécessite pas de clé API", "Style & wallpaper": "Style et fond d'écran", "Second precision": "Précision des secondes", "Group style": "Style de groupe", "Break": "Pause", "Run": "Exécuter", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Profitez-en ! Vous pouvez rouvrir l'application de bienvenue à tout moment avec Super+Shift+Alt+/. Pour ouvrir l'application de paramètres, appuyez sur Super+I", "Interface Language": "Langue de l'interface", "Game mode": "Mode jeu", "Usage: %1save CHAT_NAME": "Utilisation : %1save NOM_CONVERSATION", "Thin": "Fin", "Light": "Clair", "When not fullscreen": "Lorsqu'il n'est pas en plein écran", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Commandes, modifier les configs, rechercher.\nNécessite un tour supplémentaire pour passer en mode recherche si nécessaire", "Privacy Policy": "Politique de confidentialité", "Timeout (ms)": "Délai d'expiration (ms)", "Allow NSFW content": "Autoriser le contenu NSFW", "Edit": "Modifier", "Digits in the middle": "Chiffres au centre", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "En ligne | Modèle de Google\nUn modèle Gemini 2.5 Flash optimisé pour le rapport coût-efficacité et le débit élevé.", "Weather Service": "Service météo", "Background": "Arrière-plan", "Pick random from this folder": "Choisir aléatoirement dans ce dossier", "Pressure": "Pression", "Save": "Sauvegarder", "Run command": "Exécuter la commande", "Time to empty:": "Temps avant décharge :", "Place at bottom": "Placer en bas", "Switched to search mode. Continue with the user's request.": "Passé en mode recherche. Continuer avec la demande de l'utilisateur.", "Performance Profile toggle": "Basculer le profil de performance", "Sidebars": "Panneaux latéraux", "Usage: %1load CHAT_NAME": "Utilisation : %1load NOM_CONVERSATION", "Auto styling with Gemini": "Style automatique avec Gemini", "Simple digital": "Numérique simple", "No API key set for %1": "Aucune clé API définie pour %1", "Enter tags, or \"%1\" for commands": "Entrez des tags, ou \"%1\" pour les commandes", "%1 queries pending": "%1 requêtes en attente", "Discussions": "Discussions", "Tray": "Barre système", "Numbers": "Chiffres", "Intelligence": "Intelligence", "Open network portal": "Ouvrir le portail réseau", "No further instruction provided": "Aucune instruction supplémentaire fournie", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Langue non listée ou traductions incomplètes ?\nVous pouvez choisir de générer des traductions avec Gemini.\n1. Ouvrez le panneau latéral gauche avec Super+A, définissez le modèle sur Gemini (si ce n'est pas déjà le cas)\n2. Tapez /key, appuyez sur Entrée et suivez les instructions\n3. Tapez /key VOTRE_CLÉ_API\n4. Tapez le code locale de votre langue ci-dessous et appuyez sur Générer", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Code de locale, ex. fr_FR, de_DE, zh_CN...", "Select language": "Sélectionner la langue", "Generate translation with Gemini": "Générer la traduction avec Gemini", "Generating...\nDon't close this window!": "Génération en cours...\nNe fermez pas cette fenêtre !", "Generate\nTypically takes 2 minutes": "Générer\nPrend généralement 2 minutes", "Use system file picker": "Utiliser le sélecteur de fichiers système", "Wallpaper selector": "Sélecteur de fond d'écran", "but force at absolute corner": "mais forcer au coin absolu", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Lorsque l'option précédente est désactivée et celle-ci est activée,\nvous pouvez toujours survoler l'extrémité du coin pour ouvrir le panneau latéral,\net la zone restante peut être utilisée pour le défilement du volume/luminosité", "Copy path": "Copier le chemin", "Windows": "Fenêtres", "Regenerate": "Régénérer", "Microphone": "Microphone", "Unmuted": "Non silencieux", "System sound": "Son système", "Enable now": "Activer maintenant", "Night Light": "Veilleuse", "Show aim lines": "Afficher les lignes de visée", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Pourquoi c'est utile :\nPour les valeurs non nulles, cela ne se déclenche pas quand vous atteignez le\ncoin de l'écran le long du bord horizontal, mais se déclenchera\nquand vous le faites le long du bord vertical", "Please charge!\nAutomatic suspend triggers at %1%": "Veuillez brancher !\nLa suspension automatique se déclenche à %1%", "Example use case: eroge on one workspace, dark Discord window on another": "Exemple d'utilisation : jeu eroge sur un espace de travail, fenêtre Discord sombre sur un autre", "Couldn't recognize music": "Impossible de reconnaître la musique", "Automatic": "Automatique", "Hint target regions": "Indiquer les régions cibles", "Devices": "Appareils", "Eye protection": "Protection oculaire", "Japanese": "Japonais", "Layers": "Calques", "Listening...": "Écoute en cours...", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "LMB pour activer/désactiver\nRMB pour changer la taille\nDéfiler pour changer la position", "Identify Music": "Identifier la musique", "Quick toggles": "Bascules rapides", "Hide sussy/anime wallpapers": "Masquer les fonds d'écran douteux/anime", "Android": "Android", "Show": "Afficher", "Muted": "Silencieux", "Audio input | Right-click for volume mixer & device selector": "Entrée audio | Clic droit pour le mixeur de volume et le sélecteur de périphérique", "Region selector (screen snipping/Google Lens)": "Sélecteur de région (capture d'écran/Google Lens)", "Total duration timeout (s)": "Délai de durée totale (s)", "Music Recognition": "Reconnaissance musicale", "Night Light | Right-click to configure": "Veilleuse | Clic droit pour configurer", "Anti-flashbang (experimental)": "Anti-flashbang (expérimental)", "Digital clock settings": "Paramètres de l'horloge numérique", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Peuvent être des images ou des parties de l'écran ayant une certaine délimitation.\nPas toujours précis.\nCela est fait avec un algorithme de traitement d'image exécuté localement, sans IA.", "Polling interval (m)": "Intervalle de sondage (m)", "Inactive": "Inactif", "Authentication": "Authentification", "Full warning": "Avertissement complet", "Power Profile": "Profil d'alimentation", "Content region": "Région de contenu", "Internet": "Internet", "Record": "Enregistrer", "Circle selection": "Sélection en cercle", "Edit quick toggles": "Modifier les bascules rapides", "Virtual Keyboard": "Clavier virtuel", "Music Recognized": "Musique reconnue", "EasyEffects": "EasyEffects", "Make sure you have songrec installed": "Assurez-vous que songrec est installé", "Dark Mode": "Mode sombre", "No device": "Aucun appareil", "Animate time change": "Animer le changement d'heure", "It may take a few seconds to update": "La mise à jour peut prendre quelques secondes", "Polling interval (s)": "Intervalle de sondage (s)", "Perhaps what you're listening to is too niche": "Ce que vous écoutez est peut-être trop de niche", "Fahrenheit unit": "Unité Fahrenheit", "Sliders": "Curseurs", "Roman": "Romain", "Number style": "Style de numéro", "Intensity": "Intensité", "Google Lens": "Google Lens", "Circle": "Cercle", "Hide clipboard images copied from sussy sources": "Masquer les images du presse-papiers copiées depuis des sources douteuses", "Scroll to Bottom": "Défiler vers le bas", "Enabled": "Activé", "Nothing": "Rien", "Audio input": "Entrée audio", "with vertical offset": "avec décalage vertical", "Padding": "Rembourrage", "Please unplug the charger": "Veuillez débrancher le chargeur", "Show notifications": "Afficher les notifications", "Path copied": "Chemin copié", "On-screen keyboard": "Clavier à l'écran", "City name": "Nom de la ville", "Click to cycle through power profiles": "Cliquer pour parcourir les profils d'alimentation", "Recognize music | Right-click to toggle source": "Reconnaître la musique | Clic droit pour changer la source", "Use old sine wave cookie implementation": "Utiliser l'ancienne implémentation cookie en onde sinusoïdale", "Rectangular selection": "Sélection rectangulaire", "Audio output": "Sortie audio", "Applications": "Applications", "Circle to Search": "Cercle pour rechercher", "Audio output | Right-click for volume mixer & device selector": "Sortie audio | Clic droit pour le mixeur de volume et le sélecteur de périphérique", "Enable GPS based location": "Activer la localisation par GPS", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Vous devrez d'abord saisir votre clé API Gemini.\nTapez /key dans le panneau latéral pour les instructions.", "Sounds": "Sons", "Active": "Actif", "Keep awake": "Rester éveillé", "Auto,": "Auto,", "Normal": "Normal", "Force hover open at absolute corner": "Forcer l'ouverture au survol au coin absolu", "Open the shell config file\nAlternatively right-click to copy path": "Ouvrir le fichier de configuration du shell\nAlternativement, clic droit pour copier le chemin", "Recognize music": "Reconnaître la musique", "Stroke width": "Largeur du trait", "Use varying shapes for password characters": "Utiliser des formes variées pour les caractères du mot de passe", "Battery full": "Batterie pleine", "Pin": "Épingler", "Unpin": "Désépingler" } ================================================ FILE: dots/.config/quickshell/ii/translations/he_HE.json ================================================ { "Launch": "הפעל", "Columns": "עמודות", "Save": "שמור", "Temperature: %1": "טמפרטורה: %1", "Night Light | Right-click to toggle Auto mode": "אור לילה", "Silent": "השתק", "To Do": "תזכורת", "Action": "פקודות", "Search the web": "חיפוש באינטרנט", "Workspace": "שטח עבודה", "Desktop": "שולחן עבודה", "Settings": "הגדרות", "Math result": "תוצאה", "Calendar": "לוח שנה", "Run": "הפעל", "Cancel": "ביטול", "Search": "חיפוש", "Battery": "סוללה", "Weather": "מזג אוויר", "Brightness": "בהירות", "Clear": "נקה", "No notifications": "אין התראות", "No media": "אין מדיה", "Add task": "הוסף תזכורת", "Run command": "הפעל פקודה", "Game mode": "מצב משחק", "Reload Hyprland & Quickshell": "טען מחדש Hyprland ו-Quickshell", "Task description": "כותרת תזכורת", "%1 | Right-click to configure": "%1", "Done": "הושלם", "Keep system awake": "מנע שינה", "Search, calculate or run": "חפש, חשב או הרץ", "Copy": "העתק", "Rows": "שורות", "Session": "סשן", "Notifications": "התראות", "Unfinished": "לא הושלם", "Add": "הוסף", "Nothing here!": "אין תזכורות", "Elements": "אלמנטים", "Color picker": "בוחר צבעים", "Sleep": "שינה", "Transparency": "שקיפות", "Bluetooth": "בלוטות׳", "UV Index": "מדד קרינת UV", "Bar": "סרגל", "Format": "פורמט", "Select output device": "בחר התקן פלט", "Pressure": "לחץ", "Volume": "עוצמה", "Volume mixer": "הגדרות עוצמת שמע", "Interface": "ממשק", "Workspaces": "שטחי עבודה", "Dark": "כהה", "%1 notifications": "%1 התראות", "Reboot": "הפעל מחדש", "No": "לא", "Wind": "רוח", "Humidity": "לחות", "Select Language": "בחר שפה", "Copy code": "העתק קוד", "Allow NSFW": "הצג NSFW", "Shutdown": "כיבוי", "Translation goes here...": "תרגום יגיע לכאן...", "Polling interval (ms)": "מרווח סקר (מילישניות)", "System prompt": "פרומפט מערכת", "Base URL": "כתובת בסיס", "Always show numbers": "הצג מספרים תמיד", "Wallpaper parallax": "אפקט parallax לטפט", "illogical-impulse Welcome": "ברוך הבא ל-illogical-impulse", "Local only": "מקומי בלבד", "Dark/Light toggle": "מצב כהה/בהיר", "Screen snip": "צילום מסך", "Style & wallpaper": "סגנון וטפט", "Show app icons": "הצג אייקוני אפליקציות", "Useless buttons": "כפתורים מיותרים", "Scale (%)": "קנה מידה (%)", "Intelligence": "בינה מלאכותית", "Enable": "אפשר", "Random: Konachan": "אקראי: Konachan", "Yes": "כן", "Documentation": "תיעוד", "Unknown Artist": "אמן לא ידוע", "Help & Support": "עזרה ותמיכה", "Report a Bug": "דווח על באג", "Sunset": "שקיעה", "Weeb": "אנימה", "Workspaces shown": "מספר שטחי עבודה מוצגים", "Screenshot tool": "כלי צילום מסך", "Enter text to translate...": "הכנס טקסט לתרגום", "Unknown Album": "אלבום לא ידוע", "Hug": "מעוגל", "Scroll to change brightness": "גלול לשינוי בהירות", "Privacy Policy": "מדיניות פרטיות", "12h AM/PM": "12 שעות (AM/PM)", "No audio source": "אין מקור שמע", "Download": "הורד", "Prefixes": "קידומות", "Show next time": "הצג בפעם הבאה", "Lock": "נעילה", "Scroll to change volume": "גלול לשינוי עוצמת שמע", "Unknown": "לא ידוע", "Jump to current month": "קפוץ לחודש הנוכחי", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Terminal": "טרמינל", "About": "אודות", "Precipitation": "גשם", "Keybinds": "קיצורי מקשים", "Select input device": "בחר התקן קלט", "When not fullscreen": "כשלא במסך מלא", "Unknown Title": "כותרת לא ידועה", "Task Manager": "מנהל משימות", "System": "מערכת", "Choose file": "בחר קובץ", "Pinned on startup": "נעוץ בהפעלה", "Policies": "מדיניות", "View Markdown source": "הצג קובץ Markdown", "Sunrise": "זריחה", "Configuration": "הגדרות", "Overview": "סקירה כללית", "Translator": "מתרגם", "Finished tasks will go here": "משימות שהושלמו יוצגו כאן", "Mic toggle": "מיקרופון", "Light": "בהיר", "Advanced": "מתקדם", "Web search": "חיפוש באינטרנט", "12h am/pm": "12 שעות (am/pm)", "Services": "שירותים", "Donate": "תרום", "Resources": "משאבים", "Float": "צף", "Hibernate": "שינה עמוקה", "Visibility": "ראות", "Delete": "מחק", "Page %1": "עמוד %1", "Keyboard toggle": "מקלדת וירטואלית", "24h": "24 שעות", "Time": "שעה", "Clipboard": "לוח העתקה", "or": "או", "Edit": "ערוך", "Emojis": "אימוג'ים", "Shell & utilities": "יישומים וכלי מערכת", "Reboot to firmware settings": "הפעל מחדש להגדרות קושחה", "No API key set for %1": "לא הוגדרה מפתח API עבור %1", "Disable NSFW content": "הסתר תוכן NSFW", "Closet": "מוסתר", "Depends on sidebars": "תלוי בסרגלים צדדיים", "Invalid model. Supported: \n```": "מודל לא חוקי. נתמכים: \n```", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "הקלד /key לשימוש במודלים מקוונים\nCtrl+O לפתיחת סרגל צד\nCtrl+P להפרדה לחלון", "Not visible to model": "לא נראה למודל", "Local Ollama model | %1": "מודל Ollama מקומי | %1", "Open file link": "פתח קישור לקובץ", "Cheat sheet": "דף עזר", "Allow NSFW content": "אפשר תוכן NSFW", "%1 characters": "%1 תווים", "Model set to %1": "מודל הוגדר ל-%1", "Be patient...": "נא להמתין...", "User agent (for services that require it)": "User agent (לשירותים שדורשים זאת)", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "נקודת קצה API נוכחית: %1\nהשתמש ב-%2mode לשנות", "For desktop wallpapers | Good quality": "לטפטים | איכות טובה", "For storing API keys and other sensitive information": "לשמירת מפתחות API ומידע רגיש", "Your package manager is running": "מנהל החבילות פועל", "Provider set to": "ספק הוגדר ל", "Color generation": "יצירת צבעים", "Temperature must be between 0 and 2": "ערך הטמפרטורה חייב להיות בין 0 ל-2", "Earbang protection": "הגנת אוזניים", "Current model: %1\nSet it with %2model MODEL": "מודל נוכחי: %1\nשנה עם %2model", "Waifus only | Excellent quality, limited quantity": "וואייפוס בלבד | איכות מעולה, כמות מוגבלת", "Save to Downloads": "שמור בהורדות", "Thinking": "חושב...", "On-screen display": "תצוגת מסך", "%1 • %2 tasks": "%1 • %2 תזכורות", "Qt apps": "יישומי Qt", "The popular one | Best quantity, but quality can vary wildly": "הפופולרי | כמות גדולה, איכות משתנה", "Set the system prompt for the model.": "הגדר פרומפט מערכת למודל", "Markdown test": "בדיקת Markdown", "Prevents abrupt increments and restricts volume limit": "מונע עליות חדות ומגביל עוצמת קול", "Preferred wallpaper zoom (%)": "זום טפט מועדף (%)", "Depends on workspace": "תלוי במקום העבודה", "Pick wallpaper image on your system": "בחר טפט מהמחשב", "Invalid API provider. Supported: \n-": "ספק API לא חוקי. נתמכים: \n-", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "הנטאי | כמות רבה, NSFW, איכות משתנה", "Saved to %1": "נשמר ל-%1", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "הגדר טמפרטורה (אקראיות) למודל. ערכים 0–2 ל-Gemini, 0–1 לאחרים. ברירת מחדל 0.5", "Close": "סגור", "Tint icons": "אייקונים בגוון", "Total token count\nInput: %1\nOutput: %2": "סך כל הטוקנים\nקלט: %1\nפלט: %2", "... and %1 more": "... ועוד %1", "Lap": "הקפה", "%1 does not require an API key": "%1 אינו דורש מפתח API", "To Do:": "רשימת משימות:", "Timer": "טיימר", "AI": "בינה מלאכותית", "Clear the current list of images": "נקה את רשימת התמונות הנוכחית", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "כדי להגדיר מפתח API, העבר אותו עם הפקודה %4\n\nכדי להציג את המפתח, העבר \"get\" עם הפקודה
\n\n### עבור %1:\n\n**קישור**: %2\n\n%3", "Time to full:": "זמן עד טעינה מלאה:", "illogical-impulse": "illogical-impulse", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "מקוון | מודל של %1 | מספק תשובות מהירות, מגיבות ומעוצבות היטב. חסרונות: לא תמיד יוזם פעולות; עלול להמציא קריאות פונקציה לא מוכרות", "Refreshing (manually triggered)": "מרענן (הופעל ידנית)", "Kill conflicting programs?": "להפסיק תוכנות מתנגשות?", "System uptime:": "משך זמן פעילות המערכת:", "Monochrome": "מונוכרום", "Issues": "בעיות", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "מקוון | מודל של גוגל\nמודל חדש יותר, איטי יותר מקודמו אך אמור לספק תשובות איכותיות יותר", "Temperature set to %1": "הטמפרטורה הוגדרה ל-%1", "Fruit Salad": "סלט פירות", "No pending tasks": "אין משימות ממתינות", "Vertical": "אנכי", "Set the tool to use for the model.": "הגדר את הכלי לשימוש עבור המודל.", "Cannot find a GPS service. Using the fallback method instead.": "לא נמצא שירות GPS. משתמש בשיטה חלופית.", "API key:\n\n```txt\n%1\n```": "מפתח API:\n\n```txt\n%1\n```", "Large images | God tier quality, no NSFW.": "תמונות גדולות | איכות גבוהה מאוד, ללא NSFW.", "Language": "שפה", "Weather Service": "שירות מזג אוויר", "Hover to reveal": "העבר עכבר לחשיפה", "Drag or click a region • LMB: Copy • RMB: Edit": "גרור או לחץ על אזור • לחצן שמאלי: העתק • לחצן ימני: ערוך", "Enter password": "הזן סיסמה", "Switched to search mode. Continue with the user's request.": "הועבר למצב חיפוש. המשך בבקשת המשתמש.", "Save chat to %1": "שמור שיחה ל-%1", "Current tool: %1\nSet it with %2tool TOOL": "הכלי הנוכחי: %1\nהגדר אותו עם %2tool TOOL", "The current system prompt is\n\n---\n\n%1": "הפרומפט הנוכחי של המערכת הוא\n\n---\n\n%1", "Save chat": "שמור שיחה", "Conflicts with the shell's notification implementation": "מתנגש עם מימוש ההתראות של ה-shell", "Critically low battery": "סוללה נמוכה מאוד", "Shell conflicts killer": "מנטרל התנגשויות shell", "Number show delay when pressing Super (ms)": "השהיית הצגת מספרים בלחיצת Super (מילישניות)", "Start": "התחל", "Config file": "קובץ הגדרות", "☕ Break: %1 minutes": "☕ הפסקה: %1 דקות", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". הערות ל-Zerochan:\n- חובה להזין צבע\n- הגדר את שם המשתמש שלך ב-zerochan באופציית הקונפיג `sidebar.booru.zerochan.username`. ייתכן שתחסם אם לא תעשה זאת!", "Used:": "בשימוש:", "Hi there! First things first...": "שלום! קודם כל...", "Registration failed. Please inspect manually with the warp-cli command": "ההרשמה נכשלה. נא לבדוק ידנית עם הפקודה warp-cli", "Usage": "שימוש", "Medium": "בינוני", "Enter tags, or \"%1\" for commands": "הזן תגיות, או \"%1\" לפקודות", "Auto (System)": "אוטומטי (מערכת)", "Content": "תוכן", "Performance Profile toggle": "החלף פרופיל ביצועים", "Low battery": "סוללה חלשה", "Region height": "גובה אזור", "Invalid tool. Supported tools:\n- %1": "כלי לא חוקי. כלים נתמכים:\n- %1", "API key is set\nChange with /key YOUR_API_KEY": "מפתח API מוגדר\nשנה עם /key YOUR_API_KEY", "Shell & utilities theming must also be enabled": "יש להפעיל גם ערכת נושא ליישומי מערכת", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "טפט אנימה אקראי מ-Konachan\nהתמונה נשמרת ב-~/Pictures/Wallpapers", "Automatic suspend": "השהיה אוטומטית", "Gives the model search capabilities (immediately)": "נותן למודל יכולות חיפוש (מידית)", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "מקוון | מודל מתקדם של גוגל שמצטיין בקידוד ובמשימות חשיבה מורכבות.", "Consider plugging in your device": "שקול לחבר את המכשיר לטעינה", "Show regions of potential interest": "הצג אזורים בעלי עניין פוטנציאלי", "No further instruction provided": "לא ניתנה הנחיה נוספת", "Dock": "מעגן", "Loaded the following system prompt\n\n---\n\n%1": "נטען הפרומפט הבא של המערכת\n\n---\n\n%1", "Always": "תמיד", "Input": "קלט", "Expressive": "מביע", "Audio": "שמע", "Usage: %1tool TOOL_NAME": "שימוש: %1tool TOOL_NAME", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**מחיר**: חינם. מדיניות השימוש בנתונים משתנה לפי הגדרות החשבון שלך ב-OpenRouter.\n\n**הוראות**: התחבר לחשבון OpenRouter, עבור ל-Keys בתפריט העליון, לחץ על Create API Key", "Auto": "אוטומטי", "Sidebars": "סרגלים צדדיים", "Stopwatch": "סטופר", "Distro": "הפצה", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "פתח את קובץ הקונפיג של ה-shell.\nאם הכפתור לא עובד או לא נפתח בעורך המועדף עליך, תוכל לפתוח ידנית את ~/.config/illogical-impulse/config.json", "🌿 Long break: %1 minutes": "🌿 הפסקה ארוכה: %1 דקות", "Max allowed increase": "העלייה המרבית המותרת", "Conflicts with the shell's system tray implementation": "מתנגש עם מימוש מגש המערכת של ה-shell", "Time to empty:": "זמן עד התרוקנות:", "Discharging:": "בהתרוקנות:", "Pomodoro": "פומודורו", "API key set for %1": "מפתח API הוגדר עבור %1", "%1 queries pending": "%1 שאילתות ממתינות", "Reset": "איפוס", "Automatically hide": "הסתר אוטומטית", "Might look ass. Unsupported.": "עשוי להיראות גרוע. לא נתמך.", "Anime": "אנימה", "Online models disallowed\n\nControlled by `policies.ai` config option": "מודלים מקוונים אינם מותרים\n\nנשלט על ידי אפשרות הקונפיג `policies.ai`", "Thought": "מחשבה", "GitHub": "גיטהאב", "Clear chat history": "נקה היסטוריית שיחות", "Resume": "המשך", "Set the current API provider": "הגדר את ספק ה-API הנוכחי", "Attach a file. Only works with Gemini.": "צרף קובץ. עובד רק עם Gemini.", "Critical warning": "אזהרה קריטית", "Connection failed. Please inspect manually with the warp-cli command": "החיבור נכשל. נא לבדוק ידנית עם הפקודה warp-cli", "Usage: %1load CHAT_NAME": "שימוש: %1load CHAT_NAME", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "מאפשר לפתוח סרגלים צדדיים בלחיצה או מעבר עכבר על פינות המסך ללא קשר למיקום הסרגל", "Anime boorus": "קולקציית אנימה", "Message the model... \"%1\" for commands": "שלח הודעה למודל... \"%1\" לפקודות", "OK": "אישור", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "כאשר מופעל, שומר את תוכן הסרגל הצדדי הימני טעון כדי להפחית את ההשהיה בפתיחה, במחיר של כ-15MB זיכרון RAM קבוע. משמעות ההשהיה תלויה בביצועי המערכת שלך. שימוש בליבה מותאמת אישית כמו linux-cachyos עשוי לעזור", "Rainbow": "קשת", "Invalid arguments. Must provide `key` and `value`.": "ארגומנטים לא חוקיים. חובה לספק `key` ו-`value`.", "Up %1": "מעלה %1", "Charging:": "בטעינה:", "Break": "הפסקה", "App": "אפליקציה", "Temperature\nChange with /temp VALUE": "טמפרטורה\nשנה עם /temp VALUE", "Unknown command:": "פקודה לא ידועה:", "There might be a download in progress": "ייתכן שיש הורדה בתהליך", "Go to source (%1)": "עבור למקור (%1)", "Networking": "רשת", "Incorrect password": "סיסמה שגויה", "Dotfiles": "קבצי הגדרות (dotfiles)", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**מחיר**: חינם. נתונים משמשים לאימון.\n\n**הוראות**: התחבר לחשבון Google, אפשר ל-AI Studio ליצור פרויקט Google Cloud או כל מה שיבקש, חזור ולחץ על Get API key", "Pause": "השהה", "Load:": "טען:", "Volume limit": "מגבלת עוצמה", "Focus": "פוקוס", "Download complete": "ההורדה הושלמה", "Welcome app": "אפליקציית קבלת פנים", "Cloudflare WARP": "Cloudflare WARP", "Low": "נמוך", "Tool set to: %1": "הכלי הוגדר ל-%1", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "עשוי להיות טוב יותר אם תעשה הרבה טעויות הקלדה, אך התוצאות עשויות להיות מוזרות ואולי לא יעבדו עם ראשי תיבות (למשל \"GIMP\" לא בהכרח ייתן את תוכנת הציור)", "Choose model": "בחר מודל", "Large language models": "מודלי שפה גדולים", "%1 Safe Storage": "%1 אחסון בטוח", "No API key\nSet it with /key YOUR_API_KEY": "אין מפתח API\nהגדר אותו עם /key YOUR_API_KEY", "Logout": "התנתק", "Clean stuff | Excellent quality, no NSFW": "תמונות נקיות | איכות מעולה, ללא NSFW", "Total:": "סך הכל:", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "פקודות, עריכת קונפיגים, חיפוש.\nנדרש סיבוב נוסף למעבר למצב חיפוש אם צריך", "Tonal Spot": "נקודת טון", "Output": "פלט", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**הוראות**: התחבר לחשבון Mistral, עבור ל-Keys בסרגל הצד, לחץ על Create new key", "Unknown function call: %1": "קריאת פונקציה לא ידועה: %1", "Interface Language": "שפת ממשק", "Fully charged": "טעון במלואו", "Free:": "פנוי:", "Online via %1 | %2's model": "מקוון דרך %1 | מודל של %2", "Neutral": "נייטרלי", "Usage: %1save CHAT_NAME": "שימוש: %1save CHAT_NAME", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "מקוון | מודל Gemini 2.5 Flash של גוגל, מותאם ליעילות עלות ותפוקה גבוהה.", "Feels like %1": "מרגיש כמו %1", "Discussions": "דיונים", "Low warning": "אזהרה ברמה נמוכה", "Keep right sidebar loaded": "הסרגל הצדדי הימני תמיד יוצג", "Hover to trigger": "עבור עםְ העכבר להפעלה", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "תהנה! תוכל לפתוח מחדש את אפליקציית הברוכים הבאים בכל עת עם Super+Shift+Alt+/. כדי לפתוח את אפליקציית ההגדרות, לחץ Super+I", "Timeout (ms)": "פסק זמן (מילישניות)", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "אזורים כאלה יכולים להיות תמונות או חלקים מהמסך שיש בהם תוכן.\nייתכן שלא תמיד יהיה מדויק. זה נעשה עם אלגוריתם עיבוד תמונה מקומי וללא שימוש בבינה מלאכותית.", "Long break": "הפסקה ארוכה", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "מקשי החצים לניווט, Enter לבחירה\nEsc או לחיצה בכל מקום לביטול", "Load prompt from %1": "טען פרומפט מ-%1", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "זה לא עבד. טיפים:\n- בדוק את התגיות והגדרות NSFW\n- אם אין לך תגית, הקלד מספר עמוד", "Corner open": "פינה פתוחה", "Disable tools": "השבת כלים", "Reject": "דחה", "Visualize region": "הדמיית אזור", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**מחיר**: יש פרופיל חינם עם תקצב מוגבל. ראה https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**הוראות**: צור אסימון גישה אישי ב-GitHub עם הרשאת Models, ואז הגדר אותו כאן כמפתח API\n\n**הערה**: כדי להשתמש בזה תצטרך להגדיר את פרמטר הטמפרטורה ל-1", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "בחר את השפה לממשק המשתמש. \"אוטומטי\" ישתמש בשפת המערכת שלך.", "Automatically suspends the system when battery is low": "משהה אוטומטית את המערכת כאשר הסוללה נמוכה", "Use Levenshtein distance-based algorithm instead of fuzzy": "השתמש באלגוריתם מבוסס מרחק Levenshtein במקום חיפוש מטושטש", "Tray": "אפליקציות רקע", "Approve": "אשר", "Fidelity": "דיוק", "Load chat": "טען שיחה", "All-rounder | Good quality, decent quantity": "כללי | איכות טובה, כמות אדירה", "Info": "מידע", "Corner style": "סגנון פינות", "Online | Google's model\nFast, can perform searches for up-to-date information": "מקוון | מודל של גוגל\nמהיר, יכול לבצע חיפושים למידע עדכני", "Command rejected by user": "הפקודה נדחתה על ידי המשתמש", "Region width": "רוחב אזור", "High": "גבוה", "Tint app icons": "אייקוני אפליקציות בגוון", "Get the next page of results": "קבל את עמוד התוצאות הבא", "Invalid arguments. Must provide `command`.": "ארגומנטים לא חוקיים. חובה לספק `פקודה`.", "Please charge!\nAutomatic suspend triggers at %1": "נא להטעין!\nהשהיה אוטומטית תופעל ב-%1", "EasyEffects | Right-click to configure": "EasyEffects | קליק ימני להגדרות", "🔴 Focus: %1 minutes": "🔴 פוקוס: %1 דקות", "Set API key": "הגדר מפתח API", "Load chat from %1": "טען שיחה מ-%1", "Code saved to file": "הקוד נשמר לקובץ", "Show clock": "הצגת שעון", "Use the system file picker instead\nRight-click to make this the default behavior": "השתמש בבוחר הקבצים של המערכת במקום\nלחיצה ימנית תהפוך את התנהגות זו לברירת המחדל", "Hour marks": "סימוני שעה", "Unread indicator: show count": "מתריע על אי-קריאה: הצגת כמות", "Cookie clock settings": "הגדרות שעון עוגיה", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "לא כל האפשרויות נמצאות באפליקציה הזו. מומלץ לבדוק את קובץ ההגדרות על ידי לחיצה על הכפתור \"קובץ הגדרות\" הממוקם למעלה בפינה השמאלית. ניתן גם לפתוח את %1 באופן ידני.", "Border": "גבול", "Classic": "קלאסי", "Connected": "מחובר", "Hit \"/\" to search": "לחץ \"/\" לחיפוש", "Wallpaper & Colors": "רקע וצבעים", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "ניתן לשנות בכל עת עם /dark, /light, /wallpaper במפעיל\nאם צבעי ה-shell לא משתנים:\n 1. פתח את הסרגל הצדדי הימני עם Super+N\n 2. לחץ על \"טען מחדש Hyprland & Quickshell\" בפינה הימנית העליונה", "Launch on startup": "הפעל בעת הפעלה", "Terminal: Harmonize threshold": "טרמינל: Harmonize threshold", "Quick": "מהיר", "Pills": "גלולות", "Terminal: Harmony (%)": "טרמינל: הרמוניה (%)", "Bar position": "מיקום הסרגל", "Connect to Wi-Fi": "התחבר ל-WiFi", "Force dark mode in terminal": "כפה על מצב כהה בטרמינל", "Wallpaper safety enforced": "בטיחות הרקע נאכפה", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "לחץ לשינוי בין מצב בהיר/כהה\n(מיושם לאחר בחירת רקע)", "Dots": "Dots", "Minute hand": "מחוג הדקות", "Open editor": "פתח עורך", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "זה בדרך כלל בטוח ונדרש עבור הדפדפן שלך וסרגל הצד של הבינה המלאכותית.\nבעיקר שימושי עבור מי שמשתמש בנעילה בעת ההפעלה במקום מנהל תצוגה שעושה זאת (GDM, SDDM וכו').", "Security": "בטיחות", "Work safety": "בטיחות עבודה", "Bar & screen": "סרגל ומסך", "Crosshair overlay": "שכבת כוונת", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "וודא שהנגן שלך תומך ב-MPRIS\nאו נסה לכבות סינון נגנים כפולים", "Bottom": "מטה", "Press Super+G to toggle appearance": "לחץ סופר+G לתצוגה או הסתרה", "Thin": "דק", "Sides": "צדדים", "Hollow": "חלול", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "שימוש: %1superpaste NUM_OF_ENTRIES[i]\nהוסף i כאשר תרצה תמונות\nדוגמאות:\n%1superpaste 4i עבור 4 התמונות האחרונות\n%1superpaste 7 עבור 7 הערכים האחרונים", "Center clock": "מרכוז השעון", "Tip: Close a window with Super+Q": "טיפ: סגור חלון עם Super+Q", "Clock style": "סגנון השעון", "Right": "ימינה", "Exceeded max allowed": "חריגה מהמקסימום המותר", "Bold": "מודגש", "Group style": "סגנון קבוצה", "Numbers": "מספרים", "Disconnect": "התנתקות", "Ignored if terminal theming is not enabled": "מתעלם אם ערכת נושא לטרמינל אינה מופעלת", "Dial style": "סגנון חוגה", "Auto styling with Gemini": "עיצוב אוטומטי עם Gemini", "Screen round corner": "עיגול פינות מסך", "When this is off you'll have to click": "כאשר אפשרות זו כבויה, תצטרך ללחוץ", "Paired": "מחובר", "Details": "פרטים", "Crosshair code (in Valorant's format)": "קוד כוונת (בפורמט של Valorant)", "Positioning": "מיקום", "Also unlock keyring": "פתח גם את שרשרת המפתחות (keyring)", "Make icons pinned by default": "הצמד אייקונים כברירת מחדל", "Background": "רקע", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "רקע osu! עונתי רנדומלי\nהתמונה נשמרת ב- ~/Pictures/Wallpapers", "Cancel wallpaper selection": "בטל בחירת רקע", "Enable if you want clocks to show seconds accurately": "הפעל כדי שהשעון יראה שניות במדויק", "at": "ב", "Quote settings": "הגדרות ציטוט", "Timeout duration (if not defined by notification) (ms)": "משך זמן (אם לא מוגדר על ידי ההתראה) (באלפיות השנייה)", "Connect": "התחבר", "Digits in the middle": "ספרות באמצע", "Rect": "מלבן", "Unknown device": "מכשיר לא ידוע", "Back": "אחורה", "General": "כללי", "Bluetooth devices": "מכשירי Bluetooth", "Bubble": "בועה", "Hour hand": "מחוג השעות", "Enable translator": "הפעלת מתרגם", "Utility buttons": "כפתורי תועלת", "Value scroll": "ערך גלילה", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "זכור כי ברוב המכשירים תמיד ניתן להחזיק את כפתור ההפעלה כדי לכבות בכוח\nזה רק מקשה מעט על טעויות מקריות לקרות", "Material cookie": "עוגיה", "Locked": "נעול", "Left": "שמאלה", "Place at bottom": "מקם למטה", "Lock screen": "מסך נעילה", "Show \"Locked\" text": "הצג את הטקסט \"נעול\"", "Open network portal": "פתח את פורטל הרשת", "No active player": "אין נגן פעיל", "Simple digital": "דיגיטלי פשוט", "Tip: right-clicking a group\nalso expands it": "טיפ: לחיצה ימנית על קבוצה\nמרחיבה אותה גם כן", "Pick random from this folder": "בחר באקראיות מהתיקייה", "Illegal increment": "הוספה לא חוקית", "Style: Blurred": "סגנון: חיבור", "Full": "מלא", "Style: general": "סגנון: כללי", "Use Hyprlock (instead of Quickshell)": "השתמש ב-Hyprlock (במקום ב-Quickshell)", "Top": "מעלה", "Random: osu! seasonal": "רנדומלי: osu! עונתי", "Require password to power off/restart": "דרוש סיסמה לכיבוי/הפעלה מחדש", "Fill": "מלא", "Constantly rotate": "הסתובב לעד", "Forget": "שכח", "Pick a wallpaper": "בחירת רקע", "Show quote": "הצג ציטוט", "Line-separated": "הפרדת קו", "Shell command": "פקודת של (shell)", "Extra wallpaper zoom (%)": "הגדלת רקע (%)", "Search wallpapers": "חיפוש רקעים", "Bar style": "סגנון הסרגל", "Dot": "נקודה", "Second precision": "דיוק בשנייה", "Line": "קו", "Place the corners to trigger at the bottom": "מקם את הפינות להפעלה בתחתית", "Second hand": "מחוג השניות", "Math": "מתמטיקה", "Edit directory": "ערוך תיקייה", "Password": "סיסמה", "Terminal: Foreground boost (%)": "Terminal: Foreground boost (%)", "If you want to somehow use fingerprint unlock...": "אם תרצה איכשהו לפתוח בעזרת טביעת אצבע...", "Superpaste": "Superpaste", "Date style": "סגנון תאריך", "Enable blur": "הפעל טשטוש", "Brightness and volume": "בהירות ועוצמת שמע", "Quote": "ציטוט", "Mo": "Mo/*keep*/", "Su": "Su/*keep*/", "Sa": "Sa/*keep*/", "Fr": "Fr/*keep*/", "Tu": "Tu/*keep*/", "Th": "Th/*keep*/", "We": "We/*keep*/" } ================================================ FILE: dots/.config/quickshell/ii/translations/id_ID.json ================================================ { "Material cookie": "Material cookie", "Style: Blurred": "Gaya: Buram", "Unknown device": "Perangkat tidak dikenal", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Ubah kapan saja dengan /dark, /light, /wallpaper di peluncur\nJika warna shell tidak berubah:\n 1. Buka sidebar kanan dengan Super+N\n 2. Klik \"Muat Ulang Hyprland & Quickshell\" di pojok kanan atas", "No pending tasks": "Tidak ada tugas tertunda", "Positioning": "Posisi", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Atur temperatur (keacakan) model. Nilai berkisar antara 0 hingga 2 untuk Gemini, 0 hingga 1 untuk model lain. Bawaan adalah 0.5.", "Critical warning": "Peringatan kritis", "Unknown Artist": "Artis tidak dikenal", "Web search": "Pencarian web", "Load prompt from %1": "Muat prompt dari %1", "Attach a file. Only works with Gemini.": "Lampirkan file. Hanya berfungsi dengan Gemini.", "Reboot": "Mulai ulang", "API key:\n\n```txt\n%1\n```": "Kunci API:\n\n```txt\n%1\n```", "Pinned on startup": "Disematkan saat startup", "Right": "Kanan", "Reboot to firmware settings": "Mulai ulang ke pengaturan firmware", "Automatically hide": "Sembunyikan otomatis", "Waiting for response...": "Menunggu respons...", "To Do": "Tugas", "Full": "Penuh", "Select Language": "Pilih Bahasa", "Password": "Kata sandi", "Bluetooth devices": "Perangkat Bluetooth", "Enable": "Aktifkan", "Elements": "Elemen", "Start": "Mulai", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Wallpaper Anime SFW acak dari Konachan\nGambar disimpan di ~/Pictures/Wallpapers", "The popular one | Best quantity, but quality can vary wildly": "Paling populer | Kuantitas tinggi, kualitas bisa sangat bervariasi", "System uptime:": "Waktu aktif sistem:", "illogical-impulse Welcome": "Selamat datang di illogical-impulse", "Code saved to file": "Kode disimpan ke file", "Info": "Info", "Preferred wallpaper zoom (%)": "Preferensi perbesaran wallpaper (%)", "Time": "Waktu", "Help & Support": "Bantuan & Dukungan", "Bubble": "Gelembung", "Large images | God tier quality, no NSFW.": "Gambar besar | Kualitas terbaik, tanpa NSFW.", "Dark": "Gelap", "Center clock": "Jam tengah", "Search, calculate or run": "Cari, hitung atau jalankan", "Region height": "Tinggi wilayah", "Load chat": "Muat obrolan", "Gives the model search capabilities (immediately)": "Memberikan kemampuan pencarian pada model (langsung)", "Depends on workspace": "Bergantung pada ruang kerja", "Blurred style": "Gaya buram", "Screenshot tool": "Alat tangkapan layar", "Enter password": "Masukkan kata sandi", "Search the web": "Cari di web", "Local only": "Lokal saja", "at": "di", "Math": "Matematika", "Consider plugging in your device": "Pertimbangkan untuk mengisi ulang daya perangkat Anda", "Workspaces shown": "Ruang kerja yang ditampilkan", "Place the corners to trigger at the bottom": "Tempatkan sudut pemicu di bawah", "No API key\nSet it with /key YOUR_API_KEY": "Tidak ada kunci API\nAtur dengan /key KUNCI_API_ANDA", "Auto (System)": "Otomatis (Sistem)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Tombol panah untuk navigasi, Enter untuk memilih\nEsc atau klik di mana saja untuk membatalkan", "Critically low battery": "Baterai sangat rendah", "Open editor": "Buka editor", "%1 notifications": "%1 notifikasi", "Region width": "Lebar wilayah", "Max allowed increase": "Peningkatan maksimal yang diizinkan", "Enable translator": "Aktifkan penerjemah", "Constantly rotate": "Putar terus-menerus", "Automatically suspends the system when battery is low": "Menangguhkan sistem secara otomatis ketika baterai rendah", "Cannot find a GPS service. Using the fallback method instead.": "Tidak dapat menemukan layanan GPS. Menggunakan metode cadangan.", "Qt apps": "Aplikasi Qt", "Color picker": "Pemilih warna", "Interface": "Antarmuka", "Tint app icons": "Warnai ikon aplikasi", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Pilih bahasa untuk antarmuka pengguna.\n\"Otomatis\" akan menggunakan pengaturan lokal sistem Anda.", "Show quote": "Tampilkan kutipan", "Local Ollama model | %1": "Model Ollama lokal | %1", "Show clock": "Tampilkan jam", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Penggunaan: %1superpaste JUMLAH_ENTRI[i]\nGunakan i jika Anda menginginkan gambar\nContoh:\n%1superpaste 4i untuk 4 gambar terakhir\n%1superpaste 7 untuk 7 entri terakhir", "Audio": "Audio", "Corner style": "Gaya sudut", "No media": "Tidak ada media", "Unknown function call: %1": "Panggilan fungsi tidak dikenal: %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Online | Model %1 | Memberikan jawaban cepat, responsif dan terformat dengan baik. Kekurangan: tidak terlalu bersemangat melakukan hal-hal; mungkin membuat panggilan fungsi yang tidak dikenal", "Volume": "Volume", "Medium": "Sedang", "Copy code": "Salin kode", "Exceeded max allowed": "Melebihi batas maksimal", "Keep right sidebar loaded": "Pertahankan sidebar kanan dimuat", "Left": "Kiri", "High": "Tinggi", "Rect": "Persegi", "Lap": "Putaran", "Clear": "Hapus", "Screen snip": "Cuplikan layar", "Reset": "Atur ulang", "Back": "Kembali", "Dark/Light toggle": "Toggle Gelap/Terang", "12h am/pm": "12j am/pm", "Download complete": "Unduhan selesai", "Enable blur": "Aktifkan blur", "Second hand": "Jarum detik", "Bar & screen": "Bilah & layar", "Discharging:": "Menguras:", "Up %1": "Naik %1", "Low": "Rendah", "Hour hand": "Jarum jam", "Clear chat history": "Hapus riwayat obrolan", "Fruit Salad": "Salad Buah", "%1 Safe Storage": "Penyimpanan Aman %1", "Hibernate": "Hibernasi", "Delete": "Hapus", "OK": "OK", "Settings": "Pengaturan", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Ini biasanya aman dan diperlukan untuk browser dan sidebar AI Anda\nSebagian besar berguna bagi mereka yang menggunakan kunci saat startup daripada display manager yang melakukannya (GDM, SDDM, dll.)", "Use Hyprlock (instead of Quickshell)": "Gunakan Hyprlock (menggantikan Quickshell)", "Crosshair code (in Valorant's format)": "Kode Crosshair (dalam format Valorant)", "Silent": "Sunyi", "Useless buttons": "Tombol tidak berguna", "Hover to reveal": "Arahkan untuk menampilkan", "Wallpaper & Colors": "Wallpaper & Warna", "Auto": "Otomatis", "Visibility": "Visibilitas", "Shell & utilities": "Shell & utilitas", "Hollow": "Berongga", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "Gunakan pemilih file sistem\nKlik kanan untuk menjadikan ini perilaku bawaan", "On-screen display": "Tampilan di layar", "Dotfiles": "Dotfiles", "Search wallpapers": "Cari wallpaper", "Mic toggle": "Toggle mikrofon", "Input": "Input", "Also unlock keyring": "Juga buka kunci keyring", "Configuration": "Konfigurasi", "Keep system awake": "Jaga sistem tetap terjaga", "Unknown command:": "Perintah tidak dikenal:", "Anime boorus": "Booru anime", "To Do:": "Tugas:", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Menggunakan Gemini untuk mengkategorikan wallpaper lalu memilih preset yang sesuai berdasarkan kategori.\nAnda perlu mengatur kunci API Gemini di sidebar kiri terlebih dahulu.\nGambar diperkecil untuk performa, tetapi agar tetap aman,\njangan pilih wallpaper dengan informasi sensitif.", "Bottom": "Bawah", "Clear the current list of images": "Hapus daftar gambar saat ini", "Sunrise": "Matahari terbit", "Show app icons": "Tampilkan ikon aplikasi", "Format": "Format", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Pastikan pemutar Anda memiliki dukungan MPRIS\natau coba matikan penyaringan pemutar duplikat", "Pause": "Jeda", "Desktop": "Desktop", "Conflicts with the shell's system tray implementation": "Konflik dengan implementasi system tray shell", "Your package manager is running": "Manajer paket Anda sedang berjalan", "Conflicts with the shell's notification implementation": "Konflik dengan implementasi notifikasi shell", "Unknown Album": "Album Tidak Dikenal", "Pick wallpaper image on your system": "Pilih gambar wallpaper di sistem Anda", "Used:": "Digunakan:", "Cheat sheet": "Lembar contekan", "Clock style": "Gaya jam", "No audio source": "Tidak ada sumber audio", "Paired": "Dipasangkan", "Documentation": "Dokumentasi", "No": "Tidak", "Pills": "Pil", "Thought": "Pemikiran", "When this is off you'll have to click": "Ketika ini dimatikan Anda harus mengklik", "Select output device": "Pilih perangkat output", "Logout": "Keluar", "Tip: Close a window with Super+Q": "Tip: Tutup jendela dengan Super+Q", "Finished tasks will go here": "Tugas terselesaikan akan ada di sini", "Terminal: Harmony (%)": "Terminal: Harmoni (%)", "Corner open": "Sudut pembuka", "Shell conflicts killer": "Pemecah konflik shell", "Clean stuff | Excellent quality, no NSFW": "Konten bersih | Kualitas sangat baik, tanpa NSFW", "Scroll to change volume": "Gulir untuk mengubah volume", "Wind": "Angin", "API key is set\nChange with /key YOUR_API_KEY": "Kunci API sudah diatur\nUbah dengan /key KUNCI_API_ANDA", "Neutral": "Netral", "12h AM/PM": "12j AM/PM", "Number show delay when pressing Super (ms)": "Tunda tampilan angka saat menekan Super (ms)", "Fill": "Isi", "Always show numbers": "Selalu tampilkan angka", "Dot": "Titik", "Provider set to": "Penyedia diatur ke", "Unknown Title": "Judul tidak dikenal", "Anime": "Anime", "Refreshing (manually triggered)": "Menyegarkan (dipicu secara manual)", "Dock": "Dok", "Require password to power off/restart": "Memerlukan kata sandi untuk mematikan/mulai ulang", "Line": "Garis", "Weather": "Cuaca", "All-rounder | Good quality, decent quantity": "Serbaguna | Kualitas bagus, kuantitas lumayan", "Scale (%)": "Skala (%)", "Copy": "Salin", "Usage": "Penggunaan", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Ketik /key untuk memulai dengan model online\nCtrl+O untuk memperluas sidebar\nCtrl+P untuk melepas sidebar ke jendela", "Set the tool to use for the model.": "Atur alat untuk digunakan oleh model.", "Disable tools": "Nonaktifkan alat", "Connect": "Hubungkan", "Allow NSFW": "Izinkan NSFW", "Registration failed. Please inspect manually with the warp-cli command": "Pendaftaran gagal. Silakan periksa secara manual dengan perintah warp-cli", "Time to full:": "Waktu hingga penuh:", "Session": "Sesi", "Services": "Layanan", "Nothing here!": "Di sini kosong!", "Overview": "Ikhtisar", "Random: osu! seasonal": "Acak: osu! musiman", "If you want to somehow use fingerprint unlock...": "Jika Anda ingin menggunakan buka kunci sidik jari...", "Minute hand": "Jarum menit", "Notifications": "Notifikasi", "Enable if you want clocks to show seconds accurately": "Aktifkan jika Anda ingin jam menampilkan detik dengan akurat", "Timer": "Timer", "Quote settings": "Pengaturan kutipan", "System prompt": "Prompt sistem", "Classic": "Klasik", "Close": "Tutup", "Disconnect": "Putuskan", "Go to source (%1)": "Pergi ke sumber (%1)", "EasyEffects | Right-click to configure": "EasyEffects | Klik kanan untuk mengonfigurasi", "Forget": "Lupakan", "Output": "Output", "Date style": "Gaya tanggal", "System": "Sistem", "Usage: %1tool TOOL_NAME": "Penggunaan: %1tool NAMA_ALAT", "Workspaces": "Ruang kerja", "Calendar": "Kalender", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Instruksi**: Masuk ke akun Mistral, buka Keys di sidebar, klik Create new key", "Volume limit": "Batas volume", "Sunset": "Matahari terbenam", "Dial style": "Gaya dial", "Hi there! First things first...": "Hai! Pertama-tama...", "Save chat to %1": "Simpan obrolan ke %1", "Security": "Keamanan", "Total token count\nInput: %1\nOutput: %2": "Total jumlah token\nInput: %1\nOutput: %2", "Cancel wallpaper selection": "Batalkan pemilihan wallpaper", "Please charge!\nAutomatic suspend triggers at %1": "Silakan isi ulang daya!\nPenangguhan otomatis terpicu di %1", "Terminal: Harmonize threshold": "Terminal: Ambang harmonisasi", "Be patient...": "Harap bersabar...", "Utility buttons": "Tombol utilitas", "Tonal Spot": "Titik Tonal", "Prevents abrupt increments and restricts volume limit": "Mencegah peningkatan mendadak dan membatasi batas volume", "Set the current API provider": "Atur penyedia API saat ini", "Connection failed. Please inspect manually with the warp-cli command": "Koneksi gagal. Silakan periksa secara manual dengan perintah warp-cli", "Networking": "Jaringan", "Tint icons": "Warnai ikon", "Low battery": "Baterai rendah", "Make icons pinned by default": "Jadikan ikon disematkan secara bawaan", "Get the next page of results": "Dapatkan halaman hasil berikutnya", "Invalid API provider. Supported: \n-": "Penyedia API tidak valid. Yang didukung: \n-", "Show \"Locked\" text": "Tampilkan teks \"Terkunci\"", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Harga**: gratis. Kebijakan penggunaan data bervariasi tergantung pada pengaturan akun OpenRouter Anda.\n\n**Instruksi**: Masuk ke akun OpenRouter, buka Keys di menu kanan atas, klik Create API Key", "Not visible to model": "Tidak terlihat oleh model", "Lock screen": "Kunci layar", "Save to Downloads": "Simpan ke Unduhan", "Expressive": "Ekspresif", "Suspend at": "Tangguhkan di", "Jump to current month": "Lompat ke bulan ini", "Bold": "Tebal", "Waifus only | Excellent quality, limited quantity": "Hanya waifu | Kualitas sangat baik, kuantitas terbatas", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Klik untuk beralih mode terang/gelap\n(diterapkan saat wallpaper dipilih)", "Visualize region": "Visualisasikan wilayah", "Quote": "Kutipan", "Sleep": "Tidur", "Hit \"/\" to search": "Tekan \"/\" untuk mencari", "Hug": "Peluk", "Report a Bug": "Laporkan Bug", "Precipitation": "Curah hujan", "Crosshair": "Crosshair", "Model set to %1": "Model diatur ke %1", "Rows": "Baris", "Top": "Atas", "Long break": "Istirahat panjang", "Superpaste": "Superpaste", "Screen round corner": "Lengkungan sudut layar", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Online | Model Google\nModel lebih baru yang lebih lambat dari pendahulunya tapi memberikan jawaban kualitas lebih tinggi", "Rainbow": "Pelangi", "Weeb": "Wibu", "Large language models": "Model bahasa besar", "Online models disallowed\n\nControlled by `policies.ai` config option": "Model online tidak diizinkan\n\nDikontrol oleh opsi konfigurasi `policies.ai`", "Policies": "Kebijakan", "Temperature must be between 0 and 2": "Temperatur harus antara 0 dan 2", "Automatic suspend": "Penangguhan otomatis", "Extra wallpaper zoom (%)": "Zoom wallpaper ekstra (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | Klik kanan untuk mengonfigurasi", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Harga**: Akses gratis tersedia dengan batasan rate. Lihat https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instruksi**: Buat token akses pribadi GitHub dengan izin Models, lalu atur sebagai kunci API di sini\n\n**Catatan**: Untuk menggunakan ini Anda harus mengatur parameter temperatur ke 1", "Edit directory": "Edit direktori", "Action": "Aksi", "Search": "Cari", "Tip: right-clicking a group\nalso expands it": "Tip: klik kanan grup\njuga memperluasnya", "Bar": "Bilah", "Show regions of potential interest": "Tampilkan wilayah yang berpotensi menarik", "Clipboard": "Papan klip", "Stopwatch": "Stopwatch", "Enter text to translate...": "Masukkan teks untuk diterjemahkan...", "App": "Aplikasi", "Sides": "Sisi", "No active player": "Tidak ada pemutar aktif", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Tidak semua opsi tersedia di aplikasi ini. Anda juga harus memeriksa file konfigurasi dengan menekan tombol \"File konfigurasi\" di pojok kiri atas atau membuka %1 secara manual.", "There might be a download in progress": "Mungkin ada unduhan yang sedang berlangsung", "Math result": "Hasil matematika", "Fidelity": "Fidelitas", "Prefixes": "Awalan", "Terminal": "Terminal", "Incorrect password": "Kata sandi salah", "Line-separated": "Dipisahkan baris", "Always": "Selalu", "☕ Break: %1 minutes": "☕ Istirahat: %1 menit", "Depends on sidebars": "Bergantung pada sidebar", "Tool set to: %1": "Alat diatur ke: %1", "Save chat": "Simpan obrolan", "Crosshair overlay": "Overlay Crosshair", "Keybinds": "Pintasan keyboard", "Launch": "Luncurkan", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Hasil bisa lebih baik jika Anda membuat banyak kesalahan ketik,\nnamun hasilnya dapat menjadi aneh dan mungkin tidak bekerja dengan akronim\n(misal: \"GIMP\" mungkin tidak merujuk ke aplikasi pengolah gambar)", "Choose model": "Pilih model", "Base URL": "URL Dasar", "Float": "Mengambang", "Wallpaper parallax": "Paralaks wallpaper", "Invalid arguments. Must provide `command`.": "Argumen tidak valid. Harus menyediakan `command`.", "Fully charged": "Terisi penuh", "Earbang protection": "Perlindungan suara keras", "Low warning": "Peringatan rendah", "Advanced": "Lanjutan", "Scroll to change brightness": "Gulir untuk mengubah kecerahan", "Loaded the following system prompt\n\n---\n\n%1": "Memuat prompt sistem berikut\n\n---\n\n%1", "Show next time": "Tampilkan lain kali", "Current tool: %1\nSet it with %2tool TOOL": "Alat saat ini: %1\nAtur dengan %2tool ALAT", "Unread indicator: show count": "Indikator belum dibaca: tampilkan jumlah", "Press Super+G to toggle appearance": "Tekan Super+G untuk toggle tampilan", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Itu tidak berhasil. Tips:\n- Periksa tag dan pengaturan NSFW\n- Jika Anda tidak memasukkan tag, masukkan nomor halaman", "Dots": "Titik", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Mixer volume", "Config file": "File konfigurasi", "API key set for %1": "Kunci API diatur untuk %1", "Online via %1 | %2's model": "Online via %1 | Model %2", "Shell command": "Perintah shell", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Wilayah tersebut bisa berupa gambar atau bagian layar yang memiliki kandungan.\nMungkin tidak selalu akurat.\nIni dilakukan dengan algoritma pemrosesan gambar yang berjalan lokal dan tidak ada AI yang digunakan.", "Reload Hyprland & Quickshell": "Muat Ulang Hyprland & Quickshell", "Resources": "Sumber daya", "Brightness": "Kecerahan", "Unknown": "Tidak dikenal", "Polling interval (ms)": "Interval polling (ms)", "Lock": "Kunci", "Thinking": "Berpikir", "Approve": "Setujui", "Unfinished": "Belum selesai", "Random: Konachan": "Acak: Konachan", "Connected": "Terhubung", "Wallpaper safety enforced": "Keamanan wallpaper diberlakukan", "Invalid arguments. Must provide `key` and `value`.": "Argumen tidak valid. Harus menyediakan `key` dan `value`.", "24h": "24j", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Memungkinkan Anda membuka sidebar dengan mengklik atau mengarahkan ke sudut layar terlepas dari posisi bilah", "Bar style": "Gaya bilah", "Load:": "Beban:", "Open file link": "Buka tautan file", "Ignored if terminal theming is not enabled": "Diabaikan jika tema terminal tidak diaktifkan", "Shutdown": "Matikan", "Hour marks": "Tanda jam", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Latar belakang musiman osu! acak\nGambar disimpan ke ~/Pictures/Wallpapers", "Online | Google's model\nFast, can perform searches for up-to-date information": "Online | Model Google\nCepat, dapat melakukan pencarian untuk informasi terkini", "Current model: %1\nSet it with %2model MODEL": "Model saat ini: %1\nAtur dengan %2model MODEL", "Select input device": "Pilih perangkat input", "Connect to Wi-Fi": "Hubungkan ke Wi-Fi", "... and %1 more": "... dan %1 lainnya", "Cookie clock settings": "Pengaturan jam cookie", "Brightness and volume": "Kecerahan dan volume", "Choose file": "Pilih file", "Invalid model. Supported: \n```": "Model tidak valid. Didukung: \n```", "Task Manager": "Manajer Tugas", "Charging:": "Mengisi:", "Illegal increment": "Peningkatan ilegal", "Total:": "Total:", "or": "atau", "Battery": "Baterai", "Timeout duration (if not defined by notification) (ms)": "Durasi waktu habis (jika tidak ditentukan oleh notifikasi) (ms)", "Cancel": "Batal", "Locked": "Terkunci", "Temperature: %1": "Temperatur: %1", "Hover to trigger": "Arahkan untuk memicu", "Command rejected by user": "Perintah ditolak oleh pengguna", "User agent (for services that require it)": "Agen pengguna (untuk layanan yang memerlukannya)", "Saved to %1": "Disimpan ke %1", "Emojis": "Emoji", "Color generation": "Generasi warna", "Welcome app": "Aplikasi selamat datang", "Humidity": "Kelembaban", "Page %1": "Halaman %1", "Feels like %1": "Terasa seperti %1", "Distro": "Distro", "Transparency": "Transparansi", "%1 • %2 tasks": "%1 • %2 tugas", "Markdown test": "Tes Markdown", "Invalid tool. Supported tools:\n- %1": "Alat tidak valid. Alat yang didukung:\n- %1", "No notifications": "Tidak ada notifikasi", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Yang hentai | Kuantitas besar, banyak NSFW, kualitas sangat bervariasi", "Bluetooth": "Bluetooth", "Resume": "Lanjutkan", "Work safety": "Keamanan kerja", "Temperature\nChange with /temp VALUE": "Temperatur\nUbah dengan /temp NILAI", "Terminal: Foreground boost (%)": "Terminal: Peningkatan latar depan (%)", "Night Light | Right-click to toggle Auto mode": "Cahaya Malam | Klik kanan untuk toggle mode Otomatis", "Closet": "Lemari", "Yes": "Ya", "Columns": "Kolom", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Untuk mengatur kunci API, gunakan perintah %4\n\nUntuk melihat kunci, gunakan \"get\" dengan perintah
\n\n### Untuk %1:\n\n**Tautan**: %2\n\n%3", "Kill conflicting programs?": "Matikan program yang berkonflik?", "For storing API keys and other sensitive information": "Untuk menyimpan kunci API dan informasi sensitif lainnya", "Reject": "Tolak", "Set API key": "Atur kunci API", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Catatan untuk Zerochan:\n- Anda harus memasukkan warna\n- Atur nama pengguna zerochan Anda di opsi konfigurasi `sidebar.booru.zerochan.username`. [Akses Anda mungkin akan ditangguhkan jika tidak dilakukan](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Content": "Konten", "Pomodoro": "Pomodoro", "Vertical": "Vertikal", "Pick a wallpaper": "Pilih wallpaper", "Load chat from %1": "Muat obrolan dari %1", "Launch on startup": "Luncurkan saat startup", "Add": "Tambah", "Style: general": "Gaya: umum", "Use Levenshtein distance-based algorithm instead of fuzzy": "Gunakan algoritma berbasis jarak Levenshtein daripada fuzzy", "Shell & utilities theming must also be enabled": "Tema shell & utilitas juga harus diaktifkan", "Workspace": "Ruang kerja", "Translator": "Penerjemah", "Free:": "Gratis:", "🌿 Long break: %1 minutes": "🌿 Istirahat panjang: %1 menit", "Value scroll": "Gulir nilai", "Bar position": "Posisi bilah", "Language": "Bahasa", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Endpoint API saat ini: %1\nAtur dengan %2mode PENYEDIA", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Ingat bahwa di sebagian besar perangkat Anda selalu dapat menahan tombol daya untuk memaksa mematikan\nIni hanya membuat sedikit lebih sulit untuk kecelakaan terjadi", "AI": "AI", "Task description": "Deskripsi tugas", "Add task": "Tambah tugas", "Donate": "Donasi", "Disable NSFW content": "Nonaktifkan konten NSFW", "Set the system prompt for the model.": "Atur prompt sistem untuk model.", "Done": "Selesai", "Focus": "Fokus", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "Buka file konfigurasi shell.\nJika tombol tidak berfungsi atau tidak terbuka di editor favorit Anda,\nAnda dapat membuka ~/.config/illogical-impulse/config.json secara manual", "View Markdown source": "Lihat sumber Markdown", "Border": "Bingkai", "Temperature set to %1": "Temperatur diatur ke %1", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Online | Model Google\nModel serbaguna canggih Google yang unggul dalam tugas coding dan penalaran kompleks.", "Message the model... \"%1\" for commands": "Kirim pesan ke model... \"%1\" untuk perintah", "Translation goes here...": "Terjemahan ada di sini...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Ketika diaktifkan membuat konten sidebar kanan tetap dimuat untuk mengurangi keterlambatan saat membuka,\ndengan penambahan penggunaan RAM konsisten sebanyak 15 MB. Signifikansi keterlambatan tergantung pada kinerja sistem Anda.\nMenggunakan kernel kustom seperti linux-cachyos mungkin dapat membantu", "For desktop wallpapers | Good quality": "Untuk wallpaper desktop | Kualitas bagus", "🔴 Focus: %1 minutes": "🔴 Fokus: %1 menit", "The current system prompt is\n\n---\n\n%1": "Prompt sistem saat ini adalah\n\n---\n\n%1", "About": "Tentang", "Quick": "Cepat", "General": "Umum", "UV Index": "Indeks UV", "Force dark mode in terminal": "Paksa mode gelap di terminal", "Drag or click a region • LMB: Copy • RMB: Edit": "Seret atau klik wilayah • Klik Kiri: Salin • Klik Kanan: Edit", "%1 characters": "%1 karakter", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Harga**: gratis. Data digunakan untuk pelatihan model.\n\n**Instruksi**: Masuk ke akun Google, izinkan AI Studio membuat proyek Google Cloud atau apa pun yang diminta, kembali dan klik Get API key", "Monochrome": "Monokrom", "Details": "Detail", "Issues": "Masalah", "Keyboard toggle": "Toggle keyboard", "Might look ass. Unsupported.": "Mungkin terlihat buruk. Tidak didukung.", "Download": "Unduh", "%1 does not require an API key": "%1 tidak memerlukan kunci API", "Style & wallpaper": "Gaya & wallpaper", "Second precision": "Presisi detik", "Group style": "Gaya grup", "Break": "Istirahat", "Run": "Jalankan", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Enjoy! Anda dapat membuka kembali aplikasi selamat datang kapan saja dengan Super+Shift+Alt+/. Untuk membuka aplikasi pengaturan, tekan Super+I", "Interface Language": "Bahasa Antarmuka", "Game mode": "Mode game", "Usage: %1save CHAT_NAME": "Penggunaan: %1save NAMA_OBROLAN", "Thin": "Tipis", "Light": "Terang", "When not fullscreen": "Ketika tidak layar penuh", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Perintah, edit konfigurasi, cari.\nMemerlukan giliran ekstra untuk beralih ke mode pencarian jika diperlukan", "Privacy Policy": "Kebijakan Privasi", "Timeout (ms)": "Waktu habis (ms)", "Allow NSFW content": "Izinkan konten NSFW", "Edit": "Edit", "Digits in the middle": "Angka di tengah", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Online | Model Google\nModel Gemini 2.5 Flash yang dioptimalkan untuk efisiensi biaya dan throughput tinggi.", "Never": "Tidak pernah", "Percentage": "Persentase", "Expressive colors": "Warna ekspresif", "No workspaces": "Tidak ada ruang kerja", "Screen corner actions": "Aksi sudut layar", "Random color generation": "Generasi warna acak", "Disable session lock": "Nonaktifkan kunci sesi", "Short break": "Istirahat pendek", "Colors": "Warna", "Monitors": "Monitor", "Fuzzy": "Fuzzy", "Discord": "Discord" } ================================================ FILE: dots/.config/quickshell/ii/translations/it_IT.json ================================================ { "Launch": "Avvia", "Columns": "Colonne", "Save": "Salva", "Temperature: %1": "Temperatura: %1", "Night Light | Right-click to toggle Auto mode": "Modalità notte", "Silent": "Silenzia", "To Do": "Promemoria", "Action": "Comandi", "Search the web": "Cerca sul web", "Workspace": "Spazio di lavoro", "Desktop": "Scrivania", "Settings": "Impostazioni", "Math result": "Risultato", "Calendar": "Calendario", "Run": "Esegui", "Cancel": "Cancella", "Search": "Cerca", "Battery": "Batteria", "Weather": "Meteo", "Brightness": "Luminosità", "Clear": "Cancella", "No notifications": "Nessuna notifica", "No media": "Non in riproduzione", "Add task": "Aggiungi promemoria", "Run command": "Esegui comando", "Game mode": "Modalità gioco", "Reload Hyprland & Quickshell": "Riavvia Hyprland e Quickshell", "Task description": "Titolo promemoria", "%1 | Right-click to configure": "%1", "Done": "Completati", "Keep system awake": "Mantieni schermo attivo", "Search, calculate or run": "Cerca, calcola o esegui", "Copy": "Copia", "Rows": "Righe", "Session": "Sessione", "Notifications": "Notifiche", "Unfinished": "Da completare", "Add": "Aggiungi", "Nothing here!": "Nessun promemoria", "Mo": "Lu", "Tu": "Ma", "We": "Me", "Th": "Gi", "Fr": "Ve", "Sa": "Sa", "Su": "Do", "Edit config": "Apri config.", "Center title": "Titolo centrato", "Elements": "Elementi", "Color picker": "Selettore colore", "Title bar": "Barra del titolo", "Sleep": "Sospendi", "Transparency": "Trasparenza", "Bluetooth": "Bluetooth", "UV Index": "Indice UV", "Bar": "Barra", "Format": "Formato", "Select output device": "Seleziona dispositivo di output", "Pressure": "Pressione", "Volume": "Volume", "Volume mixer": "Mixer volume", "Interface": "Interfaccia", "Workspaces": "Spazi di lavoro", "Dark": "Scuro", "%1 notifications": "%1 notifiche", "Reboot": "Riavvia", "No": "No", "Wind": "Vento", "Humidity": "Umidità", "Select Language": "Seleziona lingua", "Wallpaper": "Sfondo", "Copy code": "Copia codice", "Allow NSFW": "Mostra NSFW", "Colors & Wallpaper": "Sfondo e stile", "Shutdown": "Spegni", "Decorations & Effects": "Decorazioni e effetti", "Translation goes here...": "Traduzione", "Polling interval (ms)": "Intervallo di polling (ms)", "System prompt": "Prompt di sistema", "Base URL": "URL base", "Always show numbers": "Mostra numeri", "Wallpaper parallax": "Effetto parallasse sfondo", "Plain rectangle": "Semplice", "illogical-impulse Welcome": "Benvenuto su illogical-impulse", "Local only": "Solo locale", "Dark/Light toggle": "Modalità chiaro/scuro", "Screen snip": "Cattura schermo", "Style & wallpaper": "Sfondo e stile", "Show app icons": "Mostra icone app", "Useless buttons": "Tasti inutili", "Scale (%)": "Dimensione (%)", "Show background": "Mostra sfondo", "Intelligence": "AI", "Enable": "Abilita", "Borderless": "Senza bordi", "Random: Konachan": "Casuale: Konachan", "Yes": "Sì", "Documentation": "Documentazione", "Unknown Artist": "Artista sconosciuto", "Help & Support": "Aiuto e supporto", "Report a Bug": "Segnala un bug", "Sunset": "Tramonto", "Weeb": "Anime", "Shell windows": "Finestre", "Workspaces shown": "Numero spazi di lavoro", "Screenshot tool": "Cattura schermo", "Enter text to translate...": "Inserisci il testo qui", "Unknown Album": "Album sconosciuto", "Hug": "Stondato", "Scroll to change brightness": "Scorri per cambiare luminosità", "Privacy Policy": "Privacy policy", "12h AM/PM": "12 ore (AM/PM)", "Material palette": "Tavolozza dei colori", "No audio source": "Nessuna sorgente audio", "Download": "Scarica", "Prefixes": "Prefissi", "Show next time": "Mostra al prossimo avvio", "Lock": "Blocca", "Scroll to change volume": "Scorri per cambiare volume", "Unknown": "Sconosciuto", "Jump to current month": "Torna al mese corrente", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Terminal": "Terminale", "About": "Informazioni", "Precipitation": "Precipitazioni", "Keybinds": "Comandi", "Select input device": "Seleziona dispositivo di input", "When not fullscreen": "Tranne a schermo intero", "Unknown Title": "Titolo sconosciuto", "Task Manager": "Gestione attività", "System": "Sistema", "Choose file": "Scegli file", "Pinned on startup": "Fissa sullo schermo", "Policies": "Servizi", "View Markdown source": "Mostra Markdown", "Sunrise": "Alba", "Configuration": "Configurazione", "Overview": "Panoramica", "Translator": "Traduttore", "Finished tasks will go here": "Nessun promemoria", "Mic toggle": "Microfono", "Light": "Chiaro", "Advanced": "Avanzate", "Web search": "Cerca sul web", "12h am/pm": "12 ore (am/pm)", "Services": "Servizi", "Donate": "Supporta", "Resources": "Risorse", "Float": "Fluttuante", "Fake screen rounding": "Bordi curvi schermo", "Hibernate": "Iberna", "Visibility": "Visibilità", "Delete": "Elimina", "Style": "Stile", "Page %1": "Pagina %1", "Keyboard toggle": "Tastiera virtuale", "Buttons": "Pulsanti", "24h": "24 ore", "Time": "Ora", "Clipboard": "Appunti", "or": "o", "Edit": "Modifica", "Emojis": "Emoji", "Shell & utilities": "App e shell", "Reboot to firmware settings": "Riavvia alle impostazioni firmware", "No API key set for %1": "Chiave API non impostata per %1", "Disable NSFW content": "Nascondi contenuti NSFW", "Closet": "Nascosto", "Depends on sidebars": "Abilita per barre laterali", "Invalid model. Supported: \n```": "Modello non valido. Supportati: \n```", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Usa /key per utilizzare i modelli online\nCtrl+O per espandere\nCtrl+P per separare in finestra", "Not visible to model": "Non visible al modello", "Local Ollama model | %1": "Modello Ollama locale | %1", "Open file link": "Apri link al file", "Waiting for response...": "In attesa di risposta...", "Cheat sheet": "Prontuario", "Allow NSFW content": "Mostra contenuti NSFW", "%1 characters": "%1 caratteri", "Model set to %1": "Modello impostato su %1", "Be patient...": "Attendi...", "User agent (for services that require it)": "User agent (usato dai servizi)", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Endpoint API: %1\nUsa %2mode per cambiarlo", "For desktop wallpapers | Good quality": "Per sfondi del desktop | Buona qualità", "For storing API keys and other sensitive information": "Gestione credenziali e informazioni sensibili", "Your package manager is running": "Il package manager è in esecuzione", "Provider set to": "Provider impostato su", "Color generation": "Tavolozza dei colori", "Temperature must be between 0 and 2": "Il valore della temperatura deve essere fra 0 e 2", "Earbang protection": "Protezione udito", "Alternatively use /dark, /light, /img in the launcher": "Oppure usa /dark, /light, /img nel launcher", "Change any time later with /dark, /light, /img in the launcher": "Oppure usa /dark, /light, /img nel launcher", "Current model: %1\nSet it with %2model MODEL": "Modello attuale: %1\nUsa %2model per cambiarlo", "Waifus only | Excellent quality, limited quantity": "Solo waifu | Qualità eccellente, quantità limitata", "Save to Downloads": "Salva in Scaricati", "Thinking": "Ragionando", "On-screen display": "Popup di sistema", "%1 • %2 tasks": "%1 • %2 promemoria", "Qt apps": "Applicazioni Qt", "The popular one | Best quantity, but quality can vary wildly": "Il più usato | Quantità elevata, ma la qualità potrebbe variare totalmente", "Set the system prompt for the model.": "Imposta il prompt di sistema per il modello.", "Markdown test": "Test Markdown", "Prevents abrupt increments and restricts volume limit": "Impedice aumenti improvvisi del volume e ne imposta un limite.", "Preferred wallpaper zoom (%)": "Zoom sfondo (%)", "Depends on workspace": "Abilita per spazi di lavoro", "Note: turning off can hurt readability": "Nota: disattivarlo potrebbe ridurre la leggibilità", "Pick wallpaper image on your system": "Seleziona un'immagine nel tuo sistema", "Invalid API provider. Supported: \n-": "Provider API non valido. Supportati: \n-", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Hentai | Quantità elevata, NSFW, la qualità potrebbe variare totalmente", "Saved to %1": "Salvato in %1", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Imposta la temperatura (casualità) del modello. Il valore va da 0 a 2 per Gemini, e da 0 a 1 per gli altri modelli. Il valore predefinito è 0.5.", "Close": "Chiudi", "Might look ass. Unsupported.": "Potrebbe fare schifo. Non supportato.", "API key set for %1": "Chiave API impostata per %1", "Temperature\nChange with /temp VALUE": "Temperatura\nUsa /temp per cambiarla", "%1 does not require an API key": "%1 non richiede una chiave API", "Online models disallowed\n\nControlled by `policies.ai` config option": "Modelli online disattivati\n\nDisattiva l'opzione \"Solo locale\"", "Set the current API provider": "Imposta il provider API", "Total token count\nInput: %1\nOutput: %2": "Numero token totali\nInput: %1\nOutput: %2", "EasyEffects | Right-click to configure": "EasyEffects", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Sfondo anime SFW casuale da Konachan\nL'immagine verrà salvata in ~/Immagini/Wallpapers", "Hover to reveal": "Mostra col passaggio del mouse", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Usa i tasti freccia per navigare, Invio per selezionare\nEsc o clicca qualsiasi punto per uscire", "Save chat to %1": "Salva chat in %1", "Choose model": "Seleziona modello", "No API key\nSet it with /key YOUR_API_KEY": "Nessuna chiave API\nUsa /key per impostarla", "API key:\n\n```txt\n%1\n```": "Chiave API:\n\n```txt\n%1\n```", "Use Levenshtein distance-based algorithm instead of fuzzy": "Usa algoritmo di Levenshtein al posto di fuzzy", "Download complete": "Download completato", "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "Suggerimento: Nascondi le icone e mostra i numeri se vuoi\nun'esperienza fedele all'originale", "Usage": "Istruzioni", "Set API key": "Imposta chiave API", "Networking": "Rete", "Volume limit": "Limite volume", "Temperature set to %1": "Temperatura impostata a %1", "Large images | God tier quality, no NSFW.": "Immagini grandi | Qualità perfetta, no NSFW.", "Shell & utilities theming must also be enabled": "\"App e shell\" deve essere abilitato", "API key is set\nChange with /key YOUR_API_KEY": "Chiave API impostata\nUsa /key per cambiarla", "All-rounder | Good quality, decent quantity": "Versatile | Qualità buona, quantità discreta", "Large language models": "Intelligenza artificiale", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Può aiutare in caso di refusi,\nma i risultati potrebbero non essere accurati\n(es. \"GIMP\" potrebbe non restituire l'omonimo programma)", "Automatic suspend": "Sospensione automatica", "Max allowed increase": "Aumento massimo consentito", "Please charge!\nAutomatic suspend triggers at %1": "Ricarica la batteria!\nSospensione automatica in %1", "Weather Service": "Servizio meteo", "The current system prompt is\n\n---\n\n%1": "Il prompt di sistema attuale è\n\n---\n\n%1", "%1 Safe Storage": "Portachiavi di %1", "Load prompt from %1": "Carica prompt da %1", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Nessun risultato. Suggerimenti:\n- Controlla i tag e le impostazioni NSFW\n- Se non hai un tag in mente, inserici il numero di pagina", "Load chat": "Carica chat", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Prezzo**: gratuito. I tuoi dati vengono usati per training.\n\n**Istruzioni**: Accedi al tuo account Google, abilita i permessi richiesti da AI Studio, torna indietro e seleziona \"Get API key\"", "Online via %1 | %2's model": "Online tramite %1 | Modello di %2", "Get the next page of results": "Ottieni la pagina successiva dei risultati", "Unknown function call: %1": "Funzione sconosciuta: %1", "Cannot find a GPS service. Using the fallback method instead.": "Servizio GPS non disponibile. Verrà utilizzata la città preimpostata.", "Registration failed. Please inspect manually with the warp-cli command": "Registrazione fallita. Verifica l'errore manualmente col comando warp-cli", "Code saved to file": "Codice salvato", "Low warning": "Livello basso", "Clear the current list of images": "Elimina la lista corrente di immagini", "Invalid arguments. Must provide `key` and `value`.": "Argomenti non validi. Il comando richiede `key` e `value`.", "Connection failed. Please inspect manually with the warp-cli command": "Connessione fallita. Verifica l'errore manualmente col comando warp-cli", "Unknown command:": "Comando sconosciuto:", "Message the model... \"%1\" for commands": "\"%1\" per mostrare i comandi", "Load chat from %1": "Carica chat da %1", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Prezzo**: gratuito. Le policy di trattamento dei dati potrebbero variare in base alle impostazioni del tuo account OpenRouter.\n\n**Istruzioni**: Accedi al tuo account OpenRouter, vai nella sezione \"Keys\" dal menu in alto, clicca \"Create API Key\"", "Enter tags, or \"%1\" for commands": "\"%1\" per mostrare i comandi", "Show regions of potential interest": "Mostra regioni d'interesse", "Critical warning": "Livello critico", "Go to source (%1)": "Vai alla fonte (%1)", "Automatically suspends the system when battery is low": "Sospende automaticamente il sistema quando il livello della batteria è basso", "Clean stuff | Excellent quality, no NSFW": "Roba pulita | Qualità eccellente, no NSFW", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Note per Zerochan:\n- Devi inserire un colore\n- Imposta il tuo nome utente di zerochan nell'opzione `sidebar.booru.zerochan.username`. Potresti [venire bannato se non lo fai](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Critically low battery": "Batteria scarica", "Loaded the following system prompt\n\n---\n\n%1": "Il seguente prompt di sistema è stato caricato\n\n---\n\n%1", "Suspend at": "Sospendi al", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Queste regioni potrebbero essere immagini o parti di schermo contenute.\nPotrebbe non essere preciso.\nViene utilizzato un algoritmo di image processing in locale, non viene usata AI.", "Clear chat history": "Elimina cronologia chat", "Low battery": "Batteria quasi scarica", "Save chat": "Salva chat", "Switched to search mode. Continue with the user's request.": "Modalità ricerca attiva. Continua con la richiesta dell'utente.", "Number show delay when pressing Super (ms)": "Mostra numeri premendo Super dopo (ms)", "Drag or click a region • LMB: Copy • RMB: Edit": "Trascina o clicca una regione • LMB: Copia • RMB: Modifica", "Consider plugging in your device": "Connetti il tuo dispositivo alla rete elettrica", "%1 queries pending": "%1 ricerche in sospeso", "No further instruction provided": "Nessun'altra istruzione fornita", "There might be a download in progress": "Potrebbe esserci un download in corso", "Approve": "Approva", "Invalid arguments. Must provide `command`.": "Argomenti non validi. Il comando richiede `command`.", "Reject": "Rifiuta", "Thought": "Pensiero", "Performance Profile toggle": "Profilo prestazioni", "Command rejected by user": "Comando rifiutato dall'utente", "Up %1": "Tempo di attività: %1", "Overall appearance": "Aspetto", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Online | Modello di Google\nModello multiuso che eccelle in scrittura di codice e compiti di ragionamento complessi.", "Usage: %1tool TOOL_NAME": "Istruzioni: %1tool TOOL_NAME", "Set the tool to use for the model.": "Imposta lo strumento da usare con questo modello.", "Online | Google's model\nFast, can perform searches for up-to-date information": "Online | Modello di Google\nVeloce, effettua ricerche con informazioni attuali", "Invalid tool. Supported tools:\n- %1": "Strumento non valido. Strumenti supportati:\n- %1", "Usage: %1load CHAT_NAME": "Istruzioni: %1load CHAT_NAME", "Current tool: %1\nSet it with %2tool TOOL": "Strumento attuale: %1\nUsa %2tool per cambiarlo", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Online | Modello di Google\nModello Gemini 2.5 Flash ottimizzato per efficienza e throughput elevato.", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Per impostare una chiave API, utilizzala come argomento del comando %4\n\nPer vedere la chiave corrente, utilizza come argomento \"get\"
\n\n### Per %1:\n\n**Link**: %2\n\n%3", "Tool set to: %1": "Strumento impostato su: %1", "Disable tools": "Disattiva strumenti", "Usage: %1save CHAT_NAME": "Istruzioni: %1save CHAT_NAME", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Online | Modello di Google\nPiù lento del suo predecessore ma restituisce risposte di qualità maggiore", "Tint icons": "Icone monocromatiche", "Tray": "Area di notifica", "Tint app icons": "Icone monocromatiche", "Sidebars": "Barre laterali", "Keep right sidebar loaded": "Mantieni barra laterale destra in memoria", "Automatically hide": "Nascondi automaticamente", "Pause": "Pausa", "Stopwatch": "Cronometro", "🌿 Long break: %1 minutes": "🌿 Pausa lunga: %1 minuti", "Reset": "Ripristina", "Resume": "Riprendi", "Break": "Pausa breve", "🔴 Focus: %1 minutes": "🔴 Focus: %1 minuti", "☕ Break: %1 minutes": "☕ Pausa breve: %1 minuti", "Incorrect password": "Password errata", "Start": "Avvia", "Lap": "Parziale", "Enter password": "Inserisci password", "Long break": "Pausa lunga", "Anime boorus": "Boorus anime", "High": "Alto", "To Do:": "Promemoria:", "Charging:": "In carica:", "... and %1 more": "... e altri %1", "No pending tasks": "Nessuno", "System uptime:": "Tempo di attività:", "Total:": "Totale:", "Medium": "Medio", "Time to full:": "Tempo di ricarica:", "Discharging:": "Consumo:", "Free:": "Disponibile:", "Fully charged": "Carica completa", "Time to empty:": "Tempo rimanente:", "Load:": "Carico:", "Used:": "In uso:", "Low": "Basso", "Vertical": "Verticale", "Horizontal": "Orizzontale", "Hi there! First things first...": "Ciao! Per cominciare...", "Welcome app": "App di benvenuto", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Puoi riaprire la schermata di benvenuto con Super+Shift+Alt+/. Per aprire le impostazioni, usa Super+I", "Attach a file. Only works with Gemini.": "Allega un file. Funziona solo su Gemini.", "Always": "Sempre", "Place at the bottom/right": "Posiziona in basso/a destra", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Prezzo**: Piano gratuito disponibile con utilizzo limitato. Più info su https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Istruzioni**: Genera un personal access token su GitHub con i permessi \"Models\" e impostalo come chiave API\n\n**Nota**: Per utilizzare questo modello imposta la temperatura a 1", "Refreshing (manually triggered)": "Aggiornamento in corso (richiesto manualmente)", "Shell conflicts killer": "Avviso conflitti", "Kill conflicting programs?": "Terminare programmi in conflitto?", "Conflicts with the shell's notification implementation": "In conflitto con il sistema di notifiche della shell", "Conflicts with the shell's system tray implementation": "In conflitto con l'area di notifica della shell", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Mantiene il contenuto della barra laterale destra in memoria per ridurne il tempo di apertura,\nrichiede circa 15MB di RAM. Il tempo di apertura dipende dalle performance del tuo sistema.\nUsare un custom kernel come linux-cachyos può migliorare la velocità", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Istruzioni**: Accedi al tuo account Mistral, vai nella sezione \"Keys\" dal menu laterale, clicca \"Create new key\"", "Gives the model search capabilities (immediately)": "Abilita modalità ricerca del modello (immediatamente)", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Comanda, modifica la richiesta, cerca.\nRichiede un turno in più per attivare la modalità ricerca se richiesta", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Online | Modello di %1 | Ritorna risposte veloci e formattate correttamente. Svantaggi: poca voglia di svolgere compiti; potrebbe fare chiamate di funzioni inesistenti" } ================================================ FILE: dots/.config/quickshell/ii/translations/ja_JP.json ================================================ { "Mo": "月/*keep*/", "Tu": "火/*keep*/", "We": "水/*keep*/", "Th": "木/*keep*/", "Fr": "金/*keep*/", "Sa": "土/*keep*/", "Su": "日/*keep*/", "%1 characters": "%1 文字", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**料金**: 無料。データ利用ポリシーはOpenRouterアカウント設定によって異なります。\n\n**手順**: OpenRouterアカウントにログインし、右上メニューのKeysに進み、Create API Keyをクリックしてください。", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**料金**: 無料。データは学習に使用されます。\n\n**手順**: Googleアカウントにログインし、AI StudioにGoogle Cloudプロジェクトの作成などを許可し、戻ってAPIキーを取得してください。", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": "Zerochanの注意:\n- 色を入力する必要があります\n- `sidebar.booru.zerochan.username`設定でユーザー名を設定してください。設定しないと[利用停止になる場合があります](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "No further instruction provided": "追加の指示はありません", "Action": "操作", "Add": "追加", "Add task": "タスクを追加", "All-rounder | Good quality, decent quantity": "万能型 | 高品質・十分な量", "Allow NSFW": "NSFWを許可", "Allow NSFW content": "NSFWコンテンツを許可", "Anime": "アニメ", "Anime boorus": "アニメ画像掲示板", "App": "アプリ", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "矢印キーで移動、Enterで選択\nEsc/クリックでキャンセル", "Bluetooth": "Bluetooth", "Brightness": "明るさ", "Cancel": "キャンセル", "Cheat sheet": "チートシート", "Choose model": "モデルを選択", "Clean stuff | Excellent quality, no NSFW": "健全 | 高品質・NSFWなし", "Clear": "クリア", "Clear chat history": "チャット履歴を消去", "Clear the current list of images": "現在の画像リストをクリア", "Close": "閉じる", "Copy": "コピー", "Copy code": "コードをコピー", "Delete": "削除", "Desktop": "デスクトップ", "Disable NSFW content": "NSFWコンテンツを無効化", "Done": "完了", "Download": "ダウンロード", "Edit": "編集", "Enter text to translate...": "翻訳するテキストを入力...", "Finished tasks will go here": "完了したタスクはここに表示されます", "For desktop wallpapers | Good quality": "デスクトップ壁紙向け | 高品質", "For storing API keys and other sensitive information": "APIキーや機密情報の保存用", "Game mode": "ゲームモード", "Get the next page of results": "次のページを取得", "Hibernate": "休止", "Input": "入力", "Intelligence": "知能", "Interface": "インターフェース", "Invalid arguments. Must provide `key` and `value`.": "無効な引数です。`key`と`value`を指定してください。", "Jump to current month": "現在の月へ移動", "Keep system awake": "システムをスリープさせない", "Large images | God tier quality, no NSFW.": "大きな画像 | 最高品質・NSFWなし", "Large language models": "大規模言語モデル", "Launch": "起動", "Lock": "ロック", "Logout": "ログアウト", "Markdown test": "Markdownテスト", "Math result": "計算結果", "No audio source": "音声ソースなし", "No media": "メディアなし", "No notifications": "通知なし", "Not visible to model": "モデルには表示されません", "Nothing here!": "何もありません!", "Notifications": "通知", "OK": "OK", "Open file link": "ファイルリンクを開く", "Output": "出力", "Reboot": "再起動", "Reboot to firmware settings": "ファームウェア設定で再起動", "Reload Hyprland & Quickshell": "HyprlandとQuickshellを再読み込み", "Run": "実行", "Run command": "コマンドを実行", "Save": "保存", "Save to Downloads": "ダウンロードに保存", "Search": "検索", "Search the web": "ウェブ検索", "Search, calculate or run": "検索・計算・実行", "Select Language": "言語を選択", "Session": "セッション", "Set API key": "APIキーを設定", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "モデルの温度(ランダム性)を設定します。Geminiは0~2、他は0~1です。初期値は0.5です。", "Set the current API provider": "現在のAPIプロバイダーを設定します", "Shutdown": "シャットダウン", "Silent": "サイレント", "Sleep": "スリープ", "System": "システム", "Task Manager": "タスクマネージャー", "Task description": "タスクの説明", "Temperature must be between 0 and 2": "温度は0~2の間で指定してください", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "成人向け | 量は多いが品質は様々・NSFW多数", "The popular one | Best quantity, but quality can vary wildly": "人気 | 量は最多だが品質は様々", "Thinking": "考え中", "Translation goes here...": "ここに翻訳が表示されます...", "Translator": "翻訳", "Unfinished": "未完了", "Unknown": "不明", "Unknown Album": "不明なアルバム", "Unknown Artist": "不明なアーティスト", "Unknown Title": "不明なタイトル", "View Markdown source": "Markdownソースを表示", "Volume": "音量", "Volume mixer": "ボリュームミキサー", "Waifus only | Excellent quality, limited quantity": "キャラクター | 高品質・量は少なめ", "Waiting for response...": "応答待ち...", "Workspace": "ワークスペース", "Invalid API provider. Supported: \n-": "無効なAPIプロバイダー。対応: \n-", "Unknown command:": "不明なコマンド:", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "/key でオンラインモデルを開始\nCtrl+O でサイドバーを展開\nCtrl+P でサイドバーをウィンドウ化", "Provider set to": "プロバイダーを設定しました:", "Invalid model. Supported: \n```": "無効なモデル。対応: \n```", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "うまくいきませんでした。ヒント:\n- タグやNSFW設定を確認\n- タグがなければページ番号を入力", "Switched to search mode. Continue with the user's request.": "検索モードに切り替えました。リクエストを続行します。", "Settings": "設定", "Save chat": "チャットを保存", "Load chat": "チャットを読み込み", "or": "または", "Set the system prompt for the model.": "モデルのシステムプロンプトを設定", "To Do": "やること", "Calendar": "カレンダー", "Advanced": "詳細", "About": "このアプリについて", "Services": "サービス", "Style": "スタイル", "Edit config": "設定を編集", "Colors & Wallpaper": "色と壁紙", "Light": "ライト", "Dark": "ダーク", "Material palette": "マテリアルパレット", "Fidelity": "忠実度", "Fruit Salad": "フルーツサラダ", "Alternatively use /dark, /light, /img in the launcher": "ランチャーで /dark, /light, /img も使用できます", "Fake screen rounding": "画面の角を丸める(疑似)", "When not fullscreen": "フルスクリーンでない時", "Choose file": "ファイルを選択", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Konachanから健全なアニメ壁紙をランダムで取得し、~/Pictures/Wallpapers に保存します", "Be patient...": "少々お待ちください…", "Decorations & Effects": "装飾と効果", "Tonal Spot": "トーナルスポット", "Shell windows": "シェルウィンドウ", "Auto": "自動", "Wallpaper": "壁紙", "Content": "コンテンツ", "Title bar": "タイトルバー", "Transparency": "透明度", "Expressive": "表現豊か", "Yes": "表示", "Enable": "有効化", "Rainbow": "レインボー", "Might look ass. Unsupported.": "表示が崩れる可能性があります(非推奨)", "Monochrome": "モノクロ", "Random: Konachan": "ランダム: Konachan", "Center title": "タイトルを中央に", "Neutral": "ニュートラル", "Pick wallpaper image on your system": "システムから壁紙画像を選択", "No": "非表示", "AI": "AI", "Local only": "ローカルのみ", "Policies": "ポリシー", "Weeb": "アニメファン向け", "Closet": "隠し", "Show next time": "次回も表示する", "Usage": "使用状況", "Plain rectangle": "長方形", "Useless buttons": "ダミーボタン", "GitHub": "GitHub", "Style & wallpaper": "スタイルと壁紙", "Configuration": "設定", "Change any time later with /dark, /light, /img in the launcher": "ランチャーで/dark, /light, /imgでいつでも変更可能", "Keybinds": "キー割り当て", "Float": "フローティング", "Hug": "固定", "illogical-impulse Welcome": "illogical-impulse へようこそ!", "Info": "情報", "Volume limit": "ボリューム制限", "Prevents abrupt increments and restricts volume limit": "急な音量変化を防ぎ、音量の上限を設定します", "Resources": "リソース", "12h am/pm": "12時間(AM/PM)", "Base URL": "ベースURL", "Audio": "オーディオ", "Networking": "ネットワーク", "Format": "フォーマット", "Time": "時間", "Battery": "バッテリー", "Prefixes": "接頭辞", "Emojis": "絵文字", "Earbang protection": "聴覚保護(急な大音量防止)", "Automatically suspends the system when battery is low": "バッテリー残量が少ないときに自動でスリープします", "Automatic suspend": "自動スリープ", "Suspend at": "スリープ開始残量(%)", "Max allowed increase": "音量の最大増加幅", "Web search": "ウェブ検索", "Polling interval (ms)": "ポーリング間隔(ms)", "Clipboard": "クリップボード", "Low warning": "バッテリー低下の警告", "24h": "24時間", "Use Levenshtein distance-based algorithm instead of fuzzy": "あいまい検索の代わりにレーベンシュタイン距離アルゴリズムを使用", "System prompt": "システムプロンプト", "12h AM/PM": "12時間(AM/PM)", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)":"入力ミスが多い場合に便利ですが、結果が意図しないものになったり、略語(例:GIMP)が正しく検索できないことがあります", "Critical warning": "重大な警告", "User agent (for services that require it)": "ユーザーエージェント(必要なサービス用)", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "これらの領域は、画像や画面の一部など、まとまりのある部分を指します。\n必ずしも正確ではありません。\nAIではなく、ローカルで実行される画像処理アルゴリズムによって検出されます。", "Note: turning off can hurt readability": "注意: オフにすると可読性が損なわれる場合があります", "Workspaces shown": "表示中のワークスペース", "Dark/Light toggle": "ダーク/ライト切替", "Dock": "ドック", "Weather": "天気", "Pinned on startup": "起動時にピン留め", "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "ヒント: アイコンを非表示にして番号を常に表示すると、クラシックなillogical-impulseの使用感を体験できます", "Always show numbers": "数字を常に表示", "Buttons": "ボタン", "Keyboard toggle": "キーボード切替", "Scale (%)": "スケール(%)", "Overview": "概要", "Rows": "行数", "Borderless": "枠なし", "Screenshot tool": "スクリーンショットツール", "Number show delay when pressing Super (ms)": "Superキー押下時の数字表示遅延(ms)", "Timeout (ms)": "タイムアウト(ms)", "Show app icons": "アプリアイコンを表示", "Workspaces": "ワークスペース", "Columns": "列数", "On-screen display": "画面表示", "Screen snip": "画面の切り抜き", "Mic toggle": "マイク切替", "Hover to reveal": "ホバーで表示", "Bar": "バー", "Show background": "背景を表示", "Show regions of potential interest": "注目領域を表示", "Color picker": "カラーピッカー", "Help & Support": "ヘルプとサポート", "Discussions": "ディスカッション", "Color generation": "色の生成", "Dotfiles": "ドットファイル", "Distro": "ディストリビューション", "Privacy Policy": "プライバシーポリシー", "Documentation": "ドキュメント", "Shell & utilities theming must also be enabled": "「シェルとユーティリティ」のテーマ設定も有効にする必要があります", "illogical-impulse": "illogical-impulse", "Donate": "寄付", "Terminal": "ターミナル", "Shell & utilities": "シェルとユーティリティ", "Qt apps": "Qtアプリ", "Report a Bug": "バグを報告", "Issues": "課題", "Drag or click a region • LMB: Copy • RMB: Edit": "領域をドラッグまたはクリック • 左クリック: コピー • 右クリック: 編集", "Current model: %1\nSet it with %2model MODEL": "現在のモデル: %1\n%2model MODELで設定", "Message the model... \"%1\" for commands": "モデルにメッセージを送信... コマンドは「%1」", "No API key set for %1": "%1のAPIキーが設定されていません", "Loaded the following system prompt\n\n---\n\n%1": "次のシステムプロンプトを読み込みました\n\n---\n\n%1", "%1 | Right-click to configure": "%1 | 右クリックで設定", "API key set for %1": "%1のAPIキーを設定しました", "Online via %1 | %2's model": "%1経由オンライン | %2のモデル", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "現在のAPIエンドポイント: %1\n%2mode PROVIDERで設定", "Go to source (%1)": "ソースを開く(%1)", "Temperature set to %1": "温度を%1に設定", "Enter tags, or \"%1\" for commands": "タグを入力、またはコマンドは「%1」", "%1 queries pending": "%1件のクエリが保留中", "API key:\n\n```txt\n%1\n```": "APIキー:\n\n```txt\n%1\n```", "%1 Safe Storage": "%1 セーフストレージ", "%1 does not require an API key": "%1はAPIキー不要です", "Temperature: %1": "温度: %1", "Model set to %1": "モデルを%1に設定", "Page %1": "ページ %1", "Local Ollama model | %1": "ローカルOllamaモデル | %1", "The current system prompt is\n\n---\n\n%1": "現在のシステムプロンプトは\n\n---\n\n%1", "Unknown function call: %1": "不明な関数呼び出し: %1", "%1 notifications": "%1件の通知", "Load chat from %1": "%1からチャットを読み込み", "Load prompt from %1": "%1からプロンプトを読み込み", "Save chat to %1": "チャットを%1に保存", "Weather Service": "天気サービス", "Cannot find a GPS service. Using the fallback method instead.": "GPSサービスが見つかりません。代替手段を使用します。", "Critically low battery": "バッテリー残量が非常に少ない", "Select output device": "出力デバイスを選択", "Code saved to file": "コードをファイルに保存しました", "Online models disallowed\n\nControlled by `policies.ai` config option": "オンラインモデルは許可されていません\n\n`policies.ai` 設定で管理されています", "Scroll to change volume": "スクロールで音量調整", "Elements": "要素", "%1 • %2 tasks": "%1 • %2件のタスク", "Download complete": "ダウンロード完了", "Please charge!\nAutomatic suspend triggers at %1": "充電してください!\n%1で自動的にサスペンドします", "Cloudflare WARP": "Cloudflare WARP", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Scroll to change brightness": "スクロールで明るさ調整", "Connection failed. Please inspect manually with the warp-cli command": "接続に失敗しました。warp-cli コマンドで手動確認してください", "Select input device": "入力デバイスを選択", "Registration failed. Please inspect manually with the warp-cli command": "登録に失敗しました。warp-cli コマンドで手動確認してください", "Consider plugging in your device": "電源を接続することを推奨します", "Low battery": "バッテリー残量低下", "Saved to %1": "%1に保存しました", "Sunset": "日没", "UV Index": "UV指数", "Humidity": "湿度", "Wind": "風", "Sunrise": "日の出", "Pressure": "気圧", "Visibility": "視界", "Precipitation": "降水量", "Time to full:": "満充電まで:", "Time to empty:": "空になるまで:", "Fully charged": "充電完了", "Charging:": "充電中:", "Discharging:": "放電中:", "No pending tasks": "保留中のタスクなし", "... and %1 more": "...他%1件", "Used:": "使用済み:", "Free:": "空き:", "Total:": "合計:", "Swap:": "スワップ:", "Not configured": "未設定", "Load:": "負荷:", "High": "高", "Medium": "中", "Low": "低", "Use the system file picker instead": "システム標準のファイル選択ツールを使用", "Tint icons": "アイコンに色付けする", "Connect to Wi-Fi": "Wi-Fiに接続", "Invalid arguments. Must provide `command`.": "無効な引数です。`command`を指定してください。", "System uptime:": "システム稼働時間:", "Gives the model search capabilities (immediately)": "モデルにすぐに検索能力を与えます", "Click to toggle light/dark mode (applied when wallpaper is chosen)": "クリックでライト/ダークモード切替(壁紙選択時に適用されます)", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**料金**: 制限付きの無料枠があります。詳細は https://docs.github.com/en/billing/concepts/product-billing/github-models を参照\n\n**手順**: Modelsアクセス権を持つGitHub個人アクセストークンを生成し、ここでAPIキーとして設定してください\n\n**注意**: 使用するには温度パラメータを1に設定する必要があります", "Depends on workspace": "ワークスペースに依存", "Hi there! First things first...": "こんにちは!まずは始めましょう...", "Refreshing (manually triggered)": "更新中(手動で開始)", "Always": "常に", "No API key\nSet it with /key YOUR_API_KEY": "APIキーがありません\n/key YOUR_API_KEYで設定してください", "Usage: %1load CHAT_NAME": "使用法: %1load チャット名", "Sidebars": "サイドバー", "Search wallpapers": "壁紙を検索", "When this is off you'll have to click": "オフの場合、クリックで表示します", "Depends on sidebars": "サイドバーに依存", "Incorrect password": "パスワードが間違っています", "Current tool: %1\nSet it with %2tool TOOL": "現在のツール: %1\n%2tool TOOLで設定", "Overall appearance": "全体の外観", "To Do:": "やること:", "Region height": "領域の高さ", "Auto (System)": "自動(システム)", "Place the corners to trigger at the bottom": "トリガーとなる角を下部に配置", "Shell conflicts killer": "シェルの競合解消", "Enter password": "パスワードを入力", "☕ Break: %1 minutes": "☕ 休憩: %1分", "Reset": "リセット", "Connect": "接続", "Tint app icons": "アプリアイコンに淡い色付けをする", "Bar layout": "バーのレイアウト", "Conflicts with the shell's notification implementation": "シェルの通知機能と競合することがあります", "Corner open": "コーナー起動", "🌿 Long break: %1 minutes": "🌿 長い休憩: %1分", "Reject": "拒否", "Command rejected by user": "コマンドはユーザーによって拒否されました", "Start": "開始", "Brightness and volume": "明るさ・音量", "Corner style": "角のスタイル", "Total token count\nInput: %1\nOutput: %2": "トークン数合計\n入力: %1\n出力: %2", "No active player": "アクティブなプレーヤーがありません", "Performance Profile toggle": "パフォーマンスプロファイル切替", "Timer": "タイマー", "Conflicts with the shell's system tray implementation": "シェルのシステムトレイ機能と競合することがあります", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "オンライン | %1のモデル | 高速で反応が良く、整形された回答が得られます。欠点: あまり積極的でない場合があり、存在しない関数を提案することがあります", "Welcome app": "ようこそアプリ", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "オンライン | Googleモデル\nコスト効率と高スループットに最適化されたGemini 2.5 Flashモデル。", "Wallpaper parallax": "壁紙パララックス効果", "Place at the bottom": "下部に配置", "Invalid tool. Supported tools:\n- %1": "無効なツールです。対応ツール:\n- %1", "Password": "パスワード", "Details": "詳細", "Edit directory": "ディレクトリを編集", "Language": "言語", "Visualize region": "領域を可視化", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "お楽しみください!ウェルカムアプリはSuper+Shift+Alt+/でいつでも開けます。設定アプリを開くにはSuper+Iを押してください", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "オンライン | Googleモデル\nコーディングや複雑な推論タスクに優れた、Googleの最先端多目的モデル。", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "有効にすると右サイドバーのコンテンツを常に読み込んでおくことで、表示遅延を短縮します。\n代償として約15MBのメモリを継続的に使用します。遅延の度合いはシステム性能に依存します。\nlinux-cachyosのようなカスタムカーネルの使用が効果的な場合があります。", "Place at bottom": "下部に配置", "Tool set to: %1": "ツールを設定: %1", "Set the tool to use for the model.": "モデルが使用するツールを設定します。", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "お使いのプレイヤーがMPRISをサポートしているか確認するか、\n重複プレイヤーのフィルタリングを無効にしてみてください", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "インターフェース言語を選択します。\n「自動」を選ぶとシステムのロケールが使用されます。", "Value scroll": "スクロールで音量・明るさを調整", "There might be a download in progress": "ダウンロードが進行中のようです", "Approve": "承認", "Tray": "トレイ", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**手順**: Mistralアカウントにログインし、サイドバーのKeysに進み、Create new keyをクリックしてください", "Pomodoro": "ポモドーロ", "Language setting saved. Please restart Quickshell (Ctrl+Super+R) to apply the new language.": "言語設定を保存しました。新しい言語を適用するにはQuickshellを再起動してください(Ctrl+Super+R)。", "Feels like %1": "体感温度 %1", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "コマンド、設定編集、検索が可能。\n検索モードへの切替が必要な場合は追加の対話が必要です", "Resume": "再開", "Preferred wallpaper zoom (%)": "壁紙の拡大率(%)", "Disable tools": "ツールをオフ", "Night Light | Right-click to toggle Auto mode": "ナイトライト | 右クリックで自動モード切替", "Online | Google's model\nFast, can perform searches for up-to-date information": "オンライン | Googleモデル\n高速で、最新情報を検索できます", "Hover to trigger": "ホバーでトリガー", "Keep right sidebar loaded": "右サイドバーを常に読み込む", "Kill conflicting programs?": "競合するプログラムを終了しますか?", "Pick a wallpaper": "壁紙を選択", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "オンライン | Googleモデル\n旧モデルより低速ですが、より高品質な回答が期待できる新モデル", "Hit \"/\" to search": "「/」で検索", "Config file": "設定ファイル", "Attach a file. Only works with Gemini.": "ファイルを添付 (Geminiでのみ利用可能)", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "APIキーを設定するには、%4コマンドを使用してください\n\nキーを確認するには、コマンドに「get」を付けてください
\n\n### %1について:\n\n**リンク**: %2\n\n%3", "Thought": "思考", "Long break": "長い休憩", "Temperature\nChange with /temp VALUE": "温度\n/temp 値で変更", "Usage: %1tool TOOL_NAME": "使用法: %1tool ツール名", "Pause": "一時停止", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "バーの配置に関わらず、画面コーナーのクリックまたはホバーでサイドバーを開けるようにします", "EasyEffects | Right-click to configure": "EasyEffects | 右クリックで設定", "Up %1": "%1稼働中", "Place at the bottom/right": "下部/右側に配置", "Focus": "集中", "Stopwatch": "ストップウォッチ", "Interface Language": "インターフェース言語", "Memory usage": "メモリ使用量", "Automatically hide": "自動的に隠す", "Break": "休憩", "Your package manager is running": "パッケージマネージャーが実行中です", "API key is set\nChange with /key YOUR_API_KEY": "APIキーが設定済み\n/key YOUR_API_KEYで変更できます", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "シェルの設定ファイルを開きます。\nボタンが機能しない、または任意のエディタで開かない場合は、\n~/.config/illogical-impulse/config.json を手動で開いてください", "CPU usage": "CPU使用率", "Swap usage": "スワップ使用量", "Usage: %1save CHAT_NAME": "使用法: %1save チャット名", "🔴 Focus: %1 minutes": "🔴 集中: %1分", "Lap": "ラップ", "Horizontal": "水平", "Region width": "領域の幅", "Vertical": "垂直" } ================================================ FILE: dots/.config/quickshell/ii/translations/pt_BR.json ================================================ { "Attach a file. Only works with Gemini.": "Anexar um arquivo. Funciona apenas com Gemini.", "%1 • %2 tasks": "%1 • %2 tarefas", "Large language models": "Grandes modelos de linguagem (LLMs)", "Open network portal": "Fazer login na rede", "Digits in the middle": "Dígitos no meio", "Auto,": "Auto,", "Select Language": "Selecionar Idioma", "Expressive": "Expressivo", "🔴 Focus: %1 minutes": "🔴 Foco: %1 minutos", "%1 | Right-click to configure": "%1 | Botão direito para configurar", "Delete": "Excluir", "Audio output": "Saída de áudio", "Full warning": "Aviso de carga completa", "Switched to search mode. Continue with the user's request.": "Alternado para modo de busca. Continue com a solicitação do usuário.", "Cloudflare WARP": "Cloudflare WARP", "Lock": "Bloquear", "Apps": "Apps", "Generating...\nDon't close this window!": "Gerando...\nNão feche esta janela!", "Please charge!\nAutomatic suspend triggers at %1%": "Por favor carregue!\nSuspensão automática em %1%", "Font family name (e.g., Google Sans Flex)": "Nome da família da fonte (ex: Google Sans Flex)", "Loaded the following system prompt\n\n---\n\n%1": "Prompt de sistema carregado\n\n---\n\n%1", "Adjust the color temperature": "Ajustar temperatura de cor", "Numbers": "Números", "Style: Blurred": "Estilo: Desfocado", "Usage": "Uso", "Ignored if terminal theming is not enabled": "Ignorado se o tema do terminal não estiver ativado", "Save to Downloads": "Salvar em Downloads", "Open file link": "Abrir link do arquivo", "Do you want to allow this app to make changes to your device?": "Deseja permitir que este app faça alterações no seu dispositivo?", "Visibility": "Visibilidade", "Set the tool to use for the model.": "Definir a ferramenta para o modelo usar.", "Bar position": "Posição da barra", "Open": "Abrir", "Mic toggle": "Alternar Microfone", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Idioma não listado ou traduções incompletas?\nVocê pode gerar traduções com o Gemini.\n1. Abra a barra lateral esquerda com Super+A, defina o modelo para Gemini\n2. Digite /key, aperte Enter e siga as instruções\n3. Digite /key SUA_API_KEY\n4. Digite o código do seu idioma abaixo e clique em Gerar", "Muted": "Mudo", "Registration failed. Please inspect manually with the warp-cli command": "Falha no registro. Por favor inspecione manualmente com o comando warp-cli", "API key is set\nChange with /key YOUR_API_KEY": "Chave API definida\nAltere com /key SUA_CHAVE_API", "Terminal: Harmonize threshold": "Terminal: Limiar de harmonização", "%1 characters": "%1 caracteres", "For desktop wallpapers | Good quality": "Para desktop | Boa qualidade", "🌿 Long break: %1 minutes": "🌿 Pausa longa: %1 minutos", "Set FPS limit": "Definir limite de FPS", "+%1 notifications": "+%1 notificações", "Shell & utilities theming must also be enabled": "Tema do Shell e utilitários também deve estar ativado", "Content region": "Região do conteúdo", "Padding": "Espaçamento (Padding)", "Session": "Sessão", "Online | Google's model\nPro-level intelligence at the speed and pricing of Flash.": "Online | Modelo do Google\nInteligência nível Pro na velocidade e preço do Flash.", "Yes": "Sim", "Listening...": "Ouvindo...", "Base URL": "URL Base", "Load:": "Uso:", "Path copied": "Caminho copiado", "Show": "Mostrar", "Donate": "Doar", "Keyboard toggle": "Alternar Teclado", "File Explorer": "Explorador de Arquivos", "Example use case: eroge on one workspace, dark Discord window on another": "Exemplo de uso: eroge em um workspace, Discord escuro em outro", "Tooltips": "Dicas", "Internet": "Internet", "Keybinds": "Atalhos", "Feels like %1": "Sensação térmica de %1", "Wi-Fi": "Wi-Fi", "Used:": "Usado:", "Used for reading large blocks of text": "Usado para leitura de grandes blocos de texto", "Use the system file picker instead\nRight-click to make this the default behavior": "Usar o selecionador de arquivos do sistema\nBotão direito para tornar este o padrão", "Enter password": "Digite a senha", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Preço**: grátis. Política de dados varia conforme configurações da conta OpenRouter.\n\n**Instruções**: Logue no OpenRouter, vá em Keys no menu superior direito, clique em Create API Key", "Monospace font": "Fonte Monoespaçada", "Positioning": "Posicionamento", "Search, calculate or run": "Pesquisar, calcular ou executar", "Message the model... \"%1\" for commands": "Mensagem para o modelo... \"%1\" para comandos", "Focus": "Foco", "Font width": "Largura da fonte", "Enable opening zoom animation": "Ativar animação de zoom ao abrir", "Logout": "Sair (Logout)", "Jump to current month": "Pular para mês atual", "Keep awake": "Manter acordado", "Show next time": "Mostrar na próxima vez", "Date style": "Estilo da data", "GitHub": "GitHub", "Saved": "Salvo", "Bottom-up": "De baixo para cima", "Disable tools": "Desativar ferramentas", "Copy code": "Copiar código", "Low warning": "Aviso de bateria fraca", "Saving...": "Salvando...", "Interface": "Interface", "Normal": "Normal", "Hug": "Arredondado", "Tint icons": "Colorir ícones", "Turn on from sunset to sunrise": "Ligar do pôr do sol ao nascer do sol", "Kill conflicting programs?": "Matar programas conflitantes?", "Model set to %1": "Modelo definido para %1", "Desktop %1": "Área de Trabalho %1", "Waifus only | Excellent quality, limited quantity": "Apenas Waifus | Excelente qualidade, quantidade limitada", "Google Lens": "Google Lens", "Battery: %1%2": "Bateria: %1%2", "Calendar": "Calendário", "24h": "24h", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Por que isso é legal:\nPara valores não-0, não acionará quando você atingir o\ncanto da tela pela borda horizontal, mas acionará quando\nvocê for pela borda vertical", "Disable NSFW content": "Desativar conteúdo NSFW", "Allow NSFW": "Permitir NSFW", "Workspaces": "Áreas de trabalho", "When not fullscreen": "Quando não estiver em tela cheia", "Run": "Executar", "Sounds": "Sons", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "Botão Esq. p/ ativar/desativar\nBotão Dir. p/ mudar tamanho\nScroll p/ trocar posição", "☕ Break: %1 minutes": "☕ Pausa: %1 minutos", "Enter a valid number": "Digite um número válido", "Split buttons": "Botões divididos", "Best match": "Melhor resultado", "Enable": "Ativar", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Setas para navegar, Enter para selecionar\nEsc ou clique fora para cancelar", "Intelligence": "Inteligência", "Launch on startup": "Iniciar no boot", "New desktop": "Nova área de trabalho", "Intensity": "Intensidade", "Paired": "Pareado", "Desktop": "Área de Trabalho", "Task View": "Visão de Tarefas", "Not visible to model": "Invisível para o modelo", "App": "App", "Bar": "Barra", "Show this window on all desktops": "Mostrar esta janela em todas as áreas", "Online models disallowed\n\nControlled by `policies.ai` config option": "Modelos online não permitidos\n\nControlado pela opção `policies.ai` na config", "Hit \"/\" to search": "Aperte \"/\" para buscar", "Font width and roundness settings are only available for some fonts like Google Sans Flex": "Largura e arredondamento só funcionam em fontes como Google Sans Flex", "Use symbols for mouse": "Usar símbolos para o mouse", "End session": "Encerrar sessão", "... and %1 more": "... e mais %1", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Código de localidade, ex: pt_BR, en_US...", "%1 Safe Storage": "Cofre Seguro do %1", "Format": "Formato", "Dots": "Dotfiles", "There might be a download in progress": "Pode haver um download em andamento", "Medium": "Médio", "Math": "Matemática", "Command-line-invoked Action": "Ação invocada por linha de comando", "Style: general": "Estilo: geral", "Time to full:": "Tempo até carga total:", "Classic": "Clássico", "Battery full": "Bateria cheia", "Unread indicator: show count": "Indicador não lido: mostrar contagem", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Online | Modelo da %1 | Respostas rápidas e bem formatadas. Desvantagens: não muito proativo; pode inventar chamadas de função inexistentes", "Workspace": "Workspace", "Power Profile": "Perfil de Energia", "Quick toggles": "Botões rápidos", "Speakers (%1): %2": "Alto-falantes (%1): %2", "Sunrise": "Nascer do sol", "Clear chat history": "Limpar histórico do chat", "Auto styling with Gemini": "Estilo automático com Gemini", "Center clock": "Relógio centralizado", "Circle": "Círculo", "Han chars": "Caracteres Han", "Roman": "Romano", "Night Light | Right-click to configure": "Luz Noturna | Botão direito para configurar", "Sliders": "Deslizadores", "On-screen display": "Exibição na tela (OSD)", "Show \"Locked\" text": "Mostrar texto \"Bloqueado\"", "Unfinished": "Inacabado", "Hour marks": "Marcas de hora", "Shut down": "Desligar", "Be patient...": "Seja paciente...", "Also unlock keyring": "Também destrancar chaveiro", "Temperature set to %1": "Temperatura definida para %1", "Choose model": "Escolher modelo", "Enter text to translate...": "Digite o texto para traduzir...", "Finished tasks will go here": "Tarefas finalizadas virão para cá", "Info": "Info", "Parallax": "Paralaxe", "Show date": "Mostrar data", "Pills": "Cápsulas", "Font family name": "Nome da família da fonte", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Online | Modelo do Google\nModelo mais novo, mais lento que o antecessor mas com maior qualidade nas respostas", "View Markdown source": "Ver código Markdown", "Stroke width": "Largura do traço", "Least busy": "Menos ocupado", "Start": "Início", "Click to cycle through power profiles": "Clique para alternar perfis de energia", "Dark Mode": "Modo Escuro", "Closet": "Closet", "Prevents abrupt increments and restricts volume limit": "Previne aumentos bruscos e restringe o limite de volume", "Output device": "Dispositivo de saída", "Stopwatch": "Cronômetro", "Local only": "Apenas local", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Define a temperatura (aleatoriedade) do modelo. Valores entre 0 a 2 para Gemini, 0 a 1 para outros. Padrão é 0.5.", "The current system prompt is\n\n---\n\n%1": "O prompt de sistema atual é\n\n---\n\n%1", "with vertical offset": "com deslocamento vertical", "Utilities & Tools": "Utilitários & Ferramentas", "Invalid API provider. Supported: \n-": "Provedor de API inválido. Suportados: \n-", "Font family name (e.g., Readex Pro)": "Nome da família da fonte (ex: Readex Pro)", "Emojis": "Emojis", "Auto (System)": "Auto (Sistema)", "Cheat sheet": "Atalhos", "General": "Geral", "Password": "Senha", "Config file": "Config file", "Temperature\nChange with /temp VALUE": "Temperatura\nAltere com /temp VALOR", "Swap": "Swap", "Up %1": "Ligado há %1", "Code saved to file": "Código salvo em arquivo", "Android": "Android", "Hint target regions": "Dicar regiões alvo", "Set API key": "Definir chave API", "Clear the current list of images": "Limpar lista atual de imagens", "Night Light": "Luz Noturna", "Font roundness": "Arredondamento da fonte", "Style & wallpaper": "Estilo & Papel de parede", "Nothing": "Nada", "Snipping area": "Área de recorte", "Exceeded max allowed": "Máximo permitido excedido", "Sidebars": "Barras laterais", "Total token count\nInput: %1\nOutput: %2": "Contagem total de tokens\nEntrada: %1\nSaída: %2", "Off": "Desligado", "e.g. 󱊫 for F1, 󱊶 for F12": "ex: 󱊫 para F1, 󱊶 para F12", "Temperature: %1": "Temperatura: %1", "Open the shell config file\nAlternatively right-click to copy path": "Abrir arquivo de config do shell\nOu botão direito para copiar caminho", "Action": "Ação", "Force hover open at absolute corner": "Forçar canto absoluto", "Fill": "Preencher", "illogical-impulse": "illogical-impulse", "Commands": "Comandos", "No active player": "Nenhum player ativo", "Show only when locked": "Mostrar apenas quando bloqueado", "Usage: %1save CHAT_NAME": "Uso: %1save NOME_DO_CHAT", "Provider set to": "Provedor definido para", "Brightness": "Brilho", "Fully charged": "Carga completa", "Privacy Policy": "Política de Privacidade", "Clean stuff | Excellent quality, no NSFW": "Coisas limpas (SFW) | Excelente qualidade, sem NSFW", "UV Index": "Índice UV", "No": "Não", "Manage my account": "Gerenciar minha conta", "Advanced": "Avançado", "Emoji": "Emoji", "Shell command": "Comando Shell", "Transparency": "Transparência", "Help & Support": "Ajuda & Suporte", "More Internet settings": "Mais configurações de Internet", "Generate\nTypically takes 2 minutes": "Gerar\nGeralmente leva 2 minutos", "Unknown Application": "Aplicativo Desconhecido", "Scale (%)": "Escala (%)", "Go to source (%1)": "Ir para a fonte (%1)", "You can also manually edit cheatsheet.superKey": "Você também pode editar manualmente cheatsheet.superKey", "Unknown Title": "Título Desconhecido", "%1\nInternet access": "%1\nAcesso à internet", "Enable now": "Ativar agora", "Creativity": "Criatividade", "Cookie": "Cookie", "Font size": "Tamanho da fonte", "Sides": "Lados", "Full": "Cheio", "Used for code and terminal": "Usado para código e terminal", "Unknown Album": "Álbum Desconhecido", "Recognize music": "Reconhecer música", "Your package manager is running": "Seu gerenciador de pacotes está rodando", "Audio output | Right-click for volume mixer & device selector": "Saída de áudio | Botão direito para mixer & seletor", "Conflicts with the shell's notification implementation": "Conflita com a implementação de notificação do shell", "Total duration timeout (s)": "Tempo limite total (s)", "Reading font": "Fonte de leitura", "It may take a few seconds to update": "Pode levar alguns segundos para atualizar", "Force dark mode in terminal": "Forçar modo escuro no terminal", "Show notifications": "Mostrar notificações", "Web search": "Busca web", "%1 mins": "%1 min", "Left to right": "Esquerda para direita", "%1 does not require an API key": "%1 não requer chave API", "System": "Sistema", "Details": "Detalhes", "Get the next page of results": "Obter próxima página de resultados", "12h AM/PM": "12h AM/PM", "Bluetooth devices": "Dispositivos Bluetooth", "Dark": "Escuro", "Neutral": "Neutro", "Dot": "Ponto", "Fidelity": "Fidelidade", "Sunset": "Pôr do sol", "Other": "Outro", "Terminal: Harmony (%)": "Terminal: Harmonia (%)", "Record": "Gravar", "More volume settings": "Mais configurações de volume", "Audio input | Right-click for volume mixer & device selector": "Entrada de áudio | Botão direito para mixer & seletor", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Selecione o idioma da interface.\n\"Auto\" usará a localidade do sistema.", "Dock": "Dock", "About": "Sobre", "Disconnect": "Desconectar", "Widget: Weather": "Widget: Clima", "Enable GPS based location": "Ativar localização por GPS", "User agent (for services that require it)": "User agent (para serviços que exigem)", "All-rounder | Good quality, decent quantity": "Geral | Boa qualidade, quantidade decente", "Set the system prompt for the model.": "Definir prompt de sistema para o modelo.", "Output": "Saída", "Reboot to firmware settings": "Reiniciar na BIOS/Firmware", "Hollow": "Oco", "Float": "Flutuante", "Terminal: Foreground boost (%)": "Terminal: Impulso de primeiro plano (%)", "Bluetooth": "Bluetooth", "Authentication": "Autenticação", "Large images | God tier quality, no NSFW.": "Imagens grandes | Qualidade divina, sem NSFW.", "Region selector (screen snipping/Google Lens)": "Seletor de região (recorte/Google Lens)", "Show hidden icons": "Mostrar ícones ocultos", "Sound effects": "Efeitos sonoros", "Manage accounts": "Gerenciar contas", "When this is off you'll have to click": "Quando desligado, você terá que clicar", "of %1": "de %1", "Monochrome": "Monocromático", "Use adaptive alignment": "Usar alinhamento adaptativo", "Close all windows": "Fechar todas as janelas", "Gives the model search capabilities (immediately)": "Dá capacidade de busca ao modelo (imediatamente)", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Nem todas as opções estão neste app. Verifique o arquivo de config clicando no botão \"Arquivo de config\" no canto superior esquerdo ou abrindo %1 manualmente.", "Volume limit": "Limite de volume", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Clique para alternar modo claro/escuro\n(aplicado ao escolher papel de parede)", "Local account": "Conta local", "Text extractor": "Extrator de texto", "Terminal": "Terminal", "Font family name (e.g., Space Grotesk)": "Nome da família da fonte (ex: Space Grotesk)", "Markdown test": "Teste Markdown", "Most busy": "Mais ocupado", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Endpoint de API atual: %1\nDefina com %2mode PROVEDOR", "Center icons": "Centralizar ícones", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Quando ativado, mantém a barra lateral direita carregada para reduzir atraso ao abrir,\nao custo de ~15MB de RAM. O impacto depende do seu sistema.\nUsar um kernel customizado como linux-cachyos pode ajudar", "Tip: Close a window with Super+Q": "Dica: Feche uma janela com Super+Q", "Overview": "Visão Geral", "Unknown device": "Dispositivo desconhecido", "Volume": "Volume", "Dark/Light toggle": "Alternar Escuro/Claro", "Place the corners to trigger at the bottom": "Colocar cantos de ativação na parte inferior", "Shell conflicts killer": "Matador de conflitos do Shell", "No media": "Sem mídia", "Keep right sidebar loaded": "Manter barra direita carregada", "Virtual Keyboard": "Teclado Virtual", "Visualize region": "Visualizar região", "Bar & screen": "Barra & tela", "RAM": "RAM", "Timeout (ms)": "Tempo limite (ms)", "Automatic": "Automático", "Humidity": "Umidade", "All": "Todos", "Connection failed. Please inspect manually with the warp-cli command": "Conexão falhou. Inspecione manualmente com o comando warp-cli", "Clipboard": "Área de transferência", "Incorrect password": "Senha incorreta", "Documentation": "Documentação", "Used for decorative/expressive text": "Usado para texto decorativo/expressivo", "Qt apps": "Apps Qt", "To Do:": "A Fazer:", "Content": "Adaptativo", "Thin": "Fino", "illogical-impulse Welcome": "Boas-vindas ao illogical-impulse", "Timer": "Timer", "Widgets": "Widgets", "Performance Profile toggle": "Alternar Perfil de Desempenho", "Description font size": "Tamanho da fonte da descrição", "Show app icons": "Mostrar ícones de apps", "Preferred wallpaper zoom (%)": "Zoom preferido do papel de parede (%)", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Lembre-se que na maioria dos dispositivos pode-se segurar o botão power para forçar desligamento\nIsso só torna um pouco mais difícil acontecer acidentes", "Translator": "Tradutor", "Cannot find a GPS service. Using the fallback method instead.": "Serviço de GPS não encontrado. Usando método alternativo.", "Image search": "Busca de imagem", "Digital clock settings": "Configurações do relógio digital", "Download complete": "Download completo", "Download": "Baixar", "Issues": "Problemas (Issues)", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Notas para Zerochan:\n- Você deve inserir uma cor\n- Defina seu usuário zerochan na config `sidebar.booru.zerochan.username`. Você [pode ser banido se não fizer isso](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Input": "Entrada", "Notes": "Notas", "Pin to Start": "Fixar no Iniciar", "Border": "Borda", "Tray": "Bandeja (Tray)", "AI": "IA", "Recognize music | Right-click to toggle source": "Reconhecer música | Botão direito p/ alternar fonte", "Random: Konachan": "Aleatório: Konachan", "Region width": "Largura da região", "Bubble": "Bolha", "No new notifications": "Nenhuma notificação nova", "Quote": "Citação", "Total:": "Total:", "Snip": "Recortar", "Battery": "Bateria", "Pick wallpaper image on your system": "Escolher imagem no sistema", "Invalid tool. Supported tools:\n- %1": "Ferramenta inválida. Suportadas:\n- %1", "Reboot": "Reiniciar", "The popular one | Best quantity, but quality can vary wildly": "O popular | Melhor quantidade, mas qualidade varia muito", "Screen snip": "Captura de tela", "Search": "Buscar", "Connect to Wi-Fi": "Conectar ao Wi-Fi", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Geralmente seguro e necessário para navegador e IA\nÚtil principalmente para quem usa bloqueio na inicialização em vez de um gerenciador de display (GDM, SDDM, etc.)", "Darken screen": "Escurecer tela", "Anti-flashbang (experimental)": "Anti-clarão (experimental)", "Sign out": "Sair", "Usage: %1load CHAT_NAME": "Uso: %1load NOME_DO_CHAT", "Color generation": "Geração de cores", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Uso: %1superpaste NUM_ENTRADAS[i]\nUse i quando quiser imagens\nExemplos:\n%1superpaste 4i para as últimas 4 imagens\n%1superpaste 7 para as últimas 7 entradas", "Search wallpapers": "Buscar papéis de parede", "For storing API keys and other sensitive information": "Para armazenar chaves API e outras informações sensíveis", "Cancel": "Cancelar", "Polling interval (s)": "Intervalo de atualização (s)", "Expressive font": "Fonte expressiva", "EasyEffects": "EasyEffects", "Rectangular selection": "Seleção retangular", "Hi there! First things first...": "Olá! Primeiramente...", "Saved to %1": "Salvo em %1", "API key:\n\n```txt\n%1\n```": "Chave API:\n\n```txt\n%1\n```", "Line": "Linha", "Number show delay when pressing Super (ms)": "Atraso numérico ao pressionar Super (ms)", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Mude depois com /dark, /light, /wallpaper no launcher\nSe as cores não mudarem:\n 1. Abra a barra direita com Super+N\n 2. Clique em \"Recarregar Hyprland & Quickshell\" no canto superior direito", "Columns": "Colunas", "Animate time change": "Animar mudança de hora", "Audio": "Áudio", "Weeb": "Otaku", "Image source": "Fonte da imagem", "Work safety": "Segurança no trabalho (SFW)", "Super key symbol": "Símbolo da tecla Super", "Select language": "Selecionar idioma", "Free:": "Livre:", "Anime": "Anime", "Wallpaper & Colors": "Papel de Parede & Cores", "Tip: right-clicking a group\nalso expands it": "Dica: clicar com botão direito num grupo\ntambém o expande", "System prompt": "Prompt do sistema", "Automatic suspend": "Suspensão automática", "Thought": "Pensamento", "Depends on sidebars": "Depende das barras laterais", "Hour hand": "Ponteiro das horas", "Security": "Segurança", "Report a Bug": "Reportar um Bug", "Reset": "Resetar", "Command": "Comando", "Active": "Ativo", "Current tool: %1\nSet it with %2tool TOOL": "Ferramenta atual: %1\nDefina com %2tool FERRAMENTA", "Used for headings and titles": "Usado para cabeçalhos e títulos", "Left": "Esquerda", "Keybind font size": "Tamanho da fonte de atalhos", "Region height": "Altura da região", "Unpin from taskbar": "Desafixar da barra de tarefas", "Search for apps": "Buscar apps", "Use symbols for function keys": "Usar símbolos para teclas de função", "Hover to reveal": "Passar mouse para revelar", "Perhaps what you're listening to is too niche": "Talvez o que você está ouvindo seja muito nichado", "at": "em", "Value scroll": "Scroll de valor", "Used for general UI text": "Usado para texto geral da UI", "Use Hyprlock (instead of Quickshell)": "Usar Hyprlock (em vez de Quickshell)", "Anime boorus": "Anime boorus", "Sleep": "Dormir", "Overlay: Floating Image": "Overlay: Imagem Flutuante", "Time to empty:": "Tempo para descarregar:", "Last refresh: %1": "Última atualização: %1", "Music Recognized": "Música Reconhecida", "Music Recognition": "Reconhecimento de Música", "Consider plugging in your device": "Considere conectar seu dispositivo", "Load chat from %1": "Carregar chat de %1", "Edit directory": "Editar diretório", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Para definir chave API, use o comando %4\n\nPara ver a chave, use \"get\" com o comando
\n\n### Para %1:\n\n**Link**: %2\n\n%3", "Polling interval (ms)": "Intervalo de atualização (ms)", "or": "ou", "Timeout duration (if not defined by notification) (ms)": "Duração do tempo limite (se não definido pela notificação) (ms)", "Low battery": "Bateria fraca", "Reload Hyprland & Quickshell": "Recarregar Hyprland & Quickshell", "Input device": "Dispositivo de entrada", "Make sure you have songrec installed": "Certifique-se de ter o songrec instalado", "Hover to trigger": "Passar mouse para ativar", "Please unplug the charger": "Por favor desconecte o carregador", "Tool set to: %1": "Ferramenta definida para: %1", "Unmuted": "Com som", "Rectangle": "Retângulo", "Invalid arguments. Must provide `key` and `value`.": "Argumentos inválidos. Deve fornecer `key` e `value`.", "Corner style": "Estilo do canto", "Screenshot Path (leave empty to just copy)": "Caminho do Print (vazio para apenas copiar)", "Generate translation with Gemini": "Gerar tradução com Gemini", "Widget: Clock": "Widget: Relógio", "Clock style (locked)": "Estilo do relógio (bloqueado)", "Thinking": "Pensando", "Cancel wallpaper selection": "Cancelar seleção de papel de parede", "Superpaste": "Supercolar", "Bold": "Negrito", "Brightness and volume": "Brilho e volume", "Close window": "Fechar janela", "Overlay: General": "Overlay: Geral", "Sound output": "Saída de som", "Conflicts with the shell's system tray implementation": "Conflita com a implementação de bandeja do sistema do shell", "Elements": "Elementos", "Audio input": "Entrada de áudio", "Rows": "Linhas", "No API key\nSet it with /key YOUR_API_KEY": "Sem chave API\nDefina com /key SUA_CHAVE_API", "CPU": "CPU", "Time": "Hora", "Cookie clock settings": "Configurações do relógio Cookie", "Resources": "Recursos", "Enable blur": "Ativar desfoque (blur)", "Set the current API provider": "Definir provedor de API atual", "Bar style": "Estilo da barra", "Configuration": "Configuração", "Tonal Spot": "Tonal Spot", "Dial style": "Estilo do mostrador", "API key set for %1": "Chave API definida para %1", "Pinned": "Fixado", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Verifique se seu player tem suporte MPRIS\nou tente desligar o filtro de player duplicado", "Discussions": "Discussões", "Precipitation": "Precipitação", "Pick a wallpaper": "Escolha um papel de parede", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Fundo aleatório sazonal de osu!\nImagem salva em ~/Pictures/Wallpapers", "Press Super+G to open the overlay and pin the crosshair": "Aperte Super+G para abrir o overlay e fixar a mira", "Save": "Salvar", "Crosshair code (in Valorant's format)": "Código da mira (formato Valorant)", "Windows": "Janelas", "Copy path": "Copiar caminho", "Make icons pinned by default": "Fixar ícones por padrão", "Extra wallpaper zoom (%)": "Zoom extra do papel de parede (%)", "Task Manager": "Gerenciador de Tarefas", "Font weight": "Peso da fonte", "On": "Ligado", "Used for displaying numbers": "Usado para exibir números", "Unknown command:": "Comando desconhecido:", "Networking": "Rede", "Scroll to Bottom": "Rolar para o fundo", "If you want to somehow use fingerprint unlock...": "Se você quiser usar desbloqueio por digital de alguma forma...", "Use system file picker": "Usar selecionador de arquivo do sistema", "Fruit Salad": "Fruit Salad", "Line-separated": "Separado por linha", "Pin to taskbar": "Fixar na barra de tarefas", "Use old sine wave cookie implementation": "Usar implementação antiga de onda senoidal cookie", "Pinned on startup": "Fixado na inicialização", "Welcome app": "App de Boas-vindas", "Unknown function call: %1": "Chamada de função desconhecida: %1", "Shell & utilities": "Shell & utilitários", "Require password to power off/restart": "Exigir senha para desligar/reiniciar", "Scroll to change brightness": "Rolar para mudar brilho", "Lap": "Volta", "Health:": "Saúde:", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Se a opção anterior estiver desligada e esta ligada,\nvocê ainda pode passar o mouse no fim do canto para abrir a barra lateral,\ne a área restante pode ser usada para rolagem de volume/brilho", "Approve": "Aprovar", "Font family name (e.g., JetBrains Mono NF)": "Nome da família da fonte (ex: JetBrains Mono NF)", "Quick": "Básico", "Max allowed increase": "Aumento máximo permitido", "Video Recording Path": "Caminho de Gravação de Vídeo", "Corner open": "Abrir pelo canto", "Enjoy your empty sidebar...": "Aproveite sua barra lateral vazia...", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar": "Digite /key para começar com modelos online\nCtrl+O para expandir barra lateral\nCtrl+P para fixar\nCtrl+D para destacar", "Digital": "Digital", "Balance brightness based on content": "Balancear brilho baseado no conteúdo", "Open editor": "Abrir editor", "Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"": "Substitui 󱕐 por \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"Botão Esq.\", R󰍽 \"Botão Dir.\", 󱕒 \"Scroll ↑/↓\" e ⇞/⇟ por \"Page_↑/↓\"", "Window": "Janela", "EasyEffects | Right-click to configure": "EasyEffects | Botão direito para configurar", "Refreshing (manually triggered)": "Atualizando (acionado manualmente)", "Distro": "Distro", "Prefixes": "Prefixos", "e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc": "ex: 󰘴 para Ctrl, 󰘵 para Alt, 󰘶 para Shift, etc", "Pressure": "Pressão", "Second hand": "Ponteiro de segundos", "To Do": "A Fazer", "Silent": "Silencioso", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Usa Gemini para categorizar o papel de parede e escolhe um preset.\nVocê precisa definir a chave API do Gemini na barra esquerda antes.\nImagens são reduzidas para desempenho, mas por segurança,\nnão selecione papéis de parede com informações sensíveis.", "Move right": "Mover para direita", "Auto": "Auto", "Move to front": "Mover para frente", "Top": "Topo", "Pomodoro": "Pomodoro", "Copy": "Copiar", "Weather": "Clima", "Couldn't recognize music": "Não foi possível reconhecer a música", "More comfortable viewing at night": "Visualização mais confortável à noite", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Não funcionou. Dicas:\n- Verifique suas tags e configurações NSFW\n- Se não tiver uma tag em mente, digite um número de página", "Polling interval (m)": "Intervalo de atualização (m)", "%1 notifications": "%1 notificações", "Long break": "Pausa longa", "Keep system awake": "Manter sistema acordado", "Place at bottom": "Colocar no fundo", "Wallpaper safety enforced": "Segurança de papel de parede forçada", "Shutdown": "Desligar", "Game mode": "Modo de jogo", "No further instruction provided": "Nenhuma instrução adicional fornecida", "Add task": "Adicionar tarefa", "Policies": "Políticas", "Discharging:": "Descarregando:", "Translation goes here...": "Tradução vai aqui...", "Depends on workspace": "Depende do workspace", "Open recordings folder": "Abrir pasta de gravações", "Vertical": "Vertical", "Hide sussy/anime wallpapers": "Esconder wallpapers suspeitos/anime", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Aproveite! Você pode reabrir o app de boas-vindas a qualquer momento com Super+Shift+Alt+/. Para abrir as configurações, aperte Super+I", "Math result": "Resultado matemático", "Nothing here!": "Nada aqui!", "Font used for Nerd Font icons": "Fonte usada para ícones Nerd Font", "Illegal increment": "Incremento ilegal", "Microphone": "Microfone", "System uptime:": "Tempo de atividade:", "Automatically suspends the system when battery is low": "Suspende automaticamente o sistema quando a bateria está fraca", "Notifications": "Notificações", "Pick random from this folder": "Escolher aleatório desta pasta", "Interface Language": "Idioma da Interface", "Reject": "Rejeitar", "Clock style": "Estilo do relógio", "Click to show": "Clique para mostrar", "Hide clipboard images copied from sussy sources": "Esconder imagens da área de transferência de fontes suspeitas", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Wallpaper Anime SFW aleatório do Konachan\nImagem salva em ~/Pictures/Wallpapers", "Wind": "Vento", "Bottom": "Inferior", "Background": "Fundo", "Enter tags, or \"%1\" for commands": "Digite tags, ou \"%1\" para comandos", "Usage: %1tool TOOL_NAME": "Uso: %1tool NOME_FERRAMENTA", "Automatically hide": "Ocultar automaticamente", "Focusing": "Focando", "Charging:": "Carregando:", "Task description": "Descrição da tarefa", "See fewer": "Ver menos", "Enable translator": "Ativar tradutor", "Weather Service": "Serviço de Clima", "Settings": "Configurações", "Allow NSFW content": "Permitir conteúdo NSFW", "Always show numbers": "Sempre mostrar números", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Podem ser imagens ou partes da tela com alguma contenção.\nPode não ser sempre preciso.\nIsso é feito com um algoritmo de processamento de imagem local, sem IA.", "Current model: %1\nSet it with %2model MODEL": "Modelo atual: %1\nDefina com %2model MODELO", "System sound": "Som do sistema", "City name": "Nome da cidade", "Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep": "Escreva algo aqui...\nUse '-' para criar marcadores copiáveis, assim:\n\nFarm de Ovelhas\n- 4x Laje\n- 1x Barco\n- 4x Pó de Redstone\n- 1x Pistão Aderente\n- 1x Lâmpada do End\n- 4x Repetidor de Redstone\n- 1x Tocha de Redstone\n- 1x Ovelha", "Right to left": "Direita para esquerda", "Choose file": "Escolher arquivo", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Pode ser melhor se você cometer muitos erros de digitação,\nmas os resultados podem ser estranhos e falhar com siglas\n(ex: \"GIMP\" pode não retornar o editor de imagem)", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Comandos, editar configs, buscar.\nLeva um turno extra para mudar para modo de busca se necessário", "Media": "Mídia", "Constantly rotate": "Girar constantemente", "No applications": "Nenhum aplicativo", "Not connected": "Não conectado", "Use macOS-like symbols for mods keys": "Usar símbolos estilo macOS para mods de teclas", "Network": "Rede", "Useless buttons": "Botões inúteis", "Top-down": "De cima para baixo", "Sound input": "Entrada de som", "Close (Esc)": "Fechar (Esc)", "No pending tasks": "Nenhuma tarefa pendente", "Save chat": "Salvar chat", "Clear all": "Limpar tudo", "Unpin from Start": "Desafixar do Iniciar", "Rect": "Retângulo", "Aligns the date and quote to left, center or right depending on its position on the screen.": "Alinha data e citação à esquerda, centro ou direita dependendo da posição na tela.", "Overlay: Crosshair": "Overlay: Mira", "Circle to Search": "Circular para Pesquisar", "Online via %1 | %2's model": "Online via %1 | Modelo de %2", "(Plugged in)": "(Conectado)", "Earbang protection": "Proteção auditiva", "Edit quick toggles": "Editar botões rápidos", "Numbers font": "Fonte dos números", "Temperature must be between 0 and 2": "Temperatura deve ser entre 0 e 2", "Productivity": "Produtividade", "Eye protection": "Proteção ocular", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Você precisa inserir sua chave API Gemini primeiro.\nDigite /key na barra lateral para instruções.", "Minute hand": "Ponteiro de minutos", "Second precision": "Precisão de segundos", "Wallpaper selector": "Seletor de papel de parede", "Close": "Fechar", "Critical warning": "Aviso crítico", "Connect": "Conectar", "Polkit": "Polkit", "Locked": "Bloqueado", "Connected": "Conectado", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Permite abrir barras laterais clicando ou passando mouse nos cantos, independente da posição da barra", "Volume mixer": "Mixer de volume", "Done": "Feito", "12h am/pm": "12h am/pm", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Preço**: grátis. Dados usados para treinamento.\n\n**Instruções**: Logue na conta Google, permita o AI Studio criar projeto Google Cloud, volte e clique em Get API key", "Regenerate": "Regenerar", "Screen round corner": "Canto arredondado da tela", "Nerd font icons": "Ícones Nerd Font", "Unknown": "Desconhecido", "Critically low battery": "Bateria criticamente fraca", "Enable if you want clocks to show seconds accurately": "Ative se quiser que relógios mostrem segundos com precisão", "Break": "Pausa", "Quick markup (Ctrl+E)": "Marcação rápida (Ctrl+E)", "Use Levenshtein distance-based algorithm instead of fuzzy": "Usar algoritmo baseado em distância Levenshtein em vez de fuzzy", "Identify Music": "Identificar Música", "Fahrenheit unit": "Unidade Fahrenheit", "Scroll to change volume": "Rolar para mudar volume", "Number style": "Estilo do número", "Circle selection": "Seleção circular", "Workspaces shown": "Workspaces mostrados", "Actions": "Ações", "OK": "OK", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "O de hentai | Grande quantidade, muito NSFW, qualidade varia muito", "Rainbow": "Arco-íris", "Back": "Voltar", "Layers": "Camadas", "Random: osu! seasonal": "Aleatório: osu! seasonal", "Pause": "Pausar", "Dotfiles": "Dotfiles", "Services": "Serviços", "Title font": "Fonte do título", "Use varying shapes for password characters": "Usar formas variadas para caracteres de senha", "Group style": "Estilo de grupo", "Main font": "Fonte principal", "Show aim lines": "Mostrar linhas de mira", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Instruções**: Logue na conta Mistral, vá em Keys na barra lateral, clique em Create new key", "Change password": "Alterar senha", "Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages": "Obtenha os últimos recursos e melhorias de segurança com\na atualização mais recente.\n\n%1 pacotes", "Load prompt from %1": "Carregar prompt de %1", "On-screen keyboard": "Teclado virtual", "No API key set for %1": "Nenhuma chave API definida para %1", "Invalid model. Supported: \n```": "Modelo inválido. Suportados: \n```", "Command rejected by user": "Comando rejeitado pelo usuário", "Language": "Idioma", "Add": "Adicionar", "Save paths": "Caminhos de salvamento", "Lock screen": "Tela de bloqueio", "Light": "Claro", "Right": "Direita", "Edit": "Editar", "Draggable": "Arrastável", "Local Ollama model | %1": "Modelo local Ollama | %1", "Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")": "Mostrar modificadores e teclas em teclas múltiplas (ex: \"Ctrl + A\" em vez de \"Ctrl A\")", "Restart": "Reiniciar", "Move left": "Mover para esquerda", "Forget": "Esquecer", "More Bluetooth settings": "Mais configurações de Bluetooth", "Fonts": "Fontes", "Secured": "Seguro", "Utility buttons": "Botões utilitários", "Hibernate": "Hibernar", "Unknown Artist": "Artista Desconhecido", "Load chat": "Carregar chat", "Anti-flashbang": "Proteção de brilho", "Enabled": "Ativado", "Always": "Sempre", "Not secured": "Não seguro", "Invalid arguments. Must provide `command`.": "Argumentos inválidos. Deve fornecer `command`.", "Font family": "Família da fonte", "Page %1": "Página %1", "Resume": "Resumir", "Web": "Web", "Save chat to %1": "Salvar chat em %1", "Tint app icons": "Colorir ícones de app", "Color picker": "Seletor de cores", "Night Light | Right-click to toggle Auto mode": "Luz Noturna | Botão direito para alternar modo Auto", "Inactive": "Inativo" } ================================================ FILE: dots/.config/quickshell/ii/translations/ru_RU.json ================================================ { "Style: Blurred": "Размытие", "Unknown device": "Неизв. устройство", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "Измените позже с /dark, /light, /wallpaper в лаунчере\nЕсли цвета оболочки не меняются:\n 1. Откройте правую панель через Super+N\n 2. Нажмите «Рестарт Hyprland и Quickshell» в правом верхнем углу", "No pending tasks": "Тут пусто!", "Positioning": "Расположение", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Установить температуру (случайность) модели. Диапазон: 0–2 для Gemini, 0–1 для других. По умолчанию 0,5", "Critical warning": "Критический %", "Unknown Artist": "Неизвестный исполнитель", "Web search": "Найти", "Load prompt from %1": "Загрузка промпта из %1", "Attach a file. Only works with Gemini.": "Прикрепить файл (только Gemini)", "Reboot": "Перезагрузка", "API key:\n\n```txt\n%1\n```": "API-ключ:\n\n```txt\n%1\n```", "Pinned on startup": "Закрепить при включении", "Right": "Справа", "Reboot to firmware settings": "Загрузка в UEFI", "Automatically hide": "Автоскрытие", "To Do": "Задачи", "Full": "Полый", "Select Language": "Выбор языка", "Password": "Пароль", "Bluetooth devices": "Bluetooth-устройства", "Enable": "Вкл.", "Elements": "Элементы", "Start": "Пуск", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Случайные SFW аниме обои с Konachan\nСохраняется в ~/Pictures/Wallpapers", "The popular one | Best quantity, but quality can vary wildly": "Популярный | Больше всего контента, качество нестабильно", "System uptime:": "Аптайм:", "illogical-impulse Welcome": "Добро пожаловать в illogical-impulse", "Code saved to file": "Код сохранён", "Info": "Инфо", "Preferred wallpaper zoom (%)": "Зум обоев (%)", "Time": "Время", "Help & Support": "Помощь", "Bubble": "Пузырчатый", "Large images | God tier quality, no NSFW.": "Большие изображения | Отличное качество, без NSFW.", "Dark": "Тёмный", "Center clock": "Часы по центру экрана", "Search, calculate or run": "Поиск, расчёт, запуск", "Region height": "Высота области", "Load chat": "Загрузить чат", "Gives the model search capabilities (immediately)": "Вкл. поиск для модели (сразу)", "Depends on workspace": "Зависит от пространства", "Enter password": "Введите пароль", "Local only": "Только локальные", "at": "в", "Math": "Математика", "Consider plugging in your device": "Подключите зарядку", "Workspaces shown": "Видимые пространства", "Place the corners to trigger at the bottom": "Триггер-углы снизу", "No API key\nSet it with /key YOUR_API_KEY": "Нет API-ключа\nУстановите: /key ВАШ_КЛЮЧ", "Auto (System)": "Авто (система)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Стрелки - навигация, Enter - выбор\nEsc или клик - отмена", "Critically low battery": "Критически низкий заряд", "Open editor": "Открыть редактор", "%1 notifications": "%1 уведомлений", "Region width": "Ширина области", "Max allowed increase": "Макс. разница", "Enable translator": "Переводчик в левой панели", "Constantly rotate": "Постоянное вращение", "Automatically suspends the system when battery is low": "Авто-сон при низком зар.", "Cannot find a GPS service. Using the fallback method instead.": "GPS не найден. Резервный метод.", "Qt apps": "Qt-приложений", "Color picker": "Пипетка", "Interface": "Интерфейс", "Tint app icons": "Тонировать иконки", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Выбор языка интерфейса.\n«Авто» - системная локаль.", "Local Ollama model | %1": "Локальная модель Ollama | %1", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Исп: %1superpaste КОЛИЧЕСТВО[i]\nДобавьте i для изображений\nПримеры:\n%1superpaste 4i - последние 4 изображения\n%1superpaste 7 - последние 7 записей", "Audio": "Звук", "Corner style": "Стиль угла", "No media": "Нет медиа", "Unknown function call: %1": "Неизвестный вызов функции: %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Онлайн | Модель %1 | Быстрые, структурированные ответы. Недостатки: может уклоняться от задач; иногда придумывает вызовы функций", "Volume": "Громкость", "Medium": "Средний", "Copy code": "Копировать код", "Exceeded max allowed": "Превышен максимум", "Keep right sidebar loaded": "Держать правую панель в ОЗУ", "Left": "Слева", "Rect": "Прямоугольное", "Lap": "Круг", "Screen snip": "Скриншот", "Reset": "Ресет", "Back": "Назад", "Dark/Light toggle": "Тоггл темы", "12h am/pm": "12ч am/pm", "Download complete": "Загрузка завершена", "Enable blur": "Включить размытие", "Second hand": "Стиль секундной стрелки", "Bar & screen": "Панель и экран", "Discharging:": "Разряд:", "Up %1": "Аптайм %1", "Hour hand": "Стиль часовой стрелки", "Clear chat history": "Очистить лог чата", "Fruit Salad": "Фруктовый салат", "%1 Safe Storage": "Безопасное хранилище %1", "Hibernate": "Гибернация", "Delete": "Удалить", "OK": "OK", "Settings": "Настройки", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Обычно безопасно и нужно для браузера и AI-панели\nПолезно при блокировке при запуске вместо менеджера дисплея (GDM, SDDM и т.д)", "Use Hyprlock (instead of Quickshell)": "Использовать Hyprlock (вместо Quickshell)", "Crosshair code (in Valorant's format)": "Код прицела (формат Valorant)", "Silent": "Тихий", "Useless buttons": "Бесполезные кнопки", "Hover to reveal": "Наведите для раскрытия", "Wallpaper & Colors": "Обои и цвета", "Auto": "Авто", "Visibility": "Видимость", "Shell & utilities": "Оболочки и утилит", "Hollow": "Полый", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "Сист. диалог файлов\nПКМ - сделать поведением по умолчанию", "On-screen display": "Экранное отображение", "Dotfiles": "Dotfile", "Search wallpapers": "Поиск обоев", "Mic toggle": "Тоггл микрофона", "Input": "Вход", "Also unlock keyring": "Также разблокировать связку ключей", "Configuration": "Конфиг", "Keep system awake": "Оставлять систему включённой", "Unknown command:": "Неизвестная команда:", "Anime boorus": "Аниме-боору", "To Do:": "Задачи:", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Gemini определяет тип обоев и подбирает пресет.\nСначала укажите API-ключ Gemini в левой панели.\nИзображения уменьшаются для производительности -\nне выбирайте обои с конфиденциальными данными.", "Bottom": "Снизу", "Clear the current list of images": "Очистить список изображений", "Sunrise": "Рассвет", "Show app icons": "Показывать иконки приложений", "Format": "Формат", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Убедитесь, что плеер поддерживает MPRIS\nили отключите в конфиге «filterDuplicatePlayers»", "Pause": "Пауза", "Desktop": "Рабочий стол", "Conflicts with the shell's system tray implementation": "Конфликт с треем оболочки", "Your package manager is running": "Запущен пакетный менеджер", "Conflicts with the shell's notification implementation": "Конфликт с уведомлениями оболочки", "Unknown Album": "Неизв. альбом", "Pick wallpaper image on your system": "Выбрать обои из файлов", "Used:": "Исп:", "Cheat sheet": "Шпаргалка", "Clock style": "Стиль часов", "Paired": "Сопряжён", "Documentation": "Документация", "No": "Нет", "Pills": "Таблеточный", "Thought": "Размышление", "When this is off you'll have to click": "При отключении требуется клик", "Logout": "Выйти", "Tip: Close a window with Super+Q": "Совет: Super+Q - закрыть окно", "Finished tasks will go here": "Выполненные задачи будут тут", "Terminal: Harmony (%)": "Терминал: гармония (%)", "Corner open": "Открытие из угла", "Shell conflicts killer": "Устранение конфликтов", "Clean stuff | Excellent quality, no NSFW": "Чисто | Отличное качество, без NSFW", "Scroll to change volume": "Скролл - громкость", "Wind": "Ветер", "API key is set\nChange with /key YOUR_API_KEY": "API-ключ установлен\nИзменить: /key ВАШ_КЛЮЧ", "Neutral": "Нейтральность", "12h AM/PM": "12ч AM/PM", "Number show delay when pressing Super (ms)": "Задержка номеров Super (мс)", "Fill": "Залитый", "Always show numbers": "Всегда показывать номера", "Dot": "Точка", "Provider set to": "Провайдер:", "Unknown Title": "Неизв. название", "Anime": "Аниме", "Refreshing (manually triggered)": "Обновление (вручную)", "Dock": "Докбар", "Require password to power off/restart": "Пароль для выключения/перезагрузки", "Line": "Линейный", "Weather": "Погода", "All-rounder | Good quality, decent quantity": "Универсал | Хорошее качество, много контента", "Scale (%)": "Масштаб (%)", "Copy": "Копировать", "Usage": "Исп.", "Set the tool to use for the model.": "Задать инструмент модели", "Disable tools": "Выкл. инструменты", "Connect": "Подключить", "Allow NSFW": "Разрешить NSFW", "Registration failed. Please inspect manually with the warp-cli command": "Ошибка регистрации. Проверьте вручную командой warp-cli", "Time to full:": "До полного заряда:", "Session": "Сессия", "Services": "Службы", "Nothing here!": "Тут пусто!", "Overview": "Обзор", "Random: osu! seasonal": "Случайные: сезонные osu!", "If you want to somehow use fingerprint unlock...": "Если хотите разблокировку по отпечатку пальца...", "Minute hand": "Стиль минутной стрелки", "Notifications": "Уведомления", "Enable if you want clocks to show seconds accurately": "Включите для отображения секунд на часах", "Timer": "Таймер", "System prompt": "Системный промпт", "Classic": "Классический", "Close": "Закрыть", "Disconnect": "Отключить", "Go to source (%1)": "Перейти к источнику (%1)", "EasyEffects | Right-click to configure": "EasyEffects | ПКМ для настройки", "Forget": "Забыть", "Output": "Выход", "Date style": "Стиль даты", "System": "Система", "Usage: %1tool TOOL_NAME": "Исп: %1tool ИМЯ_ИНСТРУМЕНТА", "Workspaces": "Пространства на панели", "Calendar": "Дата", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Инструкция**: войдите в Mistral, откройте «Ключи» на боковой панели, нажмите «Создать ключ»", "Volume limit": "Порог звука", "Sunset": "Закат", "Dial style": "Стиль циферблата", "Hi there! First things first...": "Привет! Начнём с главного...", "Save chat to %1": "Сохранить чат в %1", "Security": "Безопасность", "Total token count\nInput: %1\nOutput: %2": "Всего токенов\nВход: %1\nВыход: %2", "Cancel wallpaper selection": "Отменить выбор обоев", "Terminal: Harmonize threshold": "Терминал: порог гармонизации", "Be patient...": "Подождите...", "Utility buttons": "Служебные кнопки", "Tonal Spot": "Тональное пятно", "Prevents abrupt increments and restricts volume limit": "Плавное изменение и ограничение громкости", "Set the current API provider": "Задать API-провайдер", "Connection failed. Please inspect manually with the warp-cli command": "Ошибка подключения. Проверьте вручную командой warp-cli", "Networking": "Сеть", "Tint icons": "Тонировать иконки", "Low battery": "Низкий заряд", "Make icons pinned by default": "Закреплять все иконки", "Get the next page of results": "Следующая стр. результатов", "Invalid API provider. Supported: \n-": "Неверный провайдер. Поддерживаются:\n-", "Show \"Locked\" text": "Показывать текст «Заблокировано»", "Not visible to model": "Не видно модели", "Lock screen": "Экран блокировки", "Save to Downloads": "В «Загрузки»", "Expressive": "Выразительность", "Jump to current month": "Перейти к тек. месяцу", "Bold": "Жирный", "Waifus only | Excellent quality, limited quantity": "Только вайфу | Отличное качество, мало контента", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Переключить тему\n(применяется при выборе обоев)", "Visualize region": "Визуализация области", "Quote": "Цитата", "Sleep": "Сон", "Hit \"/\" to search": "Нажмите «/» для поиска", "Hug": "Захват", "Report a Bug": "Сообщить об ошибке", "Precipitation": "Осадки", "Model set to %1": "Модель: %1", "Rows": "Строки", "Top": "Сверху", "Long break": "Длинный перерыв", "Superpaste": "Superpaste", "Screen round corner": "Скруглённые углы", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Онлайн | Модель Google\nНовее, медленнее предшественника, но более качественные ответы", "Rainbow": "Радуга", "Weeb": "Аниме-боору", "Large language models": "Большие языковые модели", "Online models disallowed\n\nControlled by `policies.ai` config option": "Онлайн-модели запрещены\n\nУправляется параметром `policies.ai`", "Policies": "Политики", "Temperature must be between 0 and 2": "Температура должна быть от 0 до 2", "Automatic suspend": "Авто-сон", "Extra wallpaper zoom (%)": "Дополнительный зум обоев (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | ПКМ для настройки", "Edit directory": "Изменить папку", "Action": "Действие", "Search": "Поиск", "Tip: right-clicking a group\nalso expands it": "Совет: ПКМ по группе - раскрыть", "Bar": "Панель", "Clipboard": "Буфер обмена", "Stopwatch": "Секундомер", "Enter text to translate...": "Введите текст для перевода...", "App": "Приложение", "Sides": "Стороны", "No active player": "Нет активного плеера", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Не все параметры доступны здесь. Проверьте конфиг через кнопку «Конфиг» в углу или откройте %1 вручную.", "Math result": "Результат вычисления", "Fidelity": "Точность", "Prefixes": "Префиксы", "Terminal": "Терминала", "Incorrect password": "Неверный пароль", "Line-separated": "Разделение по строкам", "Always": "Всегда", "☕ Break: %1 minutes": "☕ Перерыв: %1 мин", "Depends on sidebars": "Зависит от панелей", "Tool set to: %1": "Инструмент: %1", "Save chat": "Сохранить чат", "Keybinds": "Бинды", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Лучше при частых опечатках,\nно результаты могут быть странными - аббревиатуры не всегда работают\n(например, «GIMP» может не найти редактор)", "Choose model": "Выбрать модель", "Base URL": "Базовый URL", "Float": "Флоат", "Invalid arguments. Must provide `command`.": "Неверные аргументы. Укажите `command`.", "Fully charged": "Полностью заряжена", "Earbang protection": "Защита ушей", "Low warning": "Увед. о низком %", "Advanced": "Прочее", "Scroll to change brightness": "Скролл - яркость", "Loaded the following system prompt\n\n---\n\n%1": "Загружен системный промпт\n\n---\n\n%1", "Show next time": "Показать в след. раз", "Current tool: %1\nSet it with %2tool TOOL": "Текущий инструмент: %1\nУстановить: %2tool ИНСТРУМЕНТ", "Unread indicator: show count": "Счётчик непрочитанных", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Не удалось. Советы:\n- Проверьте теги и настройки NSFW\n- Если тег неизвестен, введите номер страницы", "Dots": "С точками", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Микшер", "Config file": "Конфиг", "API key set for %1": "API-ключ установлен для %1", "Shell command": "Команда оболочки", "Reload Hyprland & Quickshell": "Рестарт Hyprland и Quickshell", "Resources": "Ресурсы", "Brightness": "Яркость", "Unknown": "Неизвестно", "Polling interval (ms)": "Интервал опроса (мс)", "Lock": "Блокировка", "Thinking": "Размышление", "Approve": "Одобрить", "Unfinished": "Незавершённые", "Random: Konachan": "Случайные: Konachan", "Connected": "Подключено", "Wallpaper safety enforced": "Фильтр безоп. обоев вкл", "Invalid arguments. Must provide `key` and `value`.": "Неверные аргументы. Укажите `key` и `value`.", "24h": "24ч", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Открытие панелей через углы экрана", "Bar style": "Стиль панели", "Load:": "Исп:", "Open file link": "Открыть ссылку на файл", "Ignored if terminal theming is not enabled": "Игнорируется без темы терминала", "Shutdown": "Выключить", "Hour marks": "Деления часов", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Случайный сезонный фон osu!\nСохраняется в ~/Pictures/Wallpapers", "Current model: %1\nSet it with %2model MODEL": "Текущая модель: %1\nУстановить: %2model МОДЕЛЬ", "Connect to Wi-Fi": "Подкл. к Wi-Fi", "... and %1 more": "... и ещё %1", "Cookie clock settings": "Настройки «Cookie»-часов", "Looks a bit softer and more consistent with different number of sides,\nbut has less impressive morphing": "Выглядит мягче и уместнее с разным количеством углов,\nно сжатие может выглядить менее впечатляюще", "Makes the clock always rotate. This is extremely expensive\n(expect 50% usage on Intel UHD Graphics) and thus impractical.": "Часы будут постоянно вращаться. Это крайне ресурсозатратно\n(примерно 50% использования Intel UHD Graphics) и фактически бесполезно.", "Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons": "Может быть активировано только в режимах 'С точками' и 'Полностью залитый' по эстетическим причинам", "Can't be turned on when using 'Numbers' dial style for aesthetic reasons": "Не может быть включено, когда используется стиль 'С числами' по эстетическим причинам", "Brightness and volume": "Яркость и громкость", "Choose file": "Выбор обоев", "Invalid model. Supported: \n```": "Неверная модель. Поддерживаемые: \n```", "Task Manager": "Диспетчер задач", "Charging:": "Заряд:", "Illegal increment": "Недопустимый шаг", "Total:": "Всего:", "or": "или", "Battery": "Батарея", "Timeout duration (if not defined by notification) (ms)": "Время уведомления (мс)", "Cancel": "Отмена", "Locked": "Заблокировано", "Temperature: %1": "Температура: %1", "Hover to trigger": "Навед. для триггера", "Command rejected by user": "Команда отклонена", "User agent (for services that require it)": "User agent (для сервисов)", "Saved to %1": "Сохранено в %1", "Emojis": "Эмодзи", "Color generation": "Генерация цвета для...", "Welcome app": "Приветствие", "Humidity": "Влажность", "Page %1": "Страница %1", "Feels like %1": "Ощущается как %1", "Distro": "Дистрибутив", "Transparency": "Включить прозрачность", "%1 • %2 tasks": "%1 • %2 задач", "Markdown test": "Тест Markdown", "Invalid tool. Supported tools:\n- %1": "Неверный инструмент. Поддерживаемые:\n- %1", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Хентай | Много контента, много NSFW, качество нестабильно", "Bluetooth": "Bluetooth", "Resume": "Продолжить", "Work safety": "Безопасность", "Temperature\nChange with /temp VALUE": "Температура\nИзменить: /temp ЗНАЧЕНИЕ", "Terminal: Foreground boost (%)": "Терминал: усиление переднего плана (%)", "Night Light | Right-click to toggle Auto mode": "Ночной режим | ПКМ для авторежима", "Closet": "Скрыто", "Yes": "Да", "Columns": "Столбцы", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Установить API-ключ командой %4\n\nПосмотреть ключ: передайте «get»
\n\n### Для %1:\n\n**Ссылка**: %2\n\n%3", "Kill conflicting programs?": "Убить конфликтующие процессы?", "For storing API keys and other sensitive information": "Для хранения API-ключей и конфид. данных", "Reject": "Отклонить", "Set API key": "Установить API-ключ", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Заметки для Zerochan:\n- Необходимо указать цвет\n- Укажите имя пользователя в `sidebar.booru.zerochan.username`. [Без этого вас могут заблокировать](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous)!", "Content": "В контенте", "Pomodoro": "Помодоро", "Vertical": "Вертикальный", "Pick a wallpaper": "Выбрать обои", "Load chat from %1": "Загрузить чат из %1", "Launch on startup": "Запускать при старте системы", "Add": "Добавить", "Style: general": "Основное", "Use Levenshtein distance-based algorithm instead of fuzzy": "Алг. Левенштейна вместо нечёткого", "Shell & utilities theming must also be enabled": "Также нужна тема оболочки", "Workspace": "Пространство", "Translator": "Перевод", "Free:": "Есть:", "🌿 Long break: %1 minutes": "🌿 Длинный перерыв: %1 мин", "Value scroll": "Скролл значений", "Bar position": "Положение панели", "Language": "Язык", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Текущий API: %1\nУстановить: %2mode ПРОВАЙДЕР", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "На большинстве устройств можно зажать кнопку питания для принудительного выключения\nЭто лишь немного снижает риск случайного выключения", "AI": "ИИ", "Task description": "Описание задачи", "Add task": "Добавить задачу", "Donate": "Поддержать", "Disable NSFW content": "Выкл. NSFW", "Set the system prompt for the model.": "Задать системный промпт", "Done": "Готово", "Focus": "Фокус", "View Markdown source": "Исходник Markdown", "Border": "Круговой", "Temperature set to %1": "Температура: %1", "Message the model... \"%1\" for commands": "Сообщение модели... «%1» для команд", "Translation goes here...": "Перевод появится здесь...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Держит правую панель в памяти для быстрого открытия\n(~15 МБ ОЗУ постоянно). Задержка зависит от произв. системы.\nКастомное ядро (linux-cachyos) может помочь", "For desktop wallpapers | Good quality": "Для обоев рабочего стола | Хорошее качество", "🔴 Focus: %1 minutes": "🔴 Фокус: %1 мин", "The current system prompt is\n\n---\n\n%1": "Текущий системный промпт\n\n---\n\n%1", "About": "Система", "Quick": "Главное", "General": "Общее", "UV Index": "УФ-индекс", "Force dark mode in terminal": "Тёмная тема в терминале", "%1 characters": "%1 символов", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Цена**: бесплатно. Данные используются для обучения.\n\n**Инструкция**: войдите в Google, разрешите AI Studio создать проект, вернитесь и нажмите «Получить API-ключ»", "Monochrome": "Монохром", "Details": "Детали", "Issues": "Проблемы", "Keyboard toggle": "Тоггл клавиатуры", "Download": "Скачать", "%1 does not require an API key": "%1 не требует API-ключа", "Style & wallpaper": "Стиль и обои", "Second precision": "С секундами", "Group style": "Стиль групп", "Break": "Перерыв", "Run": "Запуск", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Приятного использования! Открыть приветствие: Super+Shift+Alt+/. Настройки: Super+I", "Interface Language": "Язык интерфейса", "Game mode": "Игровой режим", "Usage: %1save CHAT_NAME": "Исп: %1save ИМЯ_ЧАТА", "Thin": "Тонкий", "Light": "Светлый", "When not fullscreen": "Не в фулл-скрине", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Команды, редактирование конфигов, поиск.\nПри необходимости переключается в режим поиска", "Privacy Policy": "Политика конфиденциальности", "Timeout (ms)": "Таймаут (мс)", "Allow NSFW content": "Разрешить NSFW", "Edit": "Изменить", "Digits in the middle": "Цифры по центру", "Weather Service": "Погодный сервис", "Background": "Обои", "Pick random from this folder": "Случайный из папки", "Pressure": "Давление", "Save": "Сохранить", "Time to empty:": "До разряда:", "Place at bottom": "Разместить снизу", "Switched to search mode. Continue with the user's request.": "Переключено в поиск. Продолжайте.", "Performance Profile toggle": "Тоггл профиля произв.", "Sidebars": "Боковые панели", "Usage: %1load CHAT_NAME": "Исп: %1load ИМЯ_ЧАТА", "Auto styling with Gemini": "Авто-стиль с Gemini", "No API key set for %1": "API-ключ не установлен для %1", "Enter tags, or \"%1\" for commands": "Введите теги или «%1» для команд", "Discussions": "Обсуждения", "Tray": "Трей", "Numbers": "С числами", "Intelligence": "ИИ", "Open network portal": "Открыть сетевой портал", "No further instruction provided": "Инструкция не предоставлена", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Языка нет или перевод неполный?\nМожно сгенерировать перевод через Gemini.\n1. Откройте левую панель (Super+A), выберите модель Gemini\n2. Введите /key и следуйте инструкциям\n3. Введите /key ВАШ_КЛЮЧ\n4. Введите код локали ниже и нажмите «Сгенерировать»", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Код локали, напр. ru_RU, de_DE, zh_CN...", "Select language": "Выбрать язык", "Generate translation with Gemini": "Сгенерировать перевод с Gemini", "Generating...\nDon't close this window!": "Генерация...\nНе закрывайте окно!", "Generate\nTypically takes 2 minutes": "Сгенерировать\nОбычно ~2 мин", "Use system file picker": "Системный файл-менеджер", "Wallpaper selector": "Выбор обоев", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Если предыдущий параметр выключен, а этот включён,\nможно навести на край угла для открытия панели,\nоставшаяся область работает для прокрутки громкости/яркости", "Copy path": "Копировать путь", "Windows": "Окон", "Regenerate": "Перегенерировать", "Microphone": "Микрофон", "Unmuted": "Размут", "System sound": "Системный звук", "Enable now": "Вкл. сейчас", "Night Light": "Ночной свет", "Show aim lines": "Показать линии прицела", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Зачем это нужно:\nПри ненулевых значениях не срабатывает при достижении угла\nпо горизонтальному краю, но срабатывает по вертикальному", "Please charge!\nAutomatic suspend triggers at %1%": "Зарядите устройство!\nАвто-сон при %1%", "Example use case: eroge on one workspace, dark Discord window on another": "Пример: eroge на одном пространстве, тёмный Discord на другом", "Couldn't recognize music": "Музыка не распознана", "Automatic": "Автоматически", "Hint target regions": "Обводка областей", "Eye protection": "Защита глаз", "Layers": "Слоёв", "Listening...": "Слушаю...", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "ЛКМ - вкл/выкл\nПКМ - изменить размер\nПрокрутка - поменять позицию", "Identify Music": "Определить музыку", "Quick toggles": "Быстрые тогглы", "Hide sussy/anime wallpapers": "Скрывать подозрительные/аниме обои", "Android": "Android", "Show": "Показать", "Muted": "Мут", "Audio input | Right-click for volume mixer & device selector": "Аудиовход | ПКМ - микшер и выбор устройства", "Region selector (screen snipping/Google Lens)": "Выбор области (Скрин / Google Lens)", "Total duration timeout (s)": "Макс. длительность (сек)", "Music Recognition": "Поиск музыки", "Night Light | Right-click to configure": "Ночной свет | ПКМ для настройки", "Anti-flashbang (experimental)": "Анти-вспышка (эксперим)", "Digital clock settings": "Настройки цифровых часов", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Могут быть изображения или части экрана с содержимым.\nМожет быть неточно.\nИспользуется локальный алгоритм обработки изображений, без ИИ.", "Polling interval (m)": "Интервал опроса (мин)", "Inactive": "Неактивно", "Authentication": "Аутентификация", "Full warning": "Уведомление о полном заряде при проценте", "Power Profile": "Профиль питания", "Content region": "Обл. контента", "Internet": "Интернет", "Record": "Запись", "Circle selection": "Круговое выдел.", "Edit quick toggles": "Изменить быстрые тогглы", "Virtual Keyboard": "Вирт. клавиатура", "Music Recognized": "Музыка распознана", "EasyEffects": "EasyEffects", "Make sure you have songrec installed": "Убедитесь, что songrec установлен", "Dark Mode": "Тёмный режим", "Animate time change": "Анимировать смену времени", "It may take a few seconds to update": "Обновление займёт неск. секунд", "Polling interval (s)": "Интервал опроса (сек)", "Perhaps what you're listening to is too niche": "Возможно, это слишком нишевая музыка", "Fahrenheit unit": "По Фаренгейту", "Sliders": "Ползунки", "Roman": "Римский", "Number style": "Стиль чисел", "Intensity": "Интенсивность", "Google Lens": "Google Lens", "Circle": "Круговое", "Hide clipboard images copied from sussy sources": "Скрывать изображения из подозрительных источников", "Scroll to Bottom": "В конец", "Enabled": "Вкл.", "Nothing": "Тут пусто!", "Audio input": "Аудиовход", "with vertical offset": "Смещением по вертикали", "Padding": "Отступ", "Please unplug the charger": "Отключите зарядку", "Show notifications": "Показ. уведомления", "Path copied": "Путь скопирован", "On-screen keyboard": "Экр. клавиатура", "City name": "Название города (латиница)", "Click to cycle through power profiles": "Клик - смена профиля питания", "Recognize music | Right-click to toggle source": "Найти музыку | ПКМ - сменить источник", "Use old sine wave cookie implementation": "Старая реализация Cookie (синусоида)", "Rectangular selection": "Обычное выделение", "Audio output": "Аудиовыход", "Circle to Search": "Обведите для поиска", "Audio output | Right-click for volume mixer & device selector": "Аудиовыход | ПКМ - микшер и выбор устройства", "Enable GPS based location": "Вкл. геолокацию по GPS", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Сначала введите API-ключ Gemini.\nНаберите /key в панели для инструкций.", "Sounds": "Звуки", "Active": "Активно", "Keep awake": "Не давать спать", "Auto,": "Авто,", "Normal": "Арабский", "Force hover open at absolute corner": "Форс-открытие по углу", "Open the shell config file\nAlternatively right-click to copy path": "Открыть конфиг оболочки\nПКМ - скопировать путь", "Recognize music": "Найти музыку", "Stroke width": "Толщина обводки", "Use varying shapes for password characters": "Разные фигуры для символов пароля", "Battery full": "Батарея заряжена", "Image source": "Источник изображения (ссылка)", "Restart": "Перезапустить", "Close (Esc)": "Закрыть (Esc)", "Actions": "Действия", "Font used for Nerd Font icons": "Шрифт иконок Nerd Font", "CPU": "ЦП", "Online | Google's model\nPro-level intelligence at the speed and pricing of Flash.": "Онлайн | Модель Google\nИнтеллект уровня Pro по скорости и цене Flash.", "End session": "Завершить сессию", "No applications": "Тут пусто!", "Description font size": "Размер шрифта описания", "Other": "Другое", "More volume settings": "Доп. настройки звука", "Split buttons": "Разделённые кнопки", "Open": "Открыть", "Output device": "Устр. вывода", "No new notifications": "Нет новых уведомлений", "Pin to taskbar": "Закрепить на панели", "Pin to Start": "Закрепить в пуске", "Not secured": "Не защищено", "Font family name (e.g., Google Sans Flex)": "Шрифт (напр, Google Sans Flex)", "Digital": "Цифровой", "Notes": "Заметки", "File Explorer": "Проводник", "Stopping...": "Остановка...", "Used for code and terminal": "Для кода и терминала", "Video Recording Path": "Путь для видеозаписей", "Enjoy your empty sidebar...": "Наслаждайтесь пустой панелью...", "Cookie": "Cookie MD3", "Save paths": "Сохранить пути", "Productivity": "Продуктивность", "Not connected": "Не подключено", "Battery: %1%2": "Батарея: %1%2", "Command": "Команда", "Tooltips": "Подсказки", "Show hidden icons": "Показать скрытые значки", "Used for headings and titles": "Для заголовков", "Top-down": "Сверху вниз", "of %1": "из %1", "System updates (Arch only)": "Апдейты системы (только Arch)", "Apps": "Приложения", "Main font": "Основной шрифт", "Parallax": "Параллакс", "Move left": "Переместить влево", "Anti-flashbang": "Анти-вспышка", "Desktop %1": "Рабочий Стол %1", "%1\nInternet access": "%1\nДоступ в интернет", "(Plugged in)": "(Подключено)", "Off": "Выкл", "Overlay: General": "Игровой оверлей", "Enable opening zoom animation": "Анимация зума при открытии", "More Internet settings": "Доп. сетевые настройки", "Nerd font icons": "Иконки Nerd Font", "Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")": "Показывать модификаторы с разделителем (напр, «Ctrl + A» вместо «Ctrl A»)", "Change password": "Изменить пароль", "Check interval (mins)": "Интервал пров. (мин)", "On": "Вкл", "Task View": "Просм. задач", "Secured": "Защищено", "Click to show": "Зажать для раскрытия", "Center icons": "Иконки по центру", "Rectangle": "Прямоугол.", "Creativity": "Творчество", "Sound output": "Звуковой микшер", "Copy region (LMB) or annotate (RMB)": "Копировать область (ЛКМ) или разметить (ПКМ)", "e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc": "напр. 󰘴 для Ctrl, 󰘵 для Alt, 󰘶 для Shift и т.д.", "Unpin from taskbar": "Открепить от панели", "Snipping area": "Область скриншота", "Font roundness": "Скруглённость", "Numbers font": "Шрифт чисел", "Move right": "Переместить вправо", "Unknown Application": "Неизв. приложение", "Used for decorative/expressive text": "Для декоративного / выразительного текста", "Used for reading large blocks of text": "Для чтения больших блоков", "Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar": "Введите /key для онлайн-моделей\nCtrl+O - расширить панель\nCtrl+P - закрепить панель\nCtrl+D - открепить панель", "Health:": "Здоровье:", "Widgets": "Виджеты", "Saved": "Сохранено", "Emoji": "Эмодзи", "Font family name (e.g., JetBrains Mono NF)": "Шрифт (напр, JetBrains Mono NF)", "Snip": "Скриншот", "Font weight": "Насыщенность ", "More Bluetooth settings": "Доп. настройки Bluetooth", "Recognize text": "Распознать текст", "Pinned": "Закреплено", "Unpin from Start": "Открепить из пуска", "Adjust the color temperature": "Цветовая температура", "Han chars": "Иероглифы", "Show only when locked": "Отображать только при блокировке", "Widget: Weather": "Виджет погоды", "Right to left": "Справа налево", "New desktop": "Новый рабочий стол", "Local account": "Локальная уч. запись", "Super key symbol": "Символ клавиши Super", "Used for displaying numbers": "Для отображения чисел", "Fonts": "Шрифты", "Left to right": "Слева направо", "Set FPS limit": "Лимит FPS", "Draggable": "Перетаскиваемый", "Turn on from sunset to sunrise": "От заката до рассвета", "Do you want to allow this app to make changes to your device?": "Разрешить приложению изменять устройство?", "Balance brightness based on content": "Баланс яркости по содержимому", "Font width and roundness settings are only available for some fonts like Google Sans Flex": "Ширина и скруглённость - только для некоторых шрифтов (напр, Google Sans Flex)", "Record region": "Запись обл.", "You can also manually edit cheatsheet.superKey": "Также вы можете вручную отредактировать cheatsheet.superKey", "Sign out": "Выйти", "Overlay: Crosshair": "Оверлей: прицел", "Shut down": "Выключить", "Show this window on all desktops": "На всех рабочих столах", "Quick markup (Ctrl+E)": "Быстрая разметка (Ctrl+E)", "Sound input": "Звуковой вход", "Manage accounts": "Упр. учётными записями", "+%1 notifications": "+%1 уведомлений", "Font family": "Семейство шрифта", "RAM": "ОЗУ", "Commands": "Команды", "Title font": "Шрифт заголовка", "Most busy": "Более загруж.", "Press Super+G to open the overlay and pin the crosshair": "Super+G - открыть оверлей и закрепите прицел", "Search for apps": "Поиск приложений", "See fewer": "Показать меньше", "Utilities & Tools": "Утилиты и инструменты", "Speakers (%1): %2": "Динамики (%1): %2", "Manage my account": "Упр. аккаунтом", "Use symbols for function keys": "Спец. символы для особых клавиш", "Aligns the date and quote to left, center or right depending on its position on the screen.": "Выравнивание по позиции на экране.", "Darken screen": "Затемнить экран", "Move to front": "На перед. план", "Overlay: Floating Image": "Оверлей: изображение", "OutlineVpn": "OutlineVpn", "AmneziaVPN": "AmneziaVPN", "Screenshot Path (leave empty to just copy)": "Путь снимков (пусто = только копировать)", "Open recordings folder": "Открыть папку записей", "Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep": "Пишите здесь...\nИспользуйте '-' для пунктов:\n\nПример\n- 4x Плиты\n- 1x Лодка\n- 4x Редстоуна\n- 1x Липкий поршень\n- 1x Стержень энда\n- 4x Повторитель\n- 1x Факел\n- 1x Овца", "Keybind font size": "Размер шрифта кейбиндов", "All": "Все", "Used for general UI text": "Для общего текста UI", "Media": "Медиа", "Enable update checks": "Вкл. проверку обновлений", "Widget: Clock": "Виджет: часы", "Clock style (locked)": "Стиль часов (на заблок. экране)", "Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"": "Заменить 󱕐 на «Scroll ↓», 󱕑 на «Scroll ↑», L󰍽 на «LMB», R󰍽 на «RMB», 󱕒 на «Scroll ↑/↓» и ⇞/⇟ на «Page_↑/↓»", "Monospace font": "Моноширинный шрифт", "Saving...": "Сохранение...", "Best match": "Лучшее совпадение", "Use macOS-like symbols for mods keys": "Символы в стиле macOS", "There might be a download in progress. Check your Downloads folder.": "Возможно, идёт загрузка. Проверьте «Загрузки».", "Web": "Веб", "Reading font": "Шрифт для чтения", "Enter a valid number": "Введите корректное число", "Image search": "Поиск изображений", "Use symbols for mouse": "Символы для мыши", "Font family name (e.g., Readex Pro)": "Шрифт (напр, Readex Pro)", "Font family name (e.g., Space Grotesk)": "Шрифт (напр, Space Grotesk)", "Wi-Fi": "Wi-Fi", "Clear all": "Очистить всё", "More comfortable viewing at night": "Комфортный просмотр ночью", "Close all windows": "Закрыть все окна", "Bottom-up": "Снизу вверх", "%1 mins": "%1 мин", "Font width": "Ширина шрифта", "Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages": "Новые функции и улучшения безоп.\n\n%1 пакетов", "Use adaptive alignment": "Адаптивное выравн.", "e.g. 󱊫 for F1, 󱊶 for F12": "напр. 󱊫 для F1, 󱊶 для F12", "Focusing": "Фокусировка", "Search with Google Lens": "Поиск через Google Lens", "Expressive font": "Выразительный шрифт", "VPN": "VPN", "Text extractor": "Извлечение текста", "Least busy": "Менее загруж.", "Command-line-invoked Action": "Действие из ком. строки", "Show date": "Показывать дату", "Window": "Окно", "Network": "Сеть", "Input device": "Устр. ввода", "Polkit": "Polkit", "Font size": "Размер шрифта", "Swap": "Своп", "Starting...": "Запуск...", "Close window": "Закрыть окно", "Sound effects": "Звуковые эффекты", "Last refresh: %1": "Обновлено: %1", "We": "Ср/*keep*/", "Mo": "Пн/*keep*/", "Su": "Вс/*keep*/", "Th": "Чт/*keep*/", "Tu": "Вт/*keep*/", "Sa": "Сб/*keep*/", "Fr": "Пт/*keep*/", "Font family name": "Назв. семейства", "Pin": "Закреп", "Unpin": "Откреп" } ================================================ FILE: dots/.config/quickshell/ii/translations/tools/README.md ================================================ # Translation Management Tool Suite This suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions. ## Tool Components ### 1. `translation-manager.py` - Main Translation Manager - Extract translatable texts - Compare and update translation files - Interactive addition/removal of translation keys ### 2. `translation-cleaner.py` - Translation File Maintenance Tool - Clean unused translation keys - Synchronize key structure across different language files ### 3. `manage-translations.sh` - Convenient Wrapper Script - Provides a unified command-line interface - Displays translation status - Simplifies common operations ## Quick Start ### Using the Wrapper Script (Recommended) ```bash # Enter the tools directory cd .config/quickshell/translations/tools # Show help ./manage-translations.sh --help # Show current translation status ./manage-translations.sh status # Extract translatable texts ./manage-translations.sh extract # Update all translation files ./manage-translations.sh update # Update a specific language ./manage-translations.sh update -l zh_CN # Clean unused keys ./manage-translations.sh clean # Synchronize keys across all language files ./manage-translations.sh sync ``` Or run from the project root: ```bash # Run from the project root .config/quickshell/translations/tools/manage-translations.sh status .config/quickshell/translations/tools/manage-translations.sh update ``` ## Detailed Usage ### Translation Manager (`translation-manager.py`) Basic usage: ```bash # Process all languages ./translation-manager.py # Specify a particular language ./translation-manager.py --language zh_CN # Extract translatable texts only ./translation-manager.py --extract-only # Show extracted texts ./translation-manager.py --extract-only --show-temp ``` Parameter description: - `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`) - `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`) - `--language`, `-l`: Specify the language code to process - `--extract-only`, `-e`: Only extract translatable texts - `--show-temp`: Show the content of the temporary extraction file ### Translation Cleaner (`translation-cleaner.py`) ```bash # Clean unused translation keys ./translation-cleaner.py --clean # Synchronize translation keys (using en_US as the base) ./translation-cleaner.py --sync # Specify a different source language for syncing ./translation-cleaner.py --sync --source-lang zh_CN # Clean without creating backups ./translation-cleaner.py --clean --no-backup ``` ## Workflow ### Regular Translation Update Workflow 1. **Check status**: ```bash ./manage-translations.sh status ``` 2. **Update translations**: ```bash ./manage-translations.sh update ``` 3. **Clean unused keys** (optional): ```bash ./manage-translations.sh clean ``` ### Adding a New Language 1. **Create a new language file**: ```bash ./manage-translations.sh update -l new_lang ``` 2. **Synchronize key structure**: ```bash ./manage-translations.sh sync ``` ### Cleanup After Large Refactoring 1. **Backup translation files**: ```bash cp -r .config/quickshell/translations .config/quickshell/translations.backup ``` 2. **Clean unused keys**: ```bash ./manage-translations.sh clean ``` 3. **Synchronize all languages**: ```bash ./manage-translations.sh sync ``` ## Supported Translatable Text Formats The tool recognizes the following formats for translatable texts: ```qml // Basic format Translation.tr("Hello, world!") Translation.tr('Hello, world!') Translation.tr(`Hello, world!`) // With line breaks Translation.tr("Line 1\nLine 2") // With escape characters Translation.tr("Say \"Hello\"") // With parameter placeholders Translation.tr("Hello, %1!").arg(name) ``` ## Example Output ### Status Display ``` $ ./manage-translations.sh status Analyzing translation status... === Current Project Status === 166 translatable texts extracted === Translation File Status === en_US: 470 keys zh_CN: 470 keys ``` ### Update Translations ``` $ ./manage-translations.sh update -l zh_CN Updating translation files... ================================================== Processing language: zh_CN ================================================== Analysis result: Missing keys: 5 Extra keys: 20 Found 5 missing translation keys: 1. "New feature text" 2. "Another new text" ... Add these 5 missing keys? (y/n): y 5 keys added Found 20 extra translation keys: 1. "Removed old text" -> "已删除的旧文本" ... Delete these 20 extra keys? (y/n): y 20 keys deleted Translation file saved ``` ### Clean Unused Keys ``` $ ./manage-translations.sh clean Cleaning unused translation keys... Processing language: zh_CN Found 50 unused keys: 1. "old_unused_text" 2. "deprecated_message" ... Delete these 50 unused keys? (y/n): y 50 keys deleted Original key count: 470, after cleaning: 420 ``` ## Advanced Features ### Custom Directory Structure ```bash # Use custom directories ./translation-manager.py \ --translations-dir /path/to/translations \ --source-dir /path/to/source ``` ### Ignore Mark Feature For dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing. Example: ```json { "dynamic_key": "Some dynamic value /*keep*/" } ``` ## Notes 1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files 2. **Text extraction limitations**: - ~~Only supports static strings, not dynamically constructed strings~~ - Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management. - Must use the `Translation.tr()` format 3. **File encoding**: All files must use UTF-8 encoding 4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters ## Troubleshooting ### Common Issues **Q: Text does not appear after adding Translation.tr?** A: You need to import the translation feature in your QML file using `import "root:/"`, otherwise the translation text will not be displayed correctly. **Q: The number of extracted texts does not match expectations?** A: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings. **Q: Some translations are missing after syncing?** A: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing. **Q: The cleaning operation deleted needed keys?** A: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code. ### Restore Backup ```bash # Restore a single file cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json # Restore all files cp .config/quickshell/translations.backup/* .config/quickshell/translations/ ``` ================================================ FILE: dots/.config/quickshell/ii/translations/tools/guide/translation-tools-guide-zh_CN.md ================================================ # 翻译管理工具套件 这套工具用于管理项目的翻译文件,自动提取可翻译文本,比较不同语言文件之间的差异,并提供维护功能。 ## 工具组成 ### 1. `translation-manager.py` - 主要翻译管理器 - 提取可翻译文本 - 比较和更新翻译文件 - 交互式添加/删除翻译键 ### 2. `translation-cleaner.py` - 翻译文件维护工具 - 清理不再使用的翻译键 - 同步不同语言文件的键结构 ### 3. `manage-translations.sh` - 便捷包装脚本 - 提供统一的命令行界面 - 显示翻译状态 - 简化常用操作 ## 快速开始 ### 使用便捷脚本(推荐) ```bash # 进入工具目录 cd .config/quickshell/translations/tools # 查看帮助 ./manage-translations.sh --help # 显示当前翻译状态 ./manage-translations.sh status # 提取可翻译文本 ./manage-translations.sh extract # 更新所有翻译文件 ./manage-translations.sh update # 更新特定语言 ./manage-translations.sh update -l zh_CN # 清理不再使用的键 ./manage-translations.sh clean # 同步所有语言文件的键 ./manage-translations.sh sync ``` 或者从项目根目录运行: ```bash # 从项目根目录运行 .config/quickshell/translations/tools/manage-translations.sh status .config/quickshell/translations/tools/manage-translations.sh update ``` ## 详细使用说明 ### 翻译管理器 (`translation-manager.py`) 基本用法: ```bash # 处理所有语言 ./translation-manager.py # 指定特定语言 ./translation-manager.py --language zh_CN # 仅提取可翻译文本 ./translation-manager.py --extract-only # 显示提取的文本 ./translation-manager.py --extract-only --show-temp ``` 参数说明: - `--translations-dir`, `-t`: 翻译文件目录(默认:`.config/quickshell/translations`) - `--source-dir`, `-s`: 源代码目录(默认:`.config/quickshell`) - `--language`, `-l`: 指定要处理的语言代码 - `--extract-only`, `-e`: 仅提取可翻译文本 - `--show-temp`: 显示临时提取文件的内容 ### 翻译清理器 (`translation-cleaner.py`) ```bash # 清理不再使用的翻译键 ./translation-cleaner.py --clean # 同步翻译键(以 en_US 为基准) ./translation-cleaner.py --sync # 指定不同的源语言进行同步 ./translation-cleaner.py --sync --source-lang zh_CN # 清理时不创建备份 ./translation-cleaner.py --clean --no-backup ``` ## 工作流程 ### 日常翻译更新流程 1. **检查状态**: ```bash ./manage-translations.sh status ``` 2. **更新翻译**: ```bash ./manage-translations.sh update ``` 3. **清理无用键**(可选): ```bash ./manage-translations.sh clean ``` ### 新增语言流程 1. **创建新语言文件**: ```bash ./manage-translations.sh update -l new_lang ``` 2. **同步键结构**: ```bash ./manage-translations.sh sync ``` ### 大规模重构后的清理流程 1. **备份翻译文件**: ```bash cp -r .config/quickshell/translations .config/quickshell/translations.backup ``` 2. **清理无用键**: ```bash ./manage-translations.sh clean ``` 3. **同步所有语言**: ```bash ./manage-translations.sh sync ``` ## 支持的翻译文本格式 工具可以识别以下格式的可翻译文本: ```qml // 基本格式 Translation.tr("Hello, world!") Translation.tr('Hello, world!') Translation.tr(`Hello, world!`) // 带换行符 Translation.tr("Line 1\nLine 2") // 带转义字符 Translation.tr("Say \"Hello\"") // 带参数占位符 Translation.tr("Hello, %1!").arg(name) ``` ## 示例输出 ### 状态显示 ``` $ ./manage-translations.sh status 正在分析翻译状态... === 当前项目状态 === 提取到 166 个可翻译文本 === 翻译文件状态 === en_US: 470 个键 zh_CN: 470 个键 ``` ### 更新翻译 ``` $ ./manage-translations.sh update -l zh_CN 更新翻译文件... ================================================== 处理语言: zh_CN ================================================== 分析结果: 缺少的键: 5 多余的键: 20 发现 5 个缺少的翻译键: 1. "New feature text" 2. "Another new text" ... 是否添加这 5 个缺少的键? (y/n): y 已添加 5 个键 发现 20 个多余的翻译键: 1. "Removed old text" -> "已删除的旧文本" ... 是否删除这 20 个多余的键? (y/n): y 已删除 20 个键 已保存翻译文件 ``` ### 清理无用键 ``` $ ./manage-translations.sh clean 清理不再使用的翻译键... 处理语言: zh_CN 发现 50 个不再使用的键: 1. "old_unused_text" 2. "deprecated_message" ... 是否删除这 50 个不再使用的键? (y/n): y 已删除 50 个键 原始键数: 470, 清理后: 420 ``` ## 高级功能 ### 自定义目录结构 ```bash # 使用自定义目录 ./translation-manager.py \ --translations-dir /path/to/translations \ --source-dir /path/to/source ``` ## 注意事项 1. **备份重要**:在执行清理操作前,工具会自动创建备份,但建议手动备份重要文件 2. **文本提取限制**: - ~~只支持静态字符串,不支持动态构建的字符串~~ - 动态资源(如变量拼接、运行时生成的文本)无法自动提取,需要在翻译文件中手动添加,并使用 `/*keep*/` 标记进行忽略管理。 - 必须使用 `Translation.tr()` 格式 ### 忽略标记功能 对于动态资源或特殊文本,如果不希望被自动清理,可在翻译值末尾添加 `/*keep*/`,工具会自动忽略这些键,不会在清理和同步时删除。 示例: ```json { "dynamic_key": "Some dynamic value /*keep*/" } ``` 3. **文件编码**:所有文件必须使用 UTF-8 编码 4. **键名规范**:建议使用英文作为键名,避免使用特殊字符 ## 故障排除 ### 常见问题 **Q: 添加了 Translation.tr 后文字不显示?** A: 需要在 QML 文件中使用 `import "root:/"` 导入翻译功能,否则无法正常显示翻译文本。 **Q: 提取的文本数量与预期不符?** A: 检查是否所有可翻译文本都使用了 `Translation.tr()` 格式,确保没有动态构建的字符串。 **Q: 同步后某些翻译丢失?** A: 检查源语言文件是否包含所有必要的键,考虑使用不同的源语言进行同步。 **Q: 清理操作删除了需要的键?** A: 从自动创建的备份文件中恢复,检查源代码中是否正确使用了 `Translation.tr()`。 ### 恢复备份 ```bash # 恢复单个文件 cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json # 恢复所有文件 cp .config/quickshell/translations.backup/* .config/quickshell/translations/ ``` ================================================ FILE: dots/.config/quickshell/ii/translations/tools/guide/translation-tools-guide.md ================================================ # Translation Management Tool Suite This suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions. ## Tool Components ### 1. `translation-manager.py` - Main Translation Manager - Extract translatable texts - Compare and update translation files - Interactive addition/removal of translation keys ### 2. `translation-cleaner.py` - Translation File Maintenance Tool - Clean unused translation keys - Synchronize key structure across different language files ### 3. `manage-translations.sh` - Convenient Wrapper Script - Provides a unified command-line interface - Displays translation status - Simplifies common operations ## Quick Start ### Using the Wrapper Script (Recommended) ```bash # Enter the tools directory cd .config/quickshell/translations/tools # Show help ./manage-translations.sh --help # Show current translation status ./manage-translations.sh status # Extract translatable texts ./manage-translations.sh extract # Update all translation files ./manage-translations.sh update # Update a specific language ./manage-translations.sh update -l zh_CN # Clean unused keys ./manage-translations.sh clean # Synchronize keys across all language files ./manage-translations.sh sync ``` Or run from the project root: ```bash # Run from the project root .config/quickshell/translations/tools/manage-translations.sh status .config/quickshell/translations/tools/manage-translations.sh update ``` ## Detailed Usage ### Translation Manager (`translation-manager.py`) Basic usage: ```bash # Process all languages ./translation-manager.py # Specify a particular language ./translation-manager.py --language zh_CN # Extract translatable texts only ./translation-manager.py --extract-only # Show extracted texts ./translation-manager.py --extract-only --show-temp ``` Parameter description: - `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`) - `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`) - `--language`, `-l`: Specify the language code to process - `--extract-only`, `-e`: Only extract translatable texts - `--show-temp`: Show the content of the temporary extraction file ### Translation Cleaner (`translation-cleaner.py`) ```bash # Clean unused translation keys ./translation-cleaner.py --clean # Synchronize translation keys (using en_US as the base) ./translation-cleaner.py --sync # Specify a different source language for syncing ./translation-cleaner.py --sync --source-lang zh_CN # Clean without creating backups ./translation-cleaner.py --clean --no-backup ``` ## Workflow ### Regular Translation Update Workflow 1. **Check status**: ```bash ./manage-translations.sh status ``` 2. **Update translations**: ```bash ./manage-translations.sh update ``` 3. **Clean unused keys** (optional): ```bash ./manage-translations.sh clean ``` ### Adding a New Language 1. **Create a new language file**: ```bash ./manage-translations.sh update -l new_lang ``` 2. **Synchronize key structure**: ```bash ./manage-translations.sh sync ``` ### Cleanup After Large Refactoring 1. **Backup translation files**: ```bash cp -r .config/quickshell/translations .config/quickshell/translations.backup ``` 2. **Clean unused keys**: ```bash ./manage-translations.sh clean ``` 3. **Synchronize all languages**: ```bash ./manage-translations.sh sync ``` ## Supported Translatable Text Formats The tool recognizes the following formats for translatable texts: ```qml // Basic format Translation.tr("Hello, world!") Translation.tr('Hello, world!') Translation.tr(`Hello, world!`) // With line breaks Translation.tr("Line 1\nLine 2") // With escape characters Translation.tr("Say \"Hello\"") // With parameter placeholders Translation.tr("Hello, %1!").arg(name) ``` ## Example Output ### Status Display ``` $ ./manage-translations.sh status Analyzing translation status... === Current Project Status === 166 translatable texts extracted === Translation File Status === en_US: 470 keys zh_CN: 470 keys ``` ### Update Translations ``` $ ./manage-translations.sh update -l zh_CN Updating translation files... ================================================== Processing language: zh_CN ================================================== Analysis result: Missing keys: 5 Extra keys: 20 Found 5 missing translation keys: 1. "New feature text" 2. "Another new text" ... Add these 5 missing keys? (y/n): y 5 keys added Found 20 extra translation keys: 1. "Removed old text" -> "已删除的旧文本" ... Delete these 20 extra keys? (y/n): y 20 keys deleted Translation file saved ``` ### Clean Unused Keys ``` $ ./manage-translations.sh clean Cleaning unused translation keys... Processing language: zh_CN Found 50 unused keys: 1. "old_unused_text" 2. "deprecated_message" ... Delete these 50 unused keys? (y/n): y 50 keys deleted Original key count: 470, after cleaning: 420 ``` ## Advanced Features ### Custom Directory Structure ```bash # Use custom directories ./translation-manager.py \ --translations-dir /path/to/translations \ --source-dir /path/to/source ``` ### Ignore Mark Feature For dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing. Example: ```json { "dynamic_key": "Some dynamic value /*keep*/" } ``` ## Notes 1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files 2. **Text extraction limitations**: - ~~Only supports static strings, not dynamically constructed strings~~ - Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management. - Must use the `Translation.tr()` format 3. **File encoding**: All files must use UTF-8 encoding 4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters ## Troubleshooting ### Common Issues **Q: Text does not appear after adding Translation.tr?** A: You need to import the translation feature in your QML file using `import "root:/"`, otherwise the translation text will not be displayed correctly. **Q: The number of extracted texts does not match expectations?** A: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings. **Q: Some translations are missing after syncing?** A: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing. **Q: The cleaning operation deleted needed keys?** A: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code. ### Restore Backup ```bash # Restore a single file cp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json # Restore all files cp .config/quickshell/translations.backup/* .config/quickshell/translations/ ``` ================================================ FILE: dots/.config/quickshell/ii/translations/tools/manage-translations.sh ================================================ #!/bin/bash # Translation management script - convenient wrapper set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TRANSLATIONS_DIR="$(dirname "$SCRIPT_DIR")" SOURCE_DIR="$(dirname "$(dirname "$TRANSLATIONS_DIR")")" show_help() { echo "Translation Management Tool - Convenient Wrapper" echo "" echo "Usage: $0 [options] " echo "" echo "Commands:" echo " extract Extract translatable texts to temporary file" echo " update Update translation files (add missing/remove extra keys)" echo " clean Clean unused translation keys" echo " sync Sync keys across all language files" echo " status Show translation status" echo "" echo "Options:" echo " -l, --lang LANG Specify language (e.g.: zh_CN)" echo " -t, --trans-dir DIR Translation files directory (default: $TRANSLATIONS_DIR)" echo " -s, --source-dir DIR Source code directory (default: $SOURCE_DIR)" echo " -y, --yes Skip all confirmation prompts (auto-confirm)" echo " -h, --help Show this help message" echo "" echo "Examples:" echo " $0 extract # Extract translatable texts" echo " $0 update -l zh_CN # Update Chinese translations" echo " $0 update # Update all translations" echo " $0 clean # Clean unused keys" echo " $0 sync # Sync keys across all languages" echo " $0 status # Show translation status" } show_status() { echo "Analyzing translation status..." # Extract current text count echo "=== Current Project Status ===" python3 "$SCRIPT_DIR/translation-manager.py" \ --translations-dir "$TRANSLATIONS_DIR" \ --source-dir "$SOURCE_DIR" \ --extract-only | grep "Extracted" echo "" echo "=== Translation File Status ===" if [ -d "$TRANSLATIONS_DIR" ]; then for file in "$TRANSLATIONS_DIR"/*.json; do if [ -f "$file" ]; then lang=$(basename "$file" .json) count=$(jq 'length' "$file" 2>/dev/null || echo "error") echo " $lang: $count keys" fi done else echo " Translation directory does not exist: $TRANSLATIONS_DIR" fi } # Parse command line arguments LANG_CODE="" COMMAND="" YES_FLAG="" while [[ $# -gt 0 ]]; do case $1 in -l|--lang) LANG_CODE="$2" shift 2 ;; -t|--trans-dir) TRANSLATIONS_DIR="$2" shift 2 ;; -s|--source-dir) SOURCE_DIR="$2" shift 2 ;; -y|--yes) YES_FLAG="-y" shift ;; -h|--help) show_help exit 0 ;; extract|update|clean|sync|status) if [ -n "$COMMAND" ]; then echo "Error: Only one command can be specified" exit 1 fi COMMAND="$1" shift ;; *) echo "Unknown option: $1" show_help exit 1 ;; esac done if [ -z "$COMMAND" ]; then echo "Error: A command must be specified" show_help exit 1 fi # Check dependencies if ! command -v python3 >/dev/null 2>&1; then echo "Error: python3 is required" exit 1 fi if [ "$COMMAND" = "status" ] && ! command -v jq >/dev/null 2>&1; then echo "Warning: jq is not installed, status display may be incomplete" fi # Build base arguments BASE_ARGS="--translations-dir $TRANSLATIONS_DIR --source-dir $SOURCE_DIR" case $COMMAND in extract) echo "Extracting translatable texts..." python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS $YES_FLAG --extract-only --show-temp ;; update) echo "Updating translation files..." if [ -n "$LANG_CODE" ]; then python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS $YES_FLAG --language "$LANG_CODE" else python3 "$SCRIPT_DIR/translation-manager.py" $BASE_ARGS $YES_FLAG fi ;; clean) echo "Cleaning unused translation keys..." python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS $YES_FLAG --clean ;; sync) echo "Syncing translation keys..." python3 "$SCRIPT_DIR/translation-cleaner.py" $BASE_ARGS $YES_FLAG --sync ;; status) show_status ;; *) echo "Unknown command: $COMMAND" show_help exit 1 ;; esac ================================================ FILE: dots/.config/quickshell/ii/translations/tools/translation-cleaner.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Translation File Maintenance Helper Used to clean and organize translation files, removing unused keys """ import os import sys import json import argparse import importlib.util from pathlib import Path from typing import Dict, Set, List # Import from the same directory using importlib current_dir = os.path.dirname(os.path.abspath(__file__)) manager_path = os.path.join(current_dir, 'translation-manager.py') spec = importlib.util.spec_from_file_location("translation_manager", manager_path) translation_manager = importlib.util.module_from_spec(spec) spec.loader.exec_module(translation_manager) TranslationManager = translation_manager.TranslationManager def clean_translation_files(translations_dir: str, source_dir: str, backup: bool = True, yes_mode: bool = False): """Clean translation files by removing unused keys""" print("Starting translation file cleanup...") # Create manager manager = TranslationManager(translations_dir, source_dir) # Extract currently used texts print("Extracting currently used translatable texts...") current_texts = manager.extract_translatable_texts() print(f"Extracted {len(current_texts)} currently used texts") # Get all language files languages = manager.get_available_languages() if not languages: print("No translation files found") return print(f"Found language files: {', '.join(languages)}") total_removed = 0 for lang in languages: print(f"\nProcessing language: {lang}") # Load translation file translations = manager.load_translation_file(lang) original_count = len(translations) # Find unused keys, skip those whose value ends with /*keep*/ unused_keys = set() for k in translations.keys(): v = translations[k] if k not in current_texts: if isinstance(v, str) and v.strip().endswith('/*keep*/'): continue unused_keys.add(k) if unused_keys: print(f"Found {len(unused_keys)} unused keys:") for i, key in enumerate(sorted(unused_keys)[:10], 1): # Only show first 10 print(f" {i}. \"{key[:50]}{'...' if len(key) > 50 else ''}\"") if len(unused_keys) > 10: print(f" ... and {len(unused_keys) - 10} more keys") if yes_mode: response = 'y' print(f"Delete these {len(unused_keys)} unused keys? (auto-confirmed by --yes)") else: response = input(f"Delete these {len(unused_keys)} unused keys? (y/n): ") if response.lower().strip() in ['y', 'yes']: if backup: # Create backup only when user confirms deletion backup_file = Path(translations_dir) / f"{lang}.json.bak" with open(backup_file, 'w', encoding='utf-8') as f: json.dump(translations, f, ensure_ascii=False, indent=2) print(f"Created backup: {backup_file}") # Delete unused keys for key in unused_keys: del translations[key] # Save cleaned file manager.save_translation_file(lang, translations) removed_count = len(unused_keys) total_removed += removed_count print(f"Deleted {removed_count} keys") else: print("Skipped deletion") else: print("No unused keys found") new_count = len(translations) print(f"Original key count: {original_count}, after cleanup: {new_count}") print(f"\nCleanup completed! Total deleted {total_removed} unused keys.") def sync_translations(translations_dir: str, source_lang: str = "en_US", target_langs: List[str] = None, yes_mode: bool = False): """Sync translation keys to ensure all language files have the same keys""" print(f"Starting translation key sync using {source_lang} as reference...") translations_path = Path(translations_dir) # Load source language file source_file = translations_path / f"{source_lang}.json" if not source_file.exists(): print(f"Error: Source language file does not exist: {source_file}") return with open(source_file, 'r', encoding='utf-8') as f: source_translations = json.load(f) source_keys = set(source_translations.keys()) print(f"Source language {source_lang} has {len(source_keys)} keys") # Get target language list if target_langs is None: target_langs = [] for file_path in translations_path.glob("*.json"): lang_code = file_path.stem if lang_code != source_lang: target_langs.append(lang_code) if not target_langs: print("No target language files found") return print(f"Target languages: {', '.join(target_langs)}") for target_lang in target_langs: print(f"\nSyncing language: {target_lang}") target_file = translations_path / f"{target_lang}.json" if target_file.exists(): with open(target_file, 'r', encoding='utf-8') as f: target_translations = json.load(f) else: target_translations = {} target_keys = set(target_translations.keys()) # Find missing and extra keys missing_keys = source_keys - target_keys extra_keys = target_keys - source_keys print(f" Missing keys: {len(missing_keys)}") print(f" Extra keys: {len(extra_keys)}") # Add missing keys if missing_keys: for key in missing_keys: # Use source language value as placeholder by default target_translations[key] = source_translations[key] print(f" Added {len(missing_keys)} missing keys") # Ask whether to delete extra keys if extra_keys: if yes_mode: response = 'y' print(f" Delete {len(extra_keys)} extra keys? (auto-confirmed by --yes)") else: response = input(f" Delete {len(extra_keys)} extra keys? (y/n): ") if response.lower().strip() in ['y', 'yes']: for key in extra_keys: del target_translations[key] print(f" Deleted {len(extra_keys)} extra keys") # Save file (ensure UTF-8, fix for special chars) with open(target_file, 'w', encoding='utf-8', newline='') as f: json.dump(target_translations, f, ensure_ascii=False, indent=2) print(f" Saved: {target_file}") def main(): parser = argparse.ArgumentParser(description="Translation File Maintenance Helper") parser.add_argument("--translations-dir", "-t", default=".config/quickshell/translations", help="Translation files directory") parser.add_argument("--source-dir", "-s", default=".config/quickshell", help="Source code directory") parser.add_argument("--clean", "-c", action="store_true", help="Clean unused translation keys") parser.add_argument("--sync", action="store_true", help="Sync translation keys") parser.add_argument("--source-lang", default="en_US", help="Source language for syncing (default: en_US)") parser.add_argument("--no-backup", action="store_true", help="Do not create backup files when cleaning") parser.add_argument("-y", "--yes", action="store_true", help="Skip all confirmation prompts (auto-confirm)") args = parser.parse_args() # Convert to absolute paths translations_dir = os.path.abspath(args.translations_dir) source_dir = os.path.abspath(args.source_dir) if args.clean: clean_translation_files(translations_dir, source_dir, backup=not args.no_backup, yes_mode=args.yes) elif args.sync: sync_translations(translations_dir, args.source_lang, yes_mode=args.yes) else: print("Please specify an operation:") print(" --clean: Clean unused translation keys") print(" --sync: Sync translation keys") if __name__ == "__main__": main() ================================================ FILE: dots/.config/quickshell/ii/translations/tools/translation-manager.py ================================================ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Translation File Management Script Used to update and extract translatable texts, manage JSON translation file key comparison """ import os import json import re import sys import argparse from pathlib import Path from typing import Dict, Set, List, Tuple import tempfile import subprocess class TranslationManager: def __init__(self, translations_dir: str, source_dir: str, yes_mode: bool = False): self.translations_dir = Path(translations_dir) self.source_dir = Path(source_dir) self.temp_extracted_file = None self.yes_mode = yes_mode # Ensure translation directory exists self.translations_dir.mkdir(parents=True, exist_ok=True) def extract_translatable_texts(self) -> Set[str]: """Extract translatable texts from source code""" translatable_texts = set() # Search patterns: Translation.tr("text") or Translation.tr('text') # Improved regex that handles nested quotes correctly patterns = [ r'Translation\.tr\s*\(\s*(["\'])(((?!\1)[^\\]|\\.)*)(\1)\s*\)', # Double or single quotes with escape support r'Translation\.tr\s*\(\s*`([^`]*(?:\\.[^`]*)*?)`\s*\)', # Backticks (template strings) ] # Search all .qml and .js files file_extensions = ['*.qml', '*.js'] for ext in file_extensions: for file_path in self.source_dir.rglob(ext): try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() for pattern in patterns: matches = re.findall(pattern, content, re.MULTILINE | re.DOTALL) for match in matches: # Handle different match group structures if isinstance(match, tuple): # For improved regex, text is in the second group if len(match) >= 3: text = match[1] # Second group is the text content else: text = match[0] if match else "" else: text = match try: if '\\u' in text or '\\x' in text: clean_text = bytes(text, "utf-8").decode("unicode_escape") else: clean_text = ( text.replace('\\n', '\n') .replace('\\t', '\t') .replace('\\r', '\r') .replace('\\"', '"') .replace('\\\'', "'") .replace('\\f', '\f') .replace('\\b', '\b') .replace('\\\\', '\\') ) except Exception: clean_text = text # Clean text (remove extra whitespace) clean_text = clean_text.strip() if clean_text: translatable_texts.add(clean_text) except (UnicodeDecodeError, IOError) as e: print(f"Warning: Cannot read file {file_path}: {e}") return translatable_texts def create_temp_translation_file(self, texts: Set[str]) -> str: """Create temporary JSON file containing extracted texts""" temp_data = {} for text in sorted(texts): temp_data[text] = text # Key and value are the same, indicating untranslated # Create temporary file with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f: json.dump(temp_data, f, ensure_ascii=False, indent=2) self.temp_extracted_file = f.name return self.temp_extracted_file def load_translation_file(self, lang_code: str) -> Dict[str, str]: """Load translation file for specified language""" file_path = self.translations_dir / f"{lang_code}.json" if file_path.exists(): try: with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) except (json.JSONDecodeError, IOError) as e: print(f"Warning: Cannot load translation file {file_path}: {e}") return {} return {} def save_translation_file(self, lang_code: str, translations: Dict[str, str]): """Save translation file""" file_path = self.translations_dir / f"{lang_code}.json" try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(translations, f, ensure_ascii=False, indent=2) print(f"Translation file saved: {file_path}") except IOError as e: print(f"Error: Cannot save translation file {file_path}: {e}") def get_available_languages(self) -> List[str]: """Get list of available languages""" languages = [] for file_path in self.translations_dir.glob("*.json"): lang_code = file_path.stem languages.append(lang_code) return sorted(languages) def compare_translations(self, extracted_texts: Set[str], target_lang: str) -> Tuple[Set[str], Set[str]]: """Compare extracted texts with existing translation file""" existing_translations = self.load_translation_file(target_lang) existing_keys = set(existing_translations.keys()) missing_keys = extracted_texts - existing_keys # Missing keys extra_keys = existing_keys - extracted_texts # Extra keys return missing_keys, extra_keys def interactive_update(self, lang_code: str, missing_keys: Set[str], extra_keys: Set[str]): """Interactively update translation file, create backup only if updating""" translations = self.load_translation_file(lang_code) modified = False backup_created = False # Handle missing keys if missing_keys: print(f"\nFound {len(missing_keys)} missing translation keys:") for i, key in enumerate(sorted(missing_keys), 1): print(f"{i}. \"{key}\"") if self.ask_yes_no(f"\nAdd these {len(missing_keys)} missing keys?"): if not backup_created: backup_file = self.translations_dir / f"{lang_code}.json.bak" with open(backup_file, 'w', encoding='utf-8') as f: json.dump(translations, f, ensure_ascii=False, indent=2) print(f"Created backup: {backup_file}") backup_created = True for key in missing_keys: translations[key] = key # Default value is the key itself modified = True print(f"Added {len(missing_keys)} keys") # Handle extra keys if extra_keys: # Only show extra keys that are not marked with /*keep*/ filtered_extra_keys = [key for key in extra_keys if not (isinstance(translations.get(key, ""), str) and translations.get(key, "").strip().endswith('/*keep*/'))] if filtered_extra_keys: print(f"\nFound {len(filtered_extra_keys)} extra translation keys:") for i, key in enumerate(sorted(filtered_extra_keys), 1): print(f"{i}. \"{key}\" -> \"{translations.get(key, '')}\"") if self.ask_yes_no(f"\nDelete these {len(filtered_extra_keys)} extra keys?"): if not backup_created: backup_file = self.translations_dir / f"{lang_code}.json.bak" with open(backup_file, 'w', encoding='utf-8') as f: json.dump(translations, f, ensure_ascii=False, indent=2) print(f"Created backup: {backup_file}") backup_created = True deleted_count = 0 for key in filtered_extra_keys: if key in translations: del translations[key] modified = True deleted_count += 1 print(f"Deleted {deleted_count} keys") # Save changes if modified: self.save_translation_file(lang_code, translations) else: print("No changes made") def ask_yes_no(self, question: str) -> bool: """Ask user for confirmation, auto-confirm if yes_mode is True""" if getattr(self, "yes_mode", False): print(f"{question} (auto-confirmed by --yes)") return True while True: response = input(f"{question} (y/n): ").lower().strip() if response in ['y', 'yes']: return True elif response in ['n', 'no']: return False else: print("Please enter y/yes or n/no") def cleanup(self): """Clean up temporary files""" if self.temp_extracted_file and os.path.exists(self.temp_extracted_file): os.unlink(self.temp_extracted_file) def main(): parser = argparse.ArgumentParser(description="Translation file management tool") parser.add_argument("--translations-dir", "-t", default=".config/quickshell/translations", help="Translation files directory (default: .config/quickshell/translations)") parser.add_argument("--source-dir", "-s", default=".config/quickshell/ii", help="Source code directory (default: .config/quickshell/ii)") parser.add_argument("--language", "-l", help="Specify language code to process (e.g., zh_CN)") parser.add_argument("--extract-only", "-e", action="store_true", help="Only extract translatable texts to temporary file") parser.add_argument("--show-temp", action="store_true", help="Show temporary extracted file content") parser.add_argument("-y", "--yes", action="store_true", help="Skip all confirmation prompts (auto-confirm)") args = parser.parse_args() # Convert to absolute paths translations_dir = os.path.abspath(args.translations_dir) source_dir = os.path.abspath(args.source_dir) print(f"Translation directory: {translations_dir}") print(f"Source code directory: {source_dir}") # Check if directories exist if not os.path.exists(source_dir): print(f"Error: Source code directory does not exist: {source_dir}") sys.exit(1) # Create manager manager = TranslationManager(translations_dir, source_dir, yes_mode=args.yes) try: # Extract translatable texts print("\nExtracting translatable texts...") extracted_texts = manager.extract_translatable_texts() print(f"Extracted {len(extracted_texts)} translatable texts") # Create temporary file temp_file = manager.create_temp_translation_file(extracted_texts) print(f"Created temporary file: {temp_file}") if args.show_temp: print("\nTemporary file contents:") with open(temp_file, 'r', encoding='utf-8') as f: print(f.read()) if args.extract_only: print("Extract-only mode, program finished") return # Get available languages available_languages = manager.get_available_languages() if args.language: target_languages = [args.language] else: print(f"\nAvailable languages: {', '.join(available_languages) if available_languages else 'None'}") if not available_languages: if manager.yes_mode: print("No existing translation files found, auto-skipping language creation due to --yes") return lang_input = input("Enter language code to create (e.g.: zh_CN): ").strip() if lang_input: target_languages = [lang_input] else: print("No language specified, program finished") return else: print("Choose language to process:") for i, lang in enumerate(available_languages, 1): print(f"{i}. {lang}") print("a. Process all languages") if manager.yes_mode: choice = 'a' print("Auto-selecting all languages due to --yes") else: choice = input("Please choose (enter number, language code, or 'a'): ").strip() if choice.lower() == 'a': target_languages = available_languages elif choice.isdigit() and 1 <= int(choice) <= len(available_languages): target_languages = [available_languages[int(choice) - 1]] elif choice in available_languages: target_languages = [choice] else: print("Invalid choice, program finished") return # Process each language for lang in target_languages: print(f"\n{'='*50}") print(f"Processing language: {lang}") print('='*50) missing_keys, extra_keys = manager.compare_translations(extracted_texts, lang) if not missing_keys and not extra_keys: print(f"Translation file for language {lang} is already up to date") continue print(f"Analysis results:") print(f" Missing keys: {len(missing_keys)}") # Load translation file for current lang to get values current_translations = manager.load_translation_file(lang) filtered_extra_keys = [key for key in extra_keys if not (isinstance(current_translations.get(key, ""), str) and current_translations.get(key, "").strip().endswith('/*keep*/'))] ignored_extra_keys = [key for key in extra_keys if (isinstance(current_translations.get(key, ""), str) and current_translations.get(key, "").strip().endswith('/*keep*/'))] print(f" Extra keys: {len(filtered_extra_keys)}") if ignored_extra_keys: print(f" Ignored keys: {len(ignored_extra_keys)} (marked with /*keep*/)") if missing_keys or extra_keys: manager.interactive_update(lang, missing_keys, extra_keys) finally: # Clean up temporary files manager.cleanup() if __name__ == "__main__": main() ================================================ FILE: dots/.config/quickshell/ii/translations/tr_TR.json ================================================ { "Material cookie": "Material cookie", "Style: Blurred": "Stil: Bulanık", "Unknown device": "Bilinmeyen cihaz", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "İstediğiniz zaman /dark, /light, /wallpaper komutlarıyla değiştirebilirsiniz\nShell renkleri değişmiyorsa:\n 1. Super+N ile sağ kenar çubuğunu açın\n 2. Sağ üst köşedeki \"Hyprland ve Quickshell'i Yeniden Yükle\" düğmesine tıklayın", "No pending tasks": "Bekleyen görev yok", "Positioning": "Konumlandırma", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Modelin sıcaklığını (rastgelelik) ayarlayın. Gemini için 0 ile 2 arası, diğer modeller için 0 ile 1 arası değerler. Varsayılan 0.5.", "Critical warning": "Kritik uyarı", "Unknown Artist": "Bilinmeyen Sanatçı", "Web search": "Web araması", "Load prompt from %1": "%1'den komut yükle", "Attach a file. Only works with Gemini.": "Dosya ekle. Sadece Gemini ile çalışır.", "Reboot": "Yeniden Başlat", "API key:\n\n```txt\n%1\n```": "API anahtarı:\n\n```txt\n%1\n```", "Pinned on startup": "Başlangıçta sabitle", "Right": "Sağ", "Reboot to firmware settings": "Firmware ayarlarına yeniden başlat", "Automatically hide": "Otomatik gizle", "Waiting for response...": "Yanıt bekleniyor...", "To Do": "Yapılacaklar", "Full": "Tam", "Select Language": "Dil Seçin", "Password": "Şifre", "Bluetooth devices": "Bluetooth cihazları", "Enable": "Etkinleştir", "Elements": "Öğeler", "Start": "Başlat", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Konachan'dan rastgele SFW Anime duvar kağıdı\nGörsel ~/Resimler/Wallpapers dizinine kaydedilir", "The popular one | Best quantity, but quality can vary wildly": "Popüler olan | En iyi miktar, ama kalite değişebilir", "System uptime:": "Sistem çalışma süresi:", "illogical-impulse Welcome": "illogical-impulse'a Hoş Geldiniz", "Code saved to file": "Kod dosyaya kaydedildi", "Info": "Bilgi", "Preferred wallpaper zoom (%)": "Tercih edilen duvar kağıdı yakınlaştırması (%)", "Time": "Zaman", "Help & Support": "Yardım ve Destek", "Bubble": "Baloncuk", "Large images | God tier quality, no NSFW.": "Büyük görseller | Mükemmel kalite, NSFW yok.", "Dark": "Koyu", "Center clock": "Saati ortala", "Search, calculate or run": "Ara, hesapla veya çalıştır", "Region height": "Bölge yüksekliği", "Load chat": "Sohbet yükle", "Gives the model search capabilities (immediately)": "Modele arama yetenekleri kazandırır (hemen)", "Depends on workspace": "Çalışma alanına bağlı", "Blurred style": "Bulanık stil", "Screenshot tool": "Ekran görüntüsü aracı", "Enter password": "Şifre girin", "Search the web": "Web'de ara", "Local only": "Sadece yerel", "at": "saat", "Math": "Matematik", "Consider plugging in your device": "Cihazınızı prize takın", "Workspaces shown": "Gösterilen çalışma alanları", "Place the corners to trigger at the bottom": "Tetikleyici köşeleri alta yerleştir", "No API key\nSet it with /key YOUR_API_KEY": "API anahtarı yok\n/key API_ANAHTARINIZ ile ayarlayın", "Auto (System)": "Otomatik (Sistem)", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Ok tuşlarıyla gezin, Enter ile seçin\nEsc veya herhangi bir yere tıklayarak iptal edin", "Critically low battery": "Kritik düşük pil", "Open editor": "Düzenleyici aç", "%1 notifications": "%1 bildirim", "Region width": "Bölge genişliği", "Max allowed increase": "İzin verilen maksimum artış", "Enable translator": "Çevirmeni etkinleştir", "Constantly rotate": "Sürekli döndür", "Automatically suspends the system when battery is low": "Pil düşük olduğunda sistemi otomatik olarak askıya alır", "Cannot find a GPS service. Using the fallback method instead.": "GPS hizmeti bulunamadı. Yerine yedek yöntem kullanılıyor.", "Qt apps": "Qt uygulamaları", "Color picker": "Renk seçici", "Interface": "Arayüz", "Tint app icons": "Uygulama simgelerini renklendir", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "Kullanıcı arayüzü dilini seçin.\n\"Otomatik\" sisteminizin yerel ayarını kullanır.", "Show quote": "Alıntı göster", "Local Ollama model | %1": "Yerel Ollama modeli | %1", "Show clock": "Saat göster", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "Kullanım: %1superpaste GIRDI_SAYISI[i]\nGörseller için i ekleyin\nÖrnekler:\n%1superpaste 4i son 4 görsel için\n%1superpaste 7 son 7 girdi için", "Audio": "Ses", "Corner style": "Köşe stili", "No media": "Medya yok", "Unknown function call: %1": "Bilinmeyen fonksiyon çağrısı: %1", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Çevrimiçi | %1'in modeli | Hızlı, duyarlı ve iyi biçimlendirilmiş yanıtlar verir. Dezavantajlar: işleri yapmaya pek istekli değil; bilinmeyen fonksiyon çağrıları yapabilir", "Volume": "Ses", "Medium": "Orta", "Copy code": "Kodu kopyala", "Exceeded max allowed": "İzin verilen maksimum aşıldı", "Keep right sidebar loaded": "Sağ kenar çubuğunu yüklü tut", "Left": "Sol", "High": "Yüksek", "Rect": "Dikdörtgen", "Lap": "Tur", "Clear": "Temizle", "Screen snip": "Ekran kırpma", "Reset": "Sıfırla", "Back": "Geri", "Dark/Light toggle": "Koyu/Açık geçişi", "12h am/pm": "12s öö/ös", "Download complete": "İndirme tamamlandı", "Enable blur": "Bulanıklığı etkinleştir", "Second hand": "Saniye ibresi", "Bar & screen": "Çubuk ve ekran", "Discharging:": "Deşarj:", "Up %1": "Çalışma süresi %1", "Low": "Düşük", "Hour hand": "Saat ibresi", "Clear chat history": "Sohbet geçmişini temizle", "Fruit Salad": "Meyve Salatası", "%1 Safe Storage": "%1 Güvenli Depolama", "Hibernate": "Hazırda Beklet", "Delete": "Sil", "OK": "Tamam", "Settings": "Ayarlar", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "Bu genellikle güvenlidir ve tarayıcınız ve AI kenar çubuğu için zaten gereklidir\nÇoğunlukla başlangıçta kilitleme kullanıp bunu yapan bir görüntü yöneticisi (GDM, SDDM, vb.) kullanmayanlar için faydalıdır", "Use Hyprlock (instead of Quickshell)": "Hyprlock kullan (Quickshell yerine)", "Crosshair code (in Valorant's format)": "Nişangah kodu (Valorant formatında)", "Silent": "Sessiz", "Useless buttons": "İşe yaramaz düğmeler", "Hover to reveal": "Görmek için üzerine gelin", "Wallpaper & Colors": "Duvar Kağıdı ve Renkler", "Auto": "Otomatik", "Visibility": "Görünürlük", "Shell & utilities": "Shell ve yardımcı programlar", "Hollow": "İçi boş", "illogical-impulse": "illogical-impulse", "Use the system file picker instead\nRight-click to make this the default behavior": "Bunun yerine sistem dosya seçiciyi kullan\nBunu varsayılan davranış yapmak için sağ tıklayın", "On-screen display": "Ekran üstü gösterim", "Dotfiles": "Nokta dosyaları", "Search wallpapers": "Duvar kağıtlarında ara", "Mic toggle": "Mikrofon geçişi", "Input": "Giriş", "Also unlock keyring": "Anahtar halkasının da kilidini aç", "Configuration": "Yapılandırma", "Keep system awake": "Sistemi uyanık tut", "Unknown command:": "Bilinmeyen komut:", "Anime boorus": "Anime booru'ları", "To Do:": "Yapılacaklar:", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "Gemini kullanarak duvar kağıdını kategorize eder ve ona göre bir ön ayar seçer.\nÖnce sol kenar çubuğunda Gemini API anahtarını ayarlamanız gerekir.\nGörseller performans için küçültülür, ancak güvenli olmak için\nhasas bilgi içeren duvar kağıtlarını seçmeyin.", "Bottom": "Alt", "Clear the current list of images": "Mevcut görsel listesini temizle", "Sunrise": "Gün doğumu", "Show app icons": "Uygulama simgelerini göster", "Format": "Format", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "Oynatıcınızın MPRIS desteği olduğundan emin olun\nveya yinelenen oynatıcı filtrelemeyi kapatmayı deneyin", "Pause": "Duraklat", "Desktop": "Masaüstü", "Conflicts with the shell's system tray implementation": "Kabuğun sistem tepsisi uygulamasıyla çakışıyor", "Your package manager is running": "Paket yöneticiniz çalışıyor", "Conflicts with the shell's notification implementation": "Kabuğun bildirim uygulamasıyla çakışıyor", "Unknown Album": "Bilinmeyen Albüm", "Pick wallpaper image on your system": "Sisteminizdeki duvar kağıdı görselini seçin", "Used:": "Kullanılan:", "Cheat sheet": "Kopya kağıdı", "Clock style": "Saat stili", "No audio source": "Ses kaynağı yok", "Paired": "Eşleştirildi", "Documentation": "Belgeler", "No": "Hayır", "Pills": "Haplar", "Thought": "Düşünce", "When this is off you'll have to click": "Bu kapalı olduğunda tıklamanız gerekecek", "Select output device": "Çıkış cihazını seç", "Logout": "Çıkış Yap", "Tip: Close a window with Super+Q": "İpucu: Super+Q ile bir pencereyi kapatın", "Finished tasks will go here": "Tamamlanan görevler buraya gelecek", "Terminal: Harmony (%)": "Terminal: Uyum (%)", "Corner open": "Köşe açık", "Shell conflicts killer": "Shell çakışma giderici", "Clean stuff | Excellent quality, no NSFW": "Temiz içerik | Mükemmel kalite, NSFW yok", "Scroll to change volume": "Ses değiştirmek için kaydırın", "Wind": "Rüzgar", "API key is set\nChange with /key YOUR_API_KEY": "API anahtarı ayarlandı\n/key API_ANAHTARINIZ ile değiştirin", "Neutral": "Nötr", "12h AM/PM": "12s ÖÖ/ÖS", "Number show delay when pressing Super (ms)": "Super'e basarken numara gösterme gecikmesi (ms)", "Fill": "Doldur", "Always show numbers": "Numaraları her zaman göster", "Dot": "Nokta", "Provider set to": "Sağlayıcı şu şekilde ayarlandı", "Unknown Title": "Bilinmeyen Başlık", "Anime": "Anime", "Refreshing (manually triggered)": "Yenileniyor (manuel tetiklendi)", "Dock": "Dock", "Require password to power off/restart": "Kapatma/yeniden başlatma için şifre iste", "Line": "Çizgi", "Weather": "Hava Durumu", "All-rounder | Good quality, decent quantity": "Çok yönlü | İyi kalite, makul miktar", "Scale (%)": "Ölçek (%)", "Copy": "Kopyala", "Usage": "Kullanım", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Çevrimiçi modellerle başlamak için /key yazın\nKenar çubuğunu genişletmek için Ctrl+O\nKenar çubuğunu pencereye ayırmak için Ctrl+P", "Set the tool to use for the model.": "Model için kullanılacak aracı ayarlayın.", "Disable tools": "Araçları devre dışı bırak", "Connect": "Bağlan", "Allow NSFW": "NSFW'ye izin ver", "Registration failed. Please inspect manually with the warp-cli command": "Kayıt başarısız. Lütfen warp-cli komutuyla manuel olarak kontrol edin", "Time to full:": "Dolmasına kalan süre:", "Session": "Oturum", "Services": "Hizmetler", "Nothing here!": "Burada hiçbir şey yok!", "Overview": "Genel Bakış", "Random: osu! seasonal": "Rastgele: osu! mevsimlik", "If you want to somehow use fingerprint unlock...": "Parmak izi kilidi açmayı kullanmak istiyorsanız...", "Minute hand": "Dakika ibresi", "Notifications": "Bildirimler", "Enable if you want clocks to show seconds accurately": "Saatlerin saniyeleri doğru göstermesini istiyorsanız etkinleştirin", "Timer": "Zamanlayıcı", "Quote settings": "Alıntı ayarları", "System prompt": "Sistem komutu", "Classic": "Klasik", "Close": "Kapat", "Disconnect": "Bağlantıyı kes", "Go to source (%1)": "Kaynağa git (%1)", "EasyEffects | Right-click to configure": "EasyEffects | Yapılandırmak için sağ tıklayın", "Forget": "Unut", "Output": "Çıkış", "Date style": "Tarih stili", "System": "Sistem", "Usage: %1tool TOOL_NAME": "Kullanım: %1tool ARAÇ_ADI", "Workspaces": "Çalışma alanları", "Calendar": "Takvim", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Talimatlar**: Mistral hesabına giriş yapın, kenar çubuğundaki Keys'e gidin, Create new key'e tıklayın", "Volume limit": "Ses sınırı", "Sunset": "Gün batımı", "Dial style": "Kadran stili", "Hi there! First things first...": "Merhaba! İlk önce...", "Save chat to %1": "Sohbeti %1'e kaydet", "Security": "Güvenlik", "Total token count\nInput: %1\nOutput: %2": "Toplam token sayısı\nGiriş: %1\nÇıkış: %2", "Cancel wallpaper selection": "Duvar kağıdı seçimini iptal et", "Please charge!\nAutomatic suspend triggers at %1": "Lütfen şarj edin!\nOtomatik askıya alma %1'de tetikleniyor", "Terminal: Harmonize threshold": "Terminal: Uyumlaştırma eşiği", "Be patient...": "Sabırlı olun...", "Utility buttons": "Yardımcı düğmeler", "Tonal Spot": "Tonal Nokta", "Prevents abrupt increments and restricts volume limit": "Ani artışları önler ve ses sınırını kısıtlar", "Set the current API provider": "Mevcut API sağlayıcısını ayarlayın", "Connection failed. Please inspect manually with the warp-cli command": "Bağlantı başarısız. Lütfen warp-cli komutuyla manuel olarak kontrol edin", "Networking": "Ağ", "Tint icons": "Simgeleri renklendir", "Low battery": "Düşük pil", "Make icons pinned by default": "Simgeleri varsayılan olarak sabitle", "Get the next page of results": "Sonraki sayfa sonuçları al", "Invalid API provider. Supported: \n-": "Geçersiz API sağlayıcısı. Desteklenenler: \n-", "Show \"Locked\" text": "\"Kilitli\" metnini göster", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Fiyatlandırma**: ücretsiz. Veri kullanım politikası OpenRouter hesap ayarlarınıza göre değişir.\n\n**Talimatlar**: OpenRouter hesabına giriş yapın, sağ üst menüdeki Keys'e gidin, Create API Key'e tıklayın", "Not visible to model": "Model için görünmez", "Lock screen": "Ekranı kilitle", "Save to Downloads": "İndirilenler'e kaydet", "Expressive": "İfadeli", "Suspend at": "Askıya alma zamanı", "Jump to current month": "Mevcut aya git", "Bold": "Kalın", "Waifus only | Excellent quality, limited quantity": "Sadece waifu'lar | Mükemmel kalite, sınırlı miktar", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "Açık/koyu modu değiştirmek için tıklayın\n(duvar kağıdı seçildiğinde uygulanır)", "Visualize region": "Bölgeyi görselleştir", "Quote": "Alıntı", "Sleep": "Uyku", "Hit \"/\" to search": "Aramak için \"/\" tuşuna basın", "Hug": "Sarıl", "Report a Bug": "Hata Bildir", "Precipitation": "Yağış", "Crosshair": "Nişangah", "Model set to %1": "Model %1 olarak ayarlandı", "Rows": "Satırlar", "Top": "Üst", "Long break": "Uzun mola", "Superpaste": "Süper yapıştır", "Screen round corner": "Ekran köşe yuvarlama", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Çevrimiçi | Google'ın modeli\nÖncekinden daha yavaş ama daha kaliteli yanıtlar vermesi gereken yeni model", "Rainbow": "Gökkuşağı", "Weeb": "Weeb", "Large language models": "Büyük dil modelleri", "Online models disallowed\n\nControlled by `policies.ai` config option": "Çevrimiçi modellere izin verilmiyor\n\n`policies.ai` yapılandırma seçeneği ile kontrol edilir", "Policies": "Politikalar", "Temperature must be between 0 and 2": "Sıcaklık 0 ile 2 arasında olmalıdır", "Automatic suspend": "Otomatik askıya alma", "Extra wallpaper zoom (%)": "Ekstra duvar kağıdı yakınlaştırması (%)", "GitHub": "GitHub", "%1 | Right-click to configure": "%1 | Yapılandırmak için sağ tıklayın", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**Fiyatlandırma**: Sınırlı kullanımlı ücretsiz katman mevcut. Bakınız https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Talimatlar**: Models izniyle bir GitHub kişisel erişim jetonu oluşturun, ardından buraya API anahtarı olarak ayarlayın\n\n**Not**: Bunu kullanmak için sıcaklık parametresini 1'e ayarlamanız gerekecek", "Edit directory": "Dizini düzenle", "Action": "Eylem", "Search": "Ara", "Tip: right-clicking a group\nalso expands it": "İpucu: bir gruba sağ tıklamak\naynı zamanda genişletir", "Bar": "Çubuk", "Show regions of potential interest": "Potansiyel ilgi alanlarını göster", "Clipboard": "Pano", "Stopwatch": "Kronometre", "Enter text to translate...": "Çevrilecek metni girin...", "App": "Uygulama", "Sides": "Yanlar", "No active player": "Aktif oynatıcı yok", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "Bu uygulamada tüm seçenekler mevcut değil. Sol üst köşedeki \"Yapılandırma dosyası\" düğmesine basarak veya %1'i manuel olarak açarak yapılandırma dosyasını da kontrol etmelisiniz.", "There might be a download in progress": "Devam eden bir indirme olabilir", "Math result": "Matematik sonucu", "Fidelity": "Sadakat", "Prefixes": "Önekler", "Terminal": "Terminal", "Incorrect password": "Yanlış şifre", "Line-separated": "Satır ayrılmış", "Always": "Her zaman", "☕ Break: %1 minutes": "☕ Mola: %1 dakika", "Depends on sidebars": "Kenar çubuklarına bağlı", "Tool set to: %1": "Araç şu şekilde ayarlandı: %1", "Save chat": "Sohbeti kaydet", "Crosshair overlay": "Nişangah bindirmesi", "Keybinds": "Tuş atamaları", "Launch": "Başlat", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Çok fazla yazım hatası yaparsanız daha iyi olabilir,\nancak sonuçlar garip olabilir ve kısaltmalarla çalışmayabilir\n(örn. \"GIMP\" size boyama programını vermeyebilir)", "Choose model": "Model seç", "Base URL": "Temel URL", "Float": "Yüzen", "Wallpaper parallax": "Duvar kağıdı paralaks", "Invalid arguments. Must provide `command`.": "Geçersiz argümanlar. `command` sağlanmalıdır.", "Fully charged": "Tamamen şarj oldu", "Earbang protection": "Kulak patlama koruması", "Low warning": "Düşük uyarısı", "Advanced": "Gelişmiş", "Scroll to change brightness": "Parlaklığı değiştirmek için kaydırın", "Loaded the following system prompt\n\n---\n\n%1": "Aşağıdaki sistem komutu yüklendi\n\n---\n\n%1", "Show next time": "Bir dahaki sefere göster", "Current tool: %1\nSet it with %2tool TOOL": "Mevcut araç: %1\n%2tool ARAÇ ile ayarlayın", "Unread indicator: show count": "Okunmamış göstergesi: sayıyı göster", "Press Super+G to toggle appearance": "Görünümü değiştirmek için Super+G'ye basın", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Bu işe yaramadı. İpuçları:\n- Etiketlerinizi ve NSFW ayarlarınızı kontrol edin\n- Aklınızda bir etiket yoksa, bir sayfa numarası yazın", "Dots": "Noktalar", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Volume mixer": "Ses karıştırıcı", "Config file": "config dosyası", "API key set for %1": "%1 için API anahtarı ayarlandı", "Online via %1 | %2's model": "%1 üzerinden çevrimiçi | %2'nin modeli", "Shell command": "Shell komutu", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Bu tür bölgeler görseller veya ekranın içerme özelliği olan bölümleri olabilir.\nHer zaman doğru olmayabilir.\nBu, yerel olarak çalıştırılan bir görüntü işleme algoritmasıyla yapılır ve AI kullanılmaz.", "Reload Hyprland & Quickshell": "Hyprland ve Quickshell'i Yeniden Yükle", "Resources": "Kaynaklar", "Brightness": "Parlaklık", "Unknown": "Bilinmeyen", "Polling interval (ms)": "Yoklama aralığı (ms)", "Lock": "Kilitle", "Thinking": "Düşünüyor", "Approve": "Onayla", "Unfinished": "Tamamlanmamış", "Random: Konachan": "Rastgele: Konachan", "Connected": "Bağlandı", "Wallpaper safety enforced": "Duvar kağıdı güvenliği uygulandı", "Invalid arguments. Must provide `key` and `value`.": "Geçersiz argümanlar. `key` ve `value` sağlanmalıdır.", "24h": "24s", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "Çubuk konumundan bağımsız olarak ekran köşelerine tıklayarak veya üzerine gelerek kenar çubuklarını açmanıza olanak tanır", "Bar style": "Çubuk stili", "Load:": "Yük:", "Open file link": "Dosya bağlantısını aç", "Ignored if terminal theming is not enabled": "Terminal temalaması etkinleştirilmemişse yok sayılır", "Shutdown": "Kapat", "Hour marks": "Saat işaretleri", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "Rastgele osu! mevsimlik arka plan\nGörsel ~/Resimler/Wallpapers dizinine kaydedilir", "Online | Google's model\nFast, can perform searches for up-to-date information": "Çevrimiçi | Google'ın modeli\nHızlı, güncel bilgi için arama yapabilir", "Current model: %1\nSet it with %2model MODEL": "Mevcut model: %1\n%2model MODEL ile ayarlayın", "Select input device": "Giriş cihazını seç", "Connect to Wi-Fi": "Wi-Fi'ye bağlan", "... and %1 more": "... ve %1 daha fazla", "Cookie clock settings": "Kurabiye saat ayarları", "Brightness and volume": "Parlaklık ve ses", "Choose file": "Dosya seç", "Invalid model. Supported: \n```": "Geçersiz model. Desteklenenler: \n```", "Task Manager": "Görev Yöneticisi", "Charging:": "Şarj oluyor:", "Illegal increment": "Geçersiz artış", "Total:": "Toplam:", "or": "veya", "Battery": "Pil", "Timeout duration (if not defined by notification) (ms)": "Zaman aşımı süresi (bildirim tarafından tanımlanmamışsa) (ms)", "Cancel": "İptal", "Locked": "Kilitli", "Temperature: %1": "Sıcaklık: %1", "Hover to trigger": "Tetiklemek için üzerine gelin", "Command rejected by user": "Komut kullanıcı tarafından reddedildi", "User agent (for services that require it)": "Kullanıcı aracısı (gerektiren hizmetler için)", "Saved to %1": "%1'e kaydedildi", "Emojis": "Emoji'ler", "Color generation": "Renk oluşturma", "Welcome app": "Hoş geldiniz uygulaması", "Humidity": "Nem", "Page %1": "Sayfa %1", "Feels like %1": "Hissedilen %1", "Distro": "Dağıtım", "Transparency": "Şeffaflık", "%1 • %2 tasks": "%1 • %2 görev", "Markdown test": "Markdown testi", "Invalid tool. Supported tools:\n- %1": "Geçersiz araç. Desteklenen araçlar:\n- %1", "No notifications": "Bildirim yok", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Hentai olan | Çok miktarda, çok NSFW, kalite çok değişkendir", "Bluetooth": "Bluetooth", "Resume": "Devam Et", "Work safety": "İş güvenliği", "Temperature\nChange with /temp VALUE": "Sıcaklık\n/temp DEĞER ile değiştirin", "Terminal: Foreground boost (%)": "Terminal: Ön plan artışı (%)", "Night Light | Right-click to toggle Auto mode": "Gece Işığı | Otomatik modu değiştirmek için sağ tıklayın", "Closet": "Dolap", "Yes": "Evet", "Columns": "Sütunlar", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "API anahtarı ayarlamak için %4 komutuyla birlikte iletin\n\nAnahtarı görüntülemek için komutla birlikte \"get\" iletin
\n\n### %1 için:\n\n**Bağlantı**: %2\n\n%3", "Kill conflicting programs?": "Çakışan programlar sonlandırılsın mı?", "For storing API keys and other sensitive information": "API anahtarlarını ve diğer hassas bilgileri saklamak için", "Reject": "Reddet", "Set API key": "API anahtarını ayarla", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Zerochan için notlar:\n- Bir renk girmelisiniz\n- `sidebar.booru.zerochan.username` yapılandırma seçeneğinde zerochan kullanıcı adınızı ayarlayın. [Bunu yapmazsanız yasaklanabilirsiniz](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Content": "İçerik", "Pomodoro": "Pomodoro", "Vertical": "Dikey", "Pick a wallpaper": "Bir duvar kağıdı seçin", "Load chat from %1": "%1'den sohbet yükle", "Launch on startup": "Başlangıçta başlat", "Add": "Ekle", "Style: general": "Stil: genel", "Use Levenshtein distance-based algorithm instead of fuzzy": "Bulanık yerine Levenshtein mesafe tabanlı algoritma kullan", "Shell & utilities theming must also be enabled": "Shell ve yardımcı program temalaması da etkinleştirilmelidir", "Workspace": "Çalışma alanı", "Translator": "Çevirmen", "Free:": "Boş:", "🌿 Long break: %1 minutes": "🌿 Uzun mola: %1 dakika", "Value scroll": "Değer kaydırma", "Bar position": "Çubuk konumu", "Language": "Dil", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Mevcut API uç noktası: %1\n%2mode SAĞLAYICI ile ayarlayın", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "Çoğu cihazda güç düğmesini basılı tutarak zorla kapatma yapılabileceğini unutmayın\nBu sadece kazaların olmasını biraz daha zorlaştırır", "AI": "AI", "Task description": "Görev açıklaması", "Add task": "Görev ekle", "Donate": "Bağış Yap", "Disable NSFW content": "NSFW içeriği devre dışı bırak", "Set the system prompt for the model.": "Model için sistem komutunu ayarlayın.", "Done": "Tamamlandı", "Focus": "Odaklan", "Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json": "Shell yapılandırma dosyasını açın.\nDüğme çalışmıyorsa veya favori düzenleyicinizde açılmıyorsa,\n~/.config/illogical-impulse/config.json dosyasını manuel olarak açabilirsiniz", "View Markdown source": "Markdown kaynağını görüntüle", "Border": "Kenarlık", "Temperature set to %1": "Sıcaklık %1 olarak ayarlandı", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Çevrimiçi | Google'ın modeli\nGoogle'ın kodlama ve karmaşık akıl yürütme görevlerinde mükemmel olan son teknoloji çok amaçlı modeli.", "Message the model... \"%1\" for commands": "Modele mesaj gönderin... komutlar için \"%1\"", "Translation goes here...": "Çeviri buraya gelir...", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "Etkinleştirildiğinde açarken gecikmeyi azaltmak için sağ kenar çubuğunun içeriğini yüklü tutar,\nyaklaşık 15MB tutarlı RAM kullanımı pahasına. Gecikme önemi sisteminizin performansına bağlıdır.\nlinux-cachyos gibi özel bir çekirdek kullanmak yardımcı olabilir", "For desktop wallpapers | Good quality": "Masaüstü duvar kağıtları için | İyi kalite", "🔴 Focus: %1 minutes": "🔴 Odaklan: %1 dakika", "The current system prompt is\n\n---\n\n%1": "Mevcut sistem komutu\n\n---\n\n%1", "About": "Hakkında", "Quick": "Hızlı", "General": "Genel", "UV Index": "UV İndeksi", "Force dark mode in terminal": "Terminalde koyu modu zorla", "Drag or click a region • LMB: Copy • RMB: Edit": "Bir bölgeyi sürükleyin veya tıklayın • Sol Tık: Kopyala • Sağ Tık: Düzenle", "%1 characters": "%1 karakter", "Cloudflare WARP": "Cloudflare WARP", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Fiyatlandırma**: ücretsiz. Veriler eğitim için kullanılır.\n\n**Talimatlar**: Google hesabına giriş yapın, AI Studio'nun Google Cloud projesi oluşturmasına veya her ne istiyorsa izin verin, geri dönün ve Get API key'e tıklayın", "Monochrome": "Monokrom", "Details": "Detaylar", "Issues": "Sorunlar", "Keyboard toggle": "Klavye geçişi", "Might look ass. Unsupported.": "Kötü görünebilir. Desteklenmiyor.", "Download": "İndir", "%1 does not require an API key": "%1 API anahtarı gerektirmiyor", "Style & wallpaper": "Stil ve duvar kağıdı", "Second precision": "Saniye hassasiyeti", "Group style": "Grup stili", "Break": "Mola", "Run": "Çalıştır", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "Keyfinize bakın! Hoş geldiniz uygulamasını istediğiniz zaman Super+Shift+Alt+/ ile yeniden açabilirsiniz. Ayarlar uygulamasını açmak için Super+I tuşuna basın", "Interface Language": "Arayüz Dili", "Game mode": "Oyun modu", "Usage: %1save CHAT_NAME": "Kullanım: %1save SOHBET_ADI", "Thin": "İnce", "Light": "Açık", "When not fullscreen": "Tam ekran değilken", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Komutlar, yapılandırmaları düzenle, ara.\nGerekirse arama moduna geçmek için ekstra bir tur alır", "Privacy Policy": "Gizlilik Politikası", "Timeout (ms)": "Zaman aşımı (ms)", "Allow NSFW content": "NSFW içeriğine izin ver", "Edit": "Düzenle", "Digits in the middle": "Ortada rakamlar", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Çevrimiçi | Google'ın modeli\nMaliyet verimliliği ve yüksek verim için optimize edilmiş bir Gemini 2.5 Flash modeli.", "Weather Service": "Hava Durumu Servisi", "Background": "Arka plan", "Pick random from this folder": "Bu klasörden rastgele seç", "Pressure": "Basınç", "Save": "Kaydet", "Run command": "Komutu çalıştır", "Time to empty:": "Boşalmasına kalan süre:", "Place at bottom": "Alta yerleştir", "Switched to search mode. Continue with the user's request.": "Arama moduna geçildi. Kullanıcının isteğiyle devam edin.", "Performance Profile toggle": "Performans Profili geçişi", "Sidebars": "Kenar çubukları", "Usage: %1load CHAT_NAME": "Kullanım: %1load SOHBET_ADI", "Auto styling with Gemini": "Gemini ile otomatik stil", "Simple digital": "Basit dijital", "No API key set for %1": "%1 için API anahtarı ayarlanmadı", "Enter tags, or \"%1\" for commands": "Etiketleri girin veya komutlar için \"%1\"", "%1 queries pending": "%1 sorgu bekliyor", "Discussions": "Tartışmalar", "Tray": "Tepsi", "Numbers": "Numaralar", "Intelligence": "Zeka", "Open network portal": "Ağ portalını aç", "No further instruction provided": "Başka talimat verilmedi", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "Dil listede yok veya çeviriler eksik mi?\nGemini ile çeviriler oluşturmayı seçebilirsiniz.\n1. Super+A ile sol kenar çubuğunu açın, modeli Gemini olarak ayarlayın (değilse)\n2. /key yazın, Enter'a basın ve talimatları izleyin\n3. /key API_ANAHTARINIZ yazın\n4. Dilinizin yerel ayarını aşağıya yazın ve Oluştur'a basın", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "Yerel kod, örn. tr_TR, fr_FR, de_DE, zh_CN...", "Select language": "Dil seçin", "Generate translation with Gemini": "Gemini ile çeviri oluştur", "Generating...\nDon't close this window!": "Oluşturuluyor...\nBu pencereyi kapatmayın!", "Generate\nTypically takes 2 minutes": "Oluştur\nGenellikle 2 dakika sürer", "Use system file picker": "Sistem dosya seçiciyi kullan", "Wallpaper selector": "Duvar kağıdı seçici", "but force at absolute corner": "ancak mutlak köşede zorla", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "Önceki seçenek kapalı ve bu açık olduğunda,\nkenar çubuğunu açmak için köşenin ucunun üzerine gelebilirsiniz,\nve kalan alan ses/parlaklık kaydırması için kullanılabilir", "Copy path": "Yolu kopyala", "Windows": "Pencereler", "Regenerate": "Yeniden oluştur", "Microphone": "Mikrofon", "Unmuted": "Sessiz değil", "System sound": "Sistem sesi", "Enable now": "Şimdi etkinleştir", "Night Light": "Gece Işığı", "Show aim lines": "Nişan çizgilerini göster", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "Bu neden havalı:\n0 olmayan değerler için, yatay kenar boyunca\nekran köşesine ulaştığınızda tetiklenmez, ancak\ndikey kenar boyunca ulaştığınızda tetiklenir", "Please charge!\nAutomatic suspend triggers at %1%": "Lütfen şarj edin!\nOtomatik askıya alma %1%'de tetikleniyor", "Example use case: eroge on one workspace, dark Discord window on another": "Örnek kullanım durumu: bir çalışma alanında eroge, diğerinde koyu Discord penceresi", "Couldn't recognize music": "Müzik tanınamadı", "Automatic": "Otomatik", "Hint target regions": "Hedef bölgeleri işaretle", "Devices": "Cihazlar", "Eye protection": "Göz koruması", "Japanese": "Japonca", "Layers": "Katmanlar", "Listening...": "Dinleniyor...", "LMB to enable/disable\nRMB to toggle size\nScroll to swap position": "Etkinleştirmek/devre dışı bırakmak için Sol Tık\nBoyutu değiştirmek için Sağ Tık\nKonumu değiştirmek için kaydırın", "Identify Music": "Müziği Tanı", "Quick toggles": "Hızlı geçişler", "Hide sussy/anime wallpapers": "Şüpheli/anime duvar kağıtlarını gizle", "Android": "Android", "Show": "Göster", "Muted": "Sessiz", "Audio input | Right-click for volume mixer & device selector": "Ses girişi | Ses karıştırıcı ve cihaz seçici için sağ tıklayın", "Region selector (screen snipping/Google Lens)": "Bölge seçici (ekran kırpma/Google Lens)", "Total duration timeout (s)": "Toplam süre zaman aşımı (s)", "Music Recognition": "Müzik Tanıma", "Night Light | Right-click to configure": "Gece Işığı | Yapılandırmak için sağ tıklayın", "Anti-flashbang (experimental)": "Parlama önleyici (deneysel)", "Digital clock settings": "Dijital saat ayarları", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Görseller veya ekranın içerme özelliği olan bölümleri olabilir.\nHer zaman doğru olmayabilir.\nBu, yerel olarak çalıştırılan bir görüntü işleme algoritmasıyla yapılır ve AI kullanılmaz.", "Polling interval (m)": "Yoklama aralığı (d)", "Inactive": "İnaktif", "Authentication": "Kimlik Doğrulama", "Full warning": "Tam uyarı", "Power Profile": "Güç Profili", "Content region": "İçerik bölgesi", "Internet": "İnternet", "Record": "Kaydet", "Circle selection": "Daire seçimi", "Edit quick toggles": "Hızlı geçişleri düzenle", "Virtual Keyboard": "Sanal Klavye", "Music Recognized": "Müzik Tanındı", "EasyEffects": "EasyEffects", "Make sure you have songrec installed": "songrec'in kurulu olduğundan emin olun", "Dark Mode": "Koyu Mod", "No device": "Cihaz yok", "Animate time change": "Zaman değişimini canlandır", "It may take a few seconds to update": "Güncellenme birkaç saniye sürebilir", "Polling interval (s)": "Yoklama aralığı (s)", "Perhaps what you're listening to is too niche": "Belki de dinlediğiniz şey çok niş", "Fahrenheit unit": "Fahrenheit birimi", "Sliders": "Kaydırıcılar", "Roman": "Romen", "Number style": "Numara stili", "Intensity": "Yoğunluk", "Google Lens": "Google Lens", "Circle": "Daire", "Hide clipboard images copied from sussy sources": "Şüpheli kaynaklardan kopyalanan pano görsellerini gizle", "Scroll to Bottom": "Alta Kaydır", "Enabled": "Etkin", "Nothing": "Hiçbir şey", "Audio input": "Ses girişi", "with vertical offset": "dikey ofseti ile", "Padding": "Dolgu", "Please unplug the charger": "Lütfen şarj cihazını çıkarın", "Show notifications": "Bildirimleri göster", "Path copied": "Yol kopyalandı", "On-screen keyboard": "Ekran klavyesi", "City name": "Şehir adı", "Click to cycle through power profiles": "Güç profilleri arasında geçiş yapmak için tıklayın", "Recognize music | Right-click to toggle source": "Müziği tanı | Kaynağı değiştirmek için sağ tıklayın", "Use old sine wave cookie implementation": "Eski sinüs dalgası kurabiye uygulamasını kullan", "Rectangular selection": "Dikdörtgen seçim", "Audio output": "Ses çıkışı", "Applications": "Uygulamalar", "Circle to Search": "Arama İçin Daire", "Audio output | Right-click for volume mixer & device selector": "Ses çıkışı | Ses karıştırıcı ve cihaz seçici için sağ tıklayın", "Enable GPS based location": "GPS tabanlı konumu etkinleştir", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "Önce Gemini API anahtarınızı girmeniz gerekecek.\nTalimatlar için kenar çubuğunda /key yazın.", "Sounds": "Sesler", "Active": "Aktif", "Keep awake": "Uyanık tut", "Auto,": "Otomatik,", "Normal": "Normal", "Force hover open at absolute corner": "Mutlak köşede üzerine gelinerek açmayı zorla", "Open the shell config file\nAlternatively right-click to copy path": "Shell yapılandırma dosyasını açın\nAlternatif olarak yolu kopyalamak için sağ tıklayın", "Recognize music": "Müziği tanı", "Stroke width": "Çizgi genişliği", "Use varying shapes for password characters": "Şifre karakterleri için değişen şekiller kullan", "Battery full": "Pil dolu" } ================================================ FILE: dots/.config/quickshell/ii/translations/uk_UA.json ================================================ { "Mo": "Пн/*keep*/", "Tu": "Вт/*keep*/", "We": "Ср/*keep*/", "Th": "Чт/*keep*/", "Fr": "Пт/*keep*/", "Sa": "Сб/*keep*/", "Su": "Нд/*keep*/", "%1 characters": "%1 символів", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Вартість**: безкоштовно. Політика використання даних залежить від параметрів облікового запису OpenRouter.\n\n**Інструкції**: Увійдіть в обліковий запис OpenRouter, перейдіть до розділу «Keys» у верхньому правому меню, натисніть «Create API Key»", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Вартість**: безкоштовно. Дані використовуються для тренування.\n\n**Інструкції**: Увійдіть в обліковий запис Google, дозвольте AI Studio створити проект Google Cloud або те, що вона попросить, поверніться назад і натисніть Get API key", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Примітки для Zerochan:\n- Ви повинні ввести колір\n- Встановіть ваше ім'я користувача zerochan у параметрі конфігурації `sidebar.booru.zerochan.username`. Якщо ви цього не зробите, вас [може бути заблоковано](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "No further instruction provided": "Подальших інструкцій не надано", "Action": "Дія", "Add": "Додати", "Add task": "Додати завдання", "All-rounder | Good quality, decent quantity": "Універсальний | Хороша якість, пристойна кількість", "Allow NSFW": "Дозволити NSFW", "Allow NSFW content": "Дозволити NSFW вміст", "Anime": "Аніме", "Anime boorus": "Аніме боору", "App": "Програма", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Стрілки для навігації, Enter для вибору\nНатисніть Esc або будь де щоб скасувати", "Bluetooth": "Bluetooth", "Brightness": "Яскравість", "Cancel": "Скасувати", "Cheat sheet": "Шпаргалка", "Choose model": "Виберіть модель", "Clean stuff | Excellent quality, no NSFW": "Чистий вміст | Відмінна якість, без NSFW", "Clear": "Очистити", "Clear chat history": "Очистити історію чату", "Clear the current list of images": "Очистити поточний список картинок", "Close": "Закрити", "Copy": "Копіювати", "Copy code": "Копіювати код", "Delete": "Видалити", "Desktop": "Стільниця", "Disable NSFW content": "Вимкнути NSFW вміст", "Done": "Готово", "Download": "Завантажити", "Edit": "Редагувати", "Enter text to translate...": "Введіть текст щоб перекласти...", "Finished tasks will go here": "Завершені завдання зберігаються тут", "For desktop wallpapers | Good quality": "На шпалери | Хороша якість", "For storing API keys and other sensitive information": "Для зберігання API ключів та іншої конфіденційної інформація", "Game mode": "Ігровий режим", "Get the next page of results": "Наступна сторінка результатів", "Hibernate": "Сплячий режим", "Input": "Ввід", "Intelligence": "Інтелект", "Interface": "Інтерфейс", "Invalid arguments. Must provide `key` and `value`.": "Неправильні аргументи. Повинні бути вказані `key` та `value`.", "Jump to current month": "Перейти до поточного місяця", "Keep system awake": "Тримати систему в режимі очікування", "Large images | God tier quality, no NSFW.": "Великі зображення | Божественна якість, без NSFW.", "Large language models": "Великі мовні моделі", "Launch": "Пуск", "Lock": "Блокувати", "Logout": "Вийти", "Markdown test": "Тест Markdown", "Math result": "Результат обчислень", "No audio source": "Немає джерела аудіо", "No media": "Немає медіа", "No notifications": "Немає сповіщень", "Not visible to model": "Не видно для моделі", "Nothing here!": "Тут нічого!", "Notifications": "Сповіщення", "OK": "Гаразд", "Open file link": "Відкрити лінк до файлу", "Output": "Вивід", "Reboot": "Перезапуск", "Reboot to firmware settings": "Перезапуск в параметри UEFI/BIOS", "Reload Hyprland & Quickshell": "Перезавантажити Hyprland та Quickshell", "Run": "Виконати", "Run command": "Виконати команду", "Save": "Зберегти", "Save to Downloads": "Зберегти в Завантаження", "Search": "Пошук", "Search the web": "Шукати в інтернеті", "Search, calculate or run": "Шукати, обчислювати або запускати", "Select Language": "Вибрати мову", "Session": "Сесія", "Set API key": "Вказати ключ API", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Задати температуру (випадковість) моделі. Значення в діапазоні від 0 до 2 для Gemini, від 0 до 1 для інших моделей. Значення за замовчуванням 0.5.", "Set the current API provider": "Встановити поточного провайдера API", "Shutdown": "Вимкнути", "Silent": "Тиша", "Sleep": "Сон", "System": "Система", "Task Manager": "Менеджер завдань", "Task description": "Опис завдання", "Temperature must be between 0 and 2": "Температура має бути між 0 та 2", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Хентай | Велика кількість, багато NSFW, якість сильно варіюється", "The popular one | Best quantity, but quality can vary wildly": "Популярний | Найкраща кількість, але якість може сильно варіюватись", "Thinking": "Замислився", "Translation goes here...": "Переклад буде тут...", "Translator": "Перекладач", "Unfinished": "Незавершений", "Unknown": "Невідмий", "Unknown Album": "Невідомий Альбои", "Unknown Artist": "Невідомий Виконавець", "Unknown Title": "Невідома назва", "View Markdown source": "Дивитися джерело Markdown", "Volume": "Гучність", "Volume mixer": "Мікшер гучності", "Waifus only | Excellent quality, limited quantity": "Лише вайфу | Відмінна якість, обмежена кількість", "Waiting for response...": "Чекаємо відповідь...", "Workspace": "Простір", "Invalid API provider. Supported: \n-": "Неправильний провайдер API. Підтримується: \n-", "Unknown command:": "Невідома команда:", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Введіть /key, щоб почати роботу з онлайн моделями\nCtrl+O, щоб розгорнути бічну панель\nCtrl+P, щоб прибрати бічну панель у вікно", "Provider set to": "Провайдер виставлений на", "Invalid model. Supported: \n```": "Неправельна модель. Підтримується: \n```", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Це не спрацювало. Поради:\n- Перевірте свої теги та параметри NSFW\n- Якщо ви не знаєте тега, введіть номер сторінки", "Switched to search mode. Continue with the user's request.": "Перейшли в режим пошуку. Продовження пошуку за запитом користувача.", "Settings": "Параметри", "Save chat": "Зберегти чат", "Load chat": "Завантажити чат", "or": "або", "Set the system prompt for the model.": "Встановіть системний запит для моделі.", "To Do": "Зробити", "Calendar": "Календар", "Advanced": "Розширені", "About": "Про", "Services": "Сервіси", "Style": "Стиль", "Edit config": "Редагувати конфігурацію", "Colors & Wallpaper": "Кольори та Шпалери", "Light": "Світла", "Dark": "Темна", "Material palette": "Палітра кольорів", "Fidelity": "Вірність", "Fruit Salad": "Фруктовий салат", "Alternatively use /dark, /light, /img in the launcher": "Або використовуйте /dark, /light, /img у лаунчері", "Fake screen rounding": "Фальшиві заокруглення екрану", "When not fullscreen": "Коли не на весь екран", "Choose file": "Вибрати файл", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Випадкові SFW аніме шпалери від Konachan\nЗображення збережено до ~/Pictures/Wallpapers", "Be patient...": "Потерпіть...", "Decorations & Effects": "Декорації та ефекти", "Tonal Spot": "Тональна пляма", "Shell windows": "Вікна оболонки", "Auto": "Авто", "Wallpaper": "Шпалери", "Content": "Вміст", "Title bar": "Заголовок", "Transparency": "Прозорість", "Expressive": "Виразний", "Yes": "Так", "Enable": "Увімкнути", "Rainbow": "Веселка", "Might look ass. Unsupported.": "Виглядатиме дурновато. Не підтримується.", "Monochrome": "Монохромний", "Random: Konachan": "Випадково: Konachan", "Center title": "Назва по центру", "Neutral": "Нейтральний", "Pick wallpaper image on your system": "Виберіть зображення шпалер на вашому комп'ютері", "No": "Ні", "AI": "ШІ", "Local only": "Лише локально", "Policies": "Політика", "Weeb": "Віабу", "Closet": "Шафа", "Bar style": "Стиль заголовку", "Show next time": "Показати пізніше", "Usage": "Використання", "Plain rectangle": "Звичайний квадрат", "Useless buttons": "Безкорисні кнопки", "GitHub": "GitHub", "Style & wallpaper": "Стиль та шпалери", "Configuration": "Конфігурація", "Change any time later with /dark, /light, /img in the launcher": "Змініть будь-коли пізніше за допомогою /dark, /light, /img у лаунчері", "Keybinds": "Комбінації клавіш", "Float": "Плаваюче", "Hug": "Обійми", "Yooooo hi there": "Йоооо, привіт", "illogical-impulse Welcome": "illogical-impulse Вітаємо", "Info": "Інфо", "Volume limit": "Обмеження гучності", "Prevents abrupt increments and restricts volume limit": "Запобігає різкому збільшенню та обмежує ліміт гучності", "Resources": "Ресурси", "12h am/pm": "12г AM/PM", "Base URL": "Базовий лінк", "Audio": "Аудіо", "Networking": "Мережування", "Format": "Формат", "Time": "Час", "Battery": "Батарея", "Prefixes": "Префікси", "Emojis": "Емодзі", "Earbang protection": "Захист навушників", "Automatically suspends the system when battery is low": "Автоматично призупиняє роботу системи, коли батарея розряджається", "Automatic suspend": "Автоматична зупинка", "Suspend at": "Зупиняти на", "Max allowed increase": "Максимально допустимий приріст", "Web search": "Пошук в інтернеті", "Polling interval (ms)": "Інтервал між опитуваннями (мс)", "Clipboard": "Буфер обміну", "Low warning": "Незначні попередження", "24h": "24г", "Use Levenshtein distance-based algorithm instead of fuzzy": "Використовуйте алгоритм на основі відстані Левенштейна замість нечіткого", "System prompt": "Системний запит", "12h AM/PM": "12г AM/PM", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Було б краще, якби ви зробили купу помилок,\nале результати можуть бути дивними і не працювати з абревіатурами\n(наприклад, «GIMP» може не вивести програму для малювання).", "Critical warning": "Критичні попередження", "User agent (for services that require it)": "User agent (для сервісів де це потрібно)", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Такими областями можуть бути зображення або частини екрана, які мають певні обмеження.\nМоже бути не завжди точним.\nЦе робиться за допомогою алгоритму обробки зображень, запущеного локально, і не використовується ШІ.", "Note: turning off can hurt readability": "Примітка: вимкнення може погіршити читабельність", "Workspaces shown": "Показані простори", "Dark/Light toggle": "Перемикач Світлої/Темної", "Dock": "Лоток", "Weather": "Погода", "Pinned on startup": "Прикріплено до запуску", "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "Порада: приховуйте іконки і завжди показуйте цифри для класичного досвіду illogical-impulse", "Appearance": "Вигляд", "Always show numbers": "Завжди показувати номери", "Buttons": "Кнопки", "Keyboard toggle": "Перемикання клавіатури", "Scale (%)": "Розмір (%)", "Overview": "Огляд", "Rows": "Рядки", "Borderless": "Без меж", "Screenshot tool": "Інструмент створення скріншотів", "Number show delay when pressing Super (ms)": "Затримка відображення номера при натисканні клавіші Super (мс)", "Timeout (ms)": "Тайм-аут (ms)", "Show app icons": "Показувати іконки програм", "Workspaces": "Простори", "Columns": "Стовбці", "On-screen display": "Екранний дисплей", "Screen snip": "Скріншот", "Mic toggle": "Перемикач мікрофону", "Hover to reveal": "Наведіть курсор, щоб відкрити", "Bar": "Панель", "Show background": "Показувати задній фон", "Show regions of potential interest": "Показати регіони потенційного інтересу", "Color picker": "Вибір кольору", "Help & Support": "Допомога та підтримка", "Discussions": "Дискусії", "Color generation": "Генератор кольорів", "Dotfiles": "Дотфайли", "Distro": "Дистрибютив", "Privacy Policy": "Політика Конфіденційності", "Documentation": "Документація", "Shell & utilities theming must also be enabled": "Тему оболонки та утиліт також слід увімкнути", "illogical-impulse": "illogical-impulse", "Donate": "Донат", "Terminal": "Термінал", "Shell & utilities": "Оболонка та утиліти", "Qt apps": "Програми Qt", "Report a Bug": "Повідомити про помилку", "Issues": "Проблеми", "Drag or click a region • LMB: Copy • RMB: Edit": "Перетягніть або клацніть регіон - LMB: Копіювати - RMB: Редагувати", "Current model: %1\nSet it with %2model MODEL": "Поточна модель: %1\nЗадати її за допомогою %2model МОДЕЛЬ", "Message the model... \"%1\" for commands": "Написати моделі... «%1» для команд", "No API key set for %1": "Немає вказаного API ключач для %1", "Loaded the following system prompt\n\n---\n\n%1": "Завантажився наступний системний запит\n\n---\n\n%1", "%1 | Right-click to configure": "%1 | ПКМ для налаштування", "API key set for %1": "ключ API вказано для %1", "Online via %1 | %2's model": "Онлайн через %1 | %2's модель", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Поточна кінцева точка API: %1\nВстановіть її за допомогою %2mode ПРОВАЙТЕР", "Go to source (%1)": "До джерела (%1)", "Temperature set to %1": "Температура виставлена на %1", "Enter tags, or \"%1\" for commands": "Напишіть тег або \"%1\" для команд", "%1 queries pending": "%1 запитів на розгляді", "API key:\n\n```txt\n%1\n```": "ключ API:\n\n```txt\n%1\n```", "Uptime: %1": "Активно: %1", "%1 Safe Storage": "%1 Надійне зберігання", "%1 does not require an API key": "%1 не потребує API ключа", "Temperature: %1": "температура: %1", "Model set to %1": "Модель встановлена на %1", "Page %1": "Сторінка %1", "Local Ollama model | %1": "Локальна модель Оллама | %1", "The current system prompt is\n\n---\n\n%1": "Поточний запит системи\n\n---\n\n%1", "Unknown function call: %1": "Невідомий виклик функції: %1", "%1 notifications": "%1 сповіщень", "Load chat from %1": "Завантажено чат з %1", "Load prompt from %1": "Завантажено запит з %1", "Save chat to %1": "Зберегти чат до %1", "Weather Service": "Сервіс погоди", "Cannot find a GPS service. Using the fallback method instead.": "Не вдається знайти службу GPS. Замість цього використовується резервний метод.", "Critically low battery": "Критичний рівень заряду батареї", "Select output device": "Виберіть вихідний пристрій", "Code saved to file": "Код збережений у файл", "Online models disallowed\n\nControlled by `policies.ai` config option": "Онлайн-моделі заборонено\n\nКерується параметром конфігурації `policies.ai`", "Scroll to change volume": "Прокрутіть, щоб змінити гучність", "Elements": "Елементи", "%1 • %2 tasks": "%1 • %2 завдань", "Download complete": "Завантаження завершено", "Please charge!\nAutomatic suspend triggers at %1": "Будь ласка, зарядіть! \nАвтоматичне призупинення спрацьовування при %1", "Cloudflare WARP": "Cloudflare WARP", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Scroll to change brightness": "Прокрутіть щоб змінити яскравість", "Connection failed. Please inspect manually with the warp-cli command": "Помилка зєднання. Перевірте самостійно за допомогою команди warp-cli", "Select input device": "Виберіть вхідний пристрій", "Registration failed. Please inspect manually with the warp-cli command": "Помилка реєстрації. Перевірте самостійно за допомогою команди warp-cli", "Consider plugging in your device": "Подумайте про те, щоб підключити свій пристрій", "Low battery": "Низький заряд батареї", "Saved to %1": "Збережено до %1", "Sunset": "Захід сонця", "UV Index": "Індекс UV", "Humidity": "Вологість", "Wind": "Вітер", "Sunrise": "Світанок", "Pressure": "Тиск", "Visibility": "Видимість", "Precipitation": "Опади", "No API key\nSet it with /key YOUR_API_KEY": "Немає API ключа\nВстановіть його можна командою /key YOUR_API_KEY", "Your package manager is running": "Ваш пакетний менеджер запущено", "Night Light | Right-click to toggle Auto mode": "Нічне світло | ПКМ щоб увімкнути автоматичний режим", "Gives the model search capabilities (immediately)": "Надає можливість пошуку моделі (негайно)", "Depends on workspace": "Залежно від простору", "Invalid arguments. Must provide `command`.": "Неправельні параметри. Потрібно вказати `command`.", "Temperature\nChange with /temp VALUE": "Температура\nЗмінити можна командою /temp ЗНАЧЕННЯ", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "Онлайн | Модель Google\nСучасна багатоцільова модель Google, яка чудово справляється з кодуванням і складними завданнями на міркування.", "EasyEffects | Right-click to configure": "EasyEffects | ПКМ щоб налаштувати", "Thought": "Думка", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Онлайн | Модель Google \nМодель Gemini 2.5 Flash оптимізована для економічної ефективності та високої пропускної здатності.", "API key is set\nChange with /key YOUR_API_KEY": "API ключ встановлено\nЗмінити можна командою /key YOUR_API_KEY", "Current tool: %1\nSet it with %2tool TOOL": "Поточний інструмент: %1\nВстановіть його командою %2tool TOOL", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**Інструкції**: Увійдіть в обліковий запис Mistral, перейдіть до розділу «Keys» на бічній панелі, натисніть «Create new key»", "Usage: %1tool TOOL_NAME": "Використання: %1tool TOOL_NAME", "Online | Google's model\nFast, can perform searches for up-to-date information": "Онлайн | модель Google \nШвидкий, може здійснювати пошук актуальної інформації", "Approve": "Схвалити", "Preferred wallpaper zoom (%)": "Бажаний маштаб шпалер (%)", "Performance Profile toggle": "Перемикач профілю продуктивності", "Total token count\nInput: %1\nOutput: %2": "Загальна кількість токенів\nВхідні: %1\nВихідні: %2", "Wallpaper parallax": "Обємні шпалери", "Invalid tool. Supported tools:\n- %1": "Неправельний інструмент. Підтримуються:\n- %1", "Usage: %1load CHAT_NAME": "Виокристання: %1load CHAT_NAME", "Reject": "Відхилити", "Usage: %1save CHAT_NAME": "Використання: %1save CHAT_NAME", "Set the tool to use for the model.": "Вкажіть інструмент для роботи з моделью", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "Онлайн | %1's модель | Надає швидкі, чуйні та добре відформатовані відповіді. Недоліки: не дуже охоче виконує завдання; може вигадувати виклики невідомих функцій", "Depends on sidebars": "Залежно від бокових панелей", "Command rejected by user": "Команда відхилена користувачем", "There might be a download in progress": "Можливо, триває завантаження", "Disable tools": "Вимкнути інструмент", "Tool set to: %1": "Інструмет вказано: %1", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "Команди, редагування конфігурацій, пошук.\nВиконує додаткову дію для переходу в режим пошуку, якщо це потрібно", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Щоб задати ключ API, передайте його командою %4\n\nЩоб переглянути ключ, передайте \"get\" командою
\n\n### Для %1:\n\n**Лінк**: %2\n\n%3", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Онлайн | Модель Google\nНовіша модель, яка повільніша за свою попередницю, але має надавати якісніші відповіді" } ================================================ FILE: dots/.config/quickshell/ii/translations/vi_VN.json ================================================ { "Unknown function call: %1": "Hàm không xác định: %1", "Show next time": "Hiển thị lần sau", "Fidelity": "Khá giống gốc", "Open file link": "Mở liên kết tệp", "Interrupts possibility of overview being toggled on release.": "Ngăn mở overview khi nhả nút.", "No audio source": "Không có nguồn âm thanh", "Might look ass. Unsupported.": "Có thể rất tệ. Không được hỗ trợ.", "Jump to current month": "Nhảy đến tháng hiện tại", "Delete": "Xóa", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**Giá**: miễn phí. Dữ liệu được sử dụng cho mục đích huấn luyện. **Hướng dẫn**: Đăng nhập vào tài khoản Google, cho phép AI Studio tạo dự án Google Cloud gì đó, quay lại rồi ấn Get API key", "Rainbow": "Cầu vồng", "%1 does not require an API key": "%1 không cần API key", "Choose model": "Chọn model", "Prevents abrupt increments and restricts volume limit": "Chặn thay đổi đột ngột và giới hạn âm lượng", "%1 characters": "%1 ký tự", "Change any time later with /dark, /light, /img in the launcher": "Thay đổi bất cứ lúc nào sau này với /dark, /light, /img trong launcher", "Tonal Spot": "Tonal Spot", "Neutral": "Trung tính", "To Do": "Cần làm", "Auto": "Tự động", "Polling interval (ms)": "Thời gian lặp lại (ms)", "Center title": "Căn giữa tiêu đề", "Lock": "Khóa màn hình", "Screen snip": "Chụp màn hình (chọn vùng)", "User agent (for services that require it)": "User agent (nếu cần)", "Report a Bug": "Báo lỗi", "Shutdown": "Tắt máy", "Keyboard toggle": "Mở/đóng bàn phím ảo", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "Cái nhiều hentai nhất | Số lượng rất tốt, rất nhiều NSFW, chất lượng có thể khác nhau nhiều", "Download": "Tải xuống", "Note: turning off can hurt readability": "Ghi chú: nếu tắt có thể khó đọc", "Local Ollama model | %1": "Model Ollama trên máy | %1", "Silent": "Im lặng", "Columns": "Số cột", "Set with /mode PROVIDER": "Set with /mode PROVIDER", "Issues": "Các vấn đề", "Policies": "Chính sách", "Load chat from %1": "Tải trò chuyện từ %1", "Unknown Album": "Album không xác định", "Yes": "Có", "Battery": "Pin", "Material palette": "Kiểu material", "Chain of Thought": "Dòng suy nghĩ", "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key.": "Cần cái này vì GlobalShortcut.onReleased cho một phím của Quickshell được kích hoạt kể cả khi ban ân phím khác trước khi thả phím đó.", "Low warning": "Cảnh báo thấp", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": ". Với Zerochan:\n- Hãy nhập tên một màu (bằng tiếng Anh)\n- Đặt username Zerochan trong tùy chọn `sidebar.booru.zerochan.username`. Bạn [có thể bị ban nếu không tuân thủ](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "Brightness": "Độ sáng", "Yooooo hi there": "Yooooo chào bạn", "Colors & Wallpaper": "Màu sắc & Hình nền", "No media": "Không có media", "Critical warning": "Cảnh báo rất thấp", "Mic toggle": "Bật/tắt mic", "12h AM/PM": "12h AM/PM", "Large language models": "Mô hình ngôn ngữ lớn", "Markdown test": "Test markdown", "Temperature: %1": "Nhiệt độ: %1", "Edit": "Sửa", "Waifus only | Excellent quality, limited quantity": "Chỉ waifus | Chất lượng xuất sắc, số lượng hạn chế", "Cheat sheet": "Bảng tra cứu", "Current model: %1\nSet it with %2model MODEL": "Model đang chọn: %1\nChọn với lệnh %2model MODEL", "Provider set to": "Đã đặt nhà cung cấp thành", "Clear": "Xóa hết", "GitHub": "GitHub", "App": "Ứng dụng", "Title bar": "Thanh tiêu đề", "Web search": "Tìm kiếm web", "Invalid model. Supported: \n```": "Model không hợp lệ. Các lựa chọn: \n```", "Calendar": "Lịch", "Done": "Đã xong", "Monochrome": "Đen trắng", "Show regions of potential interest": "Hiển thị vùng thông minh", "Dark/Light toggle": "Chuyển chế độ sáng/tối", "Unknown command:": "Lệnh không xác định:", "Allow NSFW content": "Cho phép nội dung NSFW", "Closes cheatsheet on press": "Đóng bảng tra cứu khi ấn", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "Chỉnh giá trị nhiệt độ (sự ngẫu nhiên) của model. Giá trị 0-2 với Gemini, 0-1 với các model khác. Mặc định là 0.5.", "Invalid API provider. Supported: \n-": "Nhà cung cấp API không hợp lệ. Các lựa chọn: \n-", "Shell windows": "Cửa sổ của shell", "Loaded the following system prompt\n\n---\n\n%1": "Đã tải chỉ dẫn hệ thống sau đây\n\n---\n\n%1", "Clipboard": "Clipboard", "For storing API keys and other sensitive information": "Để lưu trữ API key và các thông tin nhạy cảm khác", "Wallpaper": "Hình nền", "Decorations & Effects": "Trang trí & Hiệu ứng", "AI": "AI", "Large images | God tier quality, no NSFW.": "Ảnh kích thước lớn | Chất lượng cực tốt, không có NSFW.", "When not fullscreen": "Khi không toàn màn hình", "Resources": "Tài nguyên", "Light": "Sáng", "Weeb": "Wibu", "Disable NSFW content": "Tắt nội dung NSFW", "OK": "OK", "Screenshot tool": "Công cụ chụp màn hình", "Enable": "Bật", "Select Language": "Chọn ngôn ngữ", "System": "Hệ thống", "Emojis": "Emoji", "The current system prompt is\n\n---\n\n%1": "Chỉ dẫn hệ thống hiện tại như sau\n\n---\n\n%1", "Translator": "Dịch", "Sleep": "Ngủ", "Action": "Hành động", "Audio": "Âm thanh", "Show background": "Hiện nền", "All-rounder | Good quality, decent quantity": "Tốt đều | Chất lượng tốt, số lượng ổn", "Documentation": "Tài liệu", "Terminal": "Terminal", "Distro": "Distro", "Clear chat history": "Xóa lịch sử trò chuyện", "Float": "Nổi", "No further instruction provided": "No further instruction provided", "Choose file": "Chọn tệp", "Set the system prompt for the model.": "Đặt chỉ dẫn hệ thống cho model.", "Unknown Title": "Bài hát không rõ tên", "Math result": "Kết quả phép tính", "Logout": "Đăng xuất", "Privacy Policy": "Chính sách quyền riêng tư", "Style": "Phong cách", "Borderless": "Không viền", "Set API key": "Đặt API key", "Clean stuff | Excellent quality, no NSFW": "Sạch sẽ | Chất lượng xuất sắc, không có NSFW", "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "Thử nghiệm | Trực tuyến | Model của Google\nCó thể làm nhiều hơn một chút nhưng không tìm kiếm nhanh chóng", "Toggles cheatsheet on press": "Mở/đóng bảng tra cứu khi ấn", "Thinking": "Đang nghĩ", "Earbang protection": "Bảo vệ tai", "Advanced": "Nâng cao", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "Có thể tốt hơn nếu bạn gõ lệch phím nhiều,\nnhưng kết quả có thể hơi lạ và không hoạt động tốt với từ viết tắt\n(ví dụ tìm \"GIMP\" có thể không ra cái chương trình vẽ)", "Shell & utilities theming must also be enabled": "Cần Shell & công cụ cũng bật", "Desktop": "Màn hình chính", "Anime": "Anime", "Qt apps": "Các ứng dụng Qt", "Style & wallpaper": "Phong cách & hình nền", "Finished tasks will go here": "Việc đã xong sẽ hiện ở đây", "Weather": "Thời tiết", "Settings": "Cài đặt", "Shell & utilities": "Shell & tiện ích", "Unfinished": "Chưa xong", "Random: Konachan": "Ngẫu nhiên: Konachan", "Pick wallpaper image on your system": "Chọn hình nền trên máy", "Volume": "Âm lượng", "Add": "Thêm", "Hibernate": "Ngủ đông", "Run": "Chạy", "Keep system awake": "Giữ hệ thống bật", "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.": "Để đảm bảo luôn hoạt động, dùng binditn = MODKEYS, catchall trong một submap luôn được kích hoạt bao trùm mọi thứ.", "Plain rectangle": "Hình chữ nhật", "%1 queries pending": "%1 lệnh gọi đang chờ", "Temperature set to %1": "Nhiệt độ đã được đặt thành %1", "Notifications": "Thông báo", "System prompt": "Chỉ dẫn hệ thống", "Hover to reveal": "Đặt chuột vào để hiện", "No": "Không", "Bar": "Bar", "Search the web": "Tìm kiếm web", "Page %1": "Trang %1", "Reboot": "Khởi động lại", "Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "Các vùng có thể là hình ảnh hoặc phần của màn hình cớ vẻ được bao chứa.\nKhông luôn chính xác.\nSử dụng một thuật toán xử lý ảnh chạy trên máy, không dùng AI.", "Show app icons": "Hiện biểu tượng ứng dụng", "Closet": "Nghiện mà ngại", "Set the current API provider": "Đặt nguồn cung cấp API", "Cancel": "Hủy", "Networking": "Mạng", "Overview": "Overview", "Search, calculate or run": "Tìm, tính hoặc chạy", "Useless buttons": "Mấy nút vô dụng", "Transparency": "Sự trong suốt", "Temperature must be between 0 and 2": "Nhiệt độ phải trong khoảng từ 0 đến 2", "Automatically suspends the system when battery is low": "Tự động ngủ khi pin thấp", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "Endpoint API hiện tại: %1\nĐặt với lệnh %2mode PROVIDER", "Services": "Các dịch vụ", "Reload Hyprland & Quickshell": "Tải lại Hyprland & Quickshell", "Automatic suspend": "Tự động ngủ", "illogical-impulse Welcome": "illogical-impulse - Xin chào", "Interface": "Giao diện", "Load chat": "Tải cuộc trò chuyện", "Number show delay when pressing Super (ms)": "Thời gian chờ hiện số khi nhấn Super (ms)", "Clear the current list of images": "Xóa danh sách hình ảnh hiện tại", "Fake screen rounding": "Giả bo tròn màn hình", "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "Mẹo: Ẩn biểu tượng và luôn hiển thị số nếu\nmuốn giống trải nghiệm illogical-impulse gốc", "Launch": "Chạy", "%1 notifications": "%1 thông báo", "%1 | Right-click to configure": "%1 | Ấn chuột phải để chỉnh", "Unknown Artist": "Nghệ sĩ không xác định", "Appearance": "Giao diện", "Task Manager": "Quản lí ứng dụng đang chạy", "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "Để đặt API key, viết nó sau lệnh\n\nĐể xem lại, viết \"get\" sau lệnh
\n\n### Với %1:\n\n**Link**: %2\n\n%3", "Opens cheatsheet on press": "Mở bảng tra cứu khi ấn", "Invalid arguments. Must provide `key` and `value`.": "Biến không hợp lệ. cần cả `key` và `value`.", "About": "Giới thiệu", "illogical-impulse": "illogical-impulse", "Help & Support": "Trợ giúp", "Enter tags, or \"%1\" for commands": "Nhập tag hoặc \"%1\" để xem các lệnh", "Format": "Định dạng", "Content": "Giống gốc", "Edit config": "Sửa config", "Bluetooth": "Bluetooth", "Be patient...": "Bình tĩnh...", "Discussions": "Thảo luận", "Anime boorus": "Các booru anime", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "Quả này không được. Mẹo:\n- Kiểm tra tag và cài đặt NSFW\n- Nếu không nghĩ ra tag nào có thể nhập số trang", "Task description": "Mô tả công việc", "Max allowed increase": "Thay đổi tối đa", "Rows": "Số hàng", "Switched to search mode. Continue with the user's request.": "Đã chuyển sang chế độ tìm kiếm. Tiếp tục với yêu cầu của người dùng.", "Use Levenshtein distance-based algorithm instead of fuzzy": "Sử dụng thuật toán dùng khoảng cách Levenshtein thay vì fuzzy", "Copy": "Sao chép", "12h am/pm": "12h am/pm", "Unknown": "Không xác định", "Waiting for response...": "Đang chờ phản hồi...", "Workspace": "Workspace", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "Hình nền Anime SFW ngẫu nhiên từ Konachan\nẢnh được lưu vào ~/Pictures/Wallpapers", "Online via %1 | %2's model": "Trực tuyến qua %1 | Model của %2", "Always show numbers": "Luôn hiện số", "or": "hoặc", "Drag or click a region • LMB: Copy • RMB: Edit": "Kéo thả hoặc chọn vùng • Chuột trái: Sao chép • Chuột phải: Chỉnh sửa", "Local only": "Chỉ trên máy", "Donate": "Ủng hộ", "Online | Google's model\nGives up-to-date information with search.": "Trực tuyến | Model của Google\nCó thể tìm kiếm để cung cấp thông tin cập nhật.", "Run command": "Chạy lệnh", "Dotfiles": "Dotfiles", "Volume limit": "Giới hạn âm lượng", "On-screen display": "Âm lượng/độ sáng", "Reboot to firmware settings": "Khởi động lại vào cài đặt firmware", "Workspaces shown": "Số workspace hiển thị", "Save": "Lưu", "The popular one | Best quantity, but quality can vary wildly": "Phổ biến | Số lượng tốt nhất, nhưng chất lượng không biết đâu vào đâu", "Save chat": "Lưu cuộc trò chuyện", "Intelligence": "Trí tuệ", "Translation goes here...": "Bản dịch sẽ hiện ở đây...", "Toggle clipboard query on overview widget": "Mở/đóng tìm kiếm clipboard trên overview", "Search": "Tìm kiếm", "Timeout (ms)": "Thời gian chờ (ms)", "24h": "24h", "Color picker": "Chọn màu", "Save to Downloads": "Lưu vào Downloads", "No notifications": "Không có thông báo", "Game mode": "Chế độ game", "Alternatively use /dark, /light, /img in the launcher": "Có thể dùng /dark, /light, /img trong launcher", "Info": "Thông tin", "Dock": "Dock", "Pinned on startup": "Ghim khi khởi động", "Suspend at": "Tạm dừng ở", "Fruit Salad": "Salad hoa quả", "API key:\n\n```txt\n%1\n```": "API key:\n\n```txt\n%1\n```", "API key set for %1": "API key đã đặt cho %1", "Not visible to model": "Không hiển thị cho model", "Expressive": "Biểu cảm", "Enter text to translate...": "Nhập văn bản để dịch...", "Usage": "Cách dùng", "Message the model... \"%1\" for commands": "Hỏi model... \"%1\" để xem lệnh", "Keybinds": "Phím tắt", "Model set to %1": "Đã đặt model thành %1", "Scale (%)": "Tỉ lệ (%)", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "Gõ /key để bắt đầu dùng các model trực tuyến\nCtrl+O để mở rộng sidebar\nCtrl+P để nhấc sidebar thành cửa sổ", "Output": "Đầu ra", "Uptime: %1": "Máy bật được %1", "For desktop wallpapers | Good quality": "Cho hình nền máy tính | Chất lượng tốt", "Nothing here!": "Không có gì ở đây!", "Close": "Đóng", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "Phím mũi tên để chọn, Enter để xác nhận\nEsc hoặc ấn bất kỳ đâu để thoát", "Copy code": "Sao chép code", "Load prompt from %1": "Tải chỉ dẫn từ %1", "Time": "Thời gian", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**Giá**: miễn phí. Chính sách sử dụng dữ liệu tùy thuộc vào cài đặt tài khoản OpenRouter của bạn.\n\n**Hướng dẫn**: Đăng nhập vào tài khoản OpenRouter, mở Keys ở menu góc trên bên phải, ấn Create API Key", "Bar style": "Phong cách bar", "Configuration": "Cài đặt", "Prefixes": "Kí tự đầu", "No API key set for %1": "Không có API key cho %1", "Add task": "Thêm công việc", "Volume mixer": "Trộn âm lượng", "Go to source (%1)": "Đi đến nguồn (%1)", "The current API used. Endpoint:": "API đang sử dụng. Endpoint:", "View Markdown source": "Xem nguồn Markdown", "Input": "Đầu vào", "Allow NSFW": "Cho phép NSFW", "Session": "Session", "Detach left sidebar into a window/Attach it back": "Nhấc sidebar trái thành cửa sổ/Đặt nó lại", "Night Light": "Lọc ánh sáng xanh", "Workspaces": "Các workspace", "Dark": "Tối", "Base URL": "Base URL", "Hug": "Ôm", "Buttons": "Các nút", "Get the next page of results": "Lấy trang kết quả tiếp theo", "%1 Safe Storage": "Lưu trữ an toàn %1", "Color generation": "Chỉnh màu", "Select output device": "Chọn đầu ra", "Select input device": "Chọn đầu vào", "%1 • %2 tasks": "%1 • %2 việc cần làm", "Online models disallowed\n\nControlled by `policies.ai` config option": "Model trực tuyến không được cho phép\n\nCài đặt bởi lựa chọn `policies.ai`", "Download complete": "Đã tải xong", "Code saved to file": "Code đã lưu vào file", "Critically low battery": "Pin rất thấp", "Scroll to change brightness": "Cuộn để thay đổi độ sáng", "Cloudflare WARP": "Cloudflare WARP", "Toggles bar on press": "Mở/đóng bar khi ấn", "Saved to %1": "Đã lưu vào %1", "Elements": "Nguyên tố", "Save chat to %1": "Lưu chat vào %1", "Connection failed. Please inspect manually with the warp-cli command": "Kết nối không thành công. Hãy xem lại với lệnh warp-cli", "Weather Service": "Thời tiết", "Registration failed. Please inspect manually with the warp-cli command": "Đăng ký không thành công. Hãy xem lại với lệnh warp-cli", "Consider plugging in your device": "Hãy cắm nguồn thiết bị của bạn", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Cannot find a GPS service. Using the fallback method instead.": "Không tìm thấy dịch vụ GPS. Đang sử dụng phương pháp dự phòng.", "Opens bar on press": "Mở bar khi ấn", "Low battery": "Pin yếu", "Scroll to change volume": "Cuộn để thay đổi âm lượng", "Please charge!\nAutomatic suspend triggers at %1": "Hãy sạc pin!\nHệ thống sẽ tự động ngủ khi pin xuống %1", "Closes bar on press": "Đóng bar khi ấn", "Mo": "T2/*keep*/", "Tu": "T3/*keep*/", "We": "T4/*keep*/", "Th": "T5/*keep*/", "Fr": "T6/*keep*/", "Sa": "T7/*keep*/", "Su": "CN/*keep*/", "Approve": "Chấp nhận", "Set the tool to use for the model.": "Chọn công cụ để sử dụng với model.", "No API key\nSet it with /key YOUR_API_KEY": "Không có API key\nĐặt bằng /key API_KEY", "API Key": "API Key", "EasyEffects | Right-click to configure": "EasyEffects | Ấn chuột phải để chỉnh cài đặt", "API key is set": "API key đã đặt", "Invalid tool. Supported tools:\n- %1": "Công cụ không hợp lệ. Các lựa chọn:\n- %1", "Thought": "Suy nghĩ", "Current tool: %1\nSet it with %2tool TOOL": "Công cụ: %1\nĐặt bằng %2tool CÔNG_CỤ", "Edit shell config file": "Chỉnh file config của shell", "A download might be in progress": "Có thể có tệp đang tải", "API key is set\nChange with /key YOUR_API_KEY": "API key đã đặt\nThay đổi bằng /key API_KEY", "Temperature\nChange with /temp VALUE": "Nhiệt độ (độ ngẫu nhiên)\nThay đổi bằng /temp GIÁ_TRỊ", "Your package manager is running": "Package manager đang chạy", "Usage: %1load CHAT_NAME": "Hướng dẫn: %1load TÊN_ĐOẠN_CHAT", "Cannot switch to search mode from %1": "Không thể chuyển sang chế độ tìm kiếm từ %1", "UV Index": "Chỉ số UV", "Online | Google's model\nFast, can perform searches for up-to-date information": "Trực tuyến | Model của Google\nNhanh, có thể tìm kiếm Google để lấy thông tin cập nhật", "Token count": "Số lượng token", "Experimental | Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Thử nghiệm | Trực tuyến | Model của Google\nModel Gemini 2.5 Flash tối ưu hóa cho hiệu quả chi phí và băng thông.", "Wallpaper parallax": "Hiệu ứng parallax (hình nền)", "Usage: %1tool TOOL_NAME": "Hướng dẫn: %1tool TÊN_CÔNG_CỤ", "Humidity": "Độ ẩm", "Invalid tool. Supported tools: %1": "Công cụ không hợp lệ. Các lựa chọn: %1", "Sunset": "Hoàng hôn", "Total token count\nInput: %1\nOutput: %2": "Số lượng token\nInput: %1\nOutput: %2", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "Trực tuyến | Model của Google\nModel Gemini 2.5 Flash tối ưu hóa cho hiệu quả chi phí và băng thông.", "Visibility": "Tầm nhìn", "Pressure": "Áp suất", "Depends on workspace": "Phụ thuộc vào workspace", "Reject": "Từ chối", "Precipitation": "Lượng mưa", "Wind": "Gió", "Usage: %1save CHAT_NAME": "Hướng dẫn: %1save TÊN_ĐOẠN_CHAT", "Enable EasyEffects": "Bật EasyEffects", "Night Light | Click to toggle, right-click to toggle automatic mode": "Lọc ánh sáng xanh | ấn để bật/tắt, ấn chuột phải để bật/tắt chế độ tự động", "Night Light | Right-click to toggle Auto mode": "Lọc ánh sáng xanh | Ấn chuột phải để bật/tắt chế độ tự động", "No command provided": "Không có lệnh nào được cung cấp", "No API key": "Không có API key", "Performance Profile toggle": "Nút Performance Profile", "Sunrise": "Bình minh", "Online | Google's model\nNewer one that's slower": "Trực tuyến | Model của Google\nMới hơn nhưng chậm hơn", "Command rejected by user": "Lệnh bị từ chối bởi người dùng", "Experimental | Online | Google's model\nCan do a little more but takes an extra turn to perform search": "Thử nghiệm | Trực tuyến | Model của Google\nCó thể làm nhiều hơn một chút nhưng mất thêm một lượt để thực hiện tìm kiếm", "Depends on sidebars": "Phụ thuộc vào sidebar", "Temperature": "Nhiệt độ", "There might be a download in progress": "Có thể có tệp đang tải", "EasyEffects": "EasyEffects", "Token count | Input: %1 | Output: %2": "Số lượng token | Input: %1 | Output: %2", "Tool set to %1": "Công cụ được đặt thành %1", "Invalid arguments. Must provide `command`.": "Tham số không hợp lệ. Phải cung cấp `command`.", "A download is in progress": "Có một tệp đang tải", "illogical-impulse Settings": "Cài đặt illogical-impulse", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "Trực tuyến | Model của Google\nMới hơn nhưng chậm hơn so với phiên bản trước nhưng nên cung cấp câu trả lời chất lượng cao hơn", "Preferred wallpaper zoom (%)": "Tỷ lệ thu phóng hình nền (%)", "Function Response": "Phản hồi function" } ================================================ FILE: dots/.config/quickshell/ii/translations/zh_CN.json ================================================ { "Mo": "一/*keep*/", "Tu": "二/*keep*/", "We": "三/*keep*/", "Th": "四/*keep*/", "Fr": "五/*keep*/", "Sa": "六/*keep*/", "Su": "日/*keep*/", "%1 characters": "%1 个字符", "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key": "**价格**:免费。数据用于训练。\n\n**说明**:登录 Google 账户,允许 AI Studio 创建 Google Cloud 项目或其他要求,然后返回并点击获取 API 密钥", "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key": "**价格**:免费。数据使用政策取决于你的 OpenRouter 账户设置。\n\n**说明**:登录 OpenRouter 账户,在右上角菜单中选择 Keys,点击创建 API 密钥", ". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!": "。Zerochan 注意事项:\n- 你需要指定一个颜色\n- 请在 `sidebar.booru.zerochan.username` 配置项内填写你的 Zerochan 用户名。如果不这样做[将可能会被封禁](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!", "No further instruction provided": "未提供进一步说明", "API key set for %1": "已为 %1 设置 API 密钥", "API key:\n\n```txt\n%1\n```": "API 密钥:\n\n```txt\n%1\n```", "Action": "操作", "Add": "添加", "Add task": "添加任务", "All-rounder | Good quality, decent quantity": "全能型 | 质量好,数量适中", "Allow NSFW": "允许 NSFW", "Allow NSFW content": "允许 NSFW 内容", "Anime": "动漫", "Anime boorus": "动漫图库", "App": "应用", "Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel": "方向键导航,回车选择\nEsc 或点击任意地方取消", "Bluetooth": "蓝牙", "Brightness": "亮度", "Cancel": "取消", "Cheat sheet": "快捷键指南", "Choose model": "选择模型", "Clean stuff | Excellent quality, no NSFW": "清洁内容 | 优秀质量,无 NSFW", "Clear chat history": "清除聊天记录", "Clear the current list of images": "清除当前图片列表", "Close": "关闭", "Copy": "复制", "Copy code": "复制代码", "Current API endpoint: %1\nSet it with %2mode PROVIDER": "当前 API 端点:%1\n使用 %2mode PROVIDER 设置", "Delete": "删除", "Desktop": "桌面", "Disable NSFW content": "禁用 NSFW 内容", "Done": "完成", "Download": "下载", "Edit": "编辑", "Enter text to translate...": "输入要翻译的文本...", "Finished tasks will go here": "已完成的任务将显示在这里", "For desktop wallpapers | Good quality": "桌面壁纸专用 | 质量好", "For storing API keys and other sensitive information": "用于存储 API 密钥和其他敏感信息", "Game mode": "游戏模式", "Get the next page of results": "获取下一页结果", "Go to source (%1)": "转到源 (%1)", "Hibernate": "休眠", "Input": "输入", "Intelligence": "智能", "Interface": "界面", "Invalid arguments. Must provide `key` and `value`.": "参数无效。必须提供 `key` 和 `value`。", "Jump to current month": "跳转到当前月份", "Keep system awake": "保持系统唤醒", "Large images | God tier quality, no NSFW.": "大尺寸图片 | 顶级质量,无 NSFW", "Large language models": "大语言模型", "Local Ollama model | %1": "本地 Ollama 模型 | %1", "Lock": "锁定", "Logout": "注销", "Markdown test": "Markdown 测试", "Math result": "数学结果", "No API key set for %1": "未为 %1 设置 API 密钥", "No media": "无媒体", "Not visible to model": "对模型不可见", "Nothing here!": "这里什么都没有!", "Notifications": "通知", "OK": "确定", "Open file link": "打开文件链接", "Output": "输出", "Page %1": "第 %1 页", "Reboot": "重启", "Reboot to firmware settings": "重启到固件设置", "Reload Hyprland & Quickshell": "重新加载 Hyprland 与 Quickshell", "Run": "运行", "Save": "保存", "Save to Downloads": "保存到下载文件夹", "Search": "搜索", "Search, calculate or run": "搜索、计算或运行", "Select Language": "选择语言", "Session": "会话", "Set API key": "设置 API 密钥", "Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.": "设置模型的温度(随机性)。Gemini 模型范围为 0 到 2,其他模型为 0 到 1。默认值为 0.5。", "Set the current API provider": "设置当前 API 提供商", "Shutdown": "关机", "Silent": "静音", "Sleep": "睡眠", "System": "系统", "Task Manager": "任务管理器", "Task description": "任务描述", "Temperature must be between 0 and 2": "温度必须在 0 到 2 之间", "Temperature set to %1": "温度已设置为 %1", "Temperature: %1": "温度:%1", "The hentai one | Great quantity, a lot of NSFW, quality varies wildly": "成人向 | 数量巨大,大量 NSFW,质量参差不齐", "The popular one | Best quantity, but quality can vary wildly": "最受欢迎 | 数量最多,但质量参差不齐", "Thinking": "思考中", "Translation goes here...": "翻译结果会显示在这里...", "Translator": "翻译", "Unfinished": "未完成", "Unknown": "未知", "Unknown Album": "未知专辑", "Unknown Artist": "未知艺术家", "Unknown Title": "未知标题", "Unknown function call: %1": "未知函数调用:%1", "View Markdown source": "查看 Markdown 源码", "Volume": "音量", "Volume mixer": "音量混合器", "Waifus only | Excellent quality, limited quantity": "仅限角色 | 优秀质量,数量有限", "Workspace": "工作区", "%1 Safe Storage": "%1 安全存储", "%1 does not require an API key": "%1 不需要 API 密钥", "%1 | Right-click to configure": "%1 | 右键以配置", "Invalid API provider. Supported: \n- ": "无效的 API 提供商。支持的有:\n- ", "Unknown command: ": "未知命令:", "Provider set to ": "提供商已设置为 ", "Invalid model. Supported: \n```\n": "无效模型。支持的有:\n```\n", "Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。", "Enter tags, or \"%1\" for commands": "输入标签,或 “%1” 以查看命令", "Online via %1 | %2's model": "在线 | 通过 %1 | %2 的模型", "That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number": "没有找到结果。提示:\n- 检查你的标签和 NSFW 设置\n- 如果还没想到标签,可以直接输入页码", "Settings": "设置", "Save chat": "保存聊天记录", "Load chat": "加载聊天记录", "or": "或", "Set the system prompt for the model.": "为模型设置系统提示词。", "To Do": "待办", "Calendar": "日历", "Advanced": "高级", "About": "关于", "Services": "服务", "Light": "浅色", "Dark": "深色", "Fidelity": "保真", "Fruit Salad": "水果沙拉", "When not fullscreen": "非全屏时", "Choose file": "选择文件", "Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers": "随机 Konachan SFW 动漫壁纸\n图片会保存到 ~/Pictures/Wallpapers", "Be patient...": "请耐心等待...", "Tonal Spot": "色调点", "Auto": "自动", "Content": "内容", "Transparency": "透明度", "Expressive": "表现力", "Yes": "是", "Enable": "启用", "Rainbow": "彩虹", "Might look ass. Unsupported.": "可能效果很差。不支持。", "Monochrome": "单色", "Random: Konachan": "随机:Konachan", "Neutral": "中性", "Pick wallpaper image on your system": "在系统中选择壁纸图片", "No": "否", "AI": "AI", "Local only": "仅限本地", "Policies": "策略", "Weeb": "二次元", "Closet": "隐藏", "Show next time": "下次显示", "Usage": "用法", "Useless buttons": "无用按钮", "GitHub": "GitHub", "Style & wallpaper": "样式与壁纸", "Configuration": "配置", "Keybinds": "快捷键", "Float": "悬浮", "Hug": "贴合", "illogical-impulse Welcome": "illogical-impulse 欢迎页", "Info": "信息", "Volume limit": "音量限制", "Prevents abrupt increments and restricts volume limit": "防止骤增并限制音量", "Resources": "资源", "12h am/pm": "12小时制 am/pm", "Base URL": "基础 URL", "Audio": "声音", "Networking": "网络", "Format": "格式", "Time": "时间", "Battery": "电池", "Prefixes": "前缀", "Emojis": "表情符号", "Earbang protection": "防爆音保护", "Automatically suspends the system when battery is low": "电池电量低时自动挂起系统", "Automatic suspend": "自动挂起", "Max allowed increase": "最大允许增幅", "Web search": "网页搜索", "Polling interval (ms)": "轮询间隔(毫秒)", "Clipboard": "剪贴板", "Low warning": "低电量警告", "24h": "24小时制", "Use Levenshtein distance-based algorithm instead of fuzzy": "使用 Levenshtein 距离算法替代模糊匹配", "System prompt": "系统提示词", "12h AM/PM": "12小时制 AM/PM", "Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)": "如果你经常打错字可能更好用,但结果可能会奇怪,并且可能无法匹配缩写(如 “GIMP” 可能搜不到绘图程序)", "Critical warning": "临界警告", "User agent (for services that require it)": "用户代理(部分服务需要)", "Workspaces shown": "显示的工作区数", "Dark/Light toggle": "深浅色切换", "Dock": "停靠栏", "Weather": "天气", "Pinned on startup": "启动时固定", "Always show numbers": "始终显示数字", "Keyboard toggle": "键盘切换", "Scale (%)": "缩放比例(%)", "Overview": "概览", "Rows": "行数", "Number show delay when pressing Super (ms)": "按下 Super 时的数字显示延迟(毫秒)", "Timeout (ms)": "显示时间(毫秒)", "Show app icons": "显示应用图标", "Workspaces": "工作区", "Columns": "列数", "On-screen display": "屏幕显示", "Screen snip": "屏幕截图", "Mic toggle": "麦克风切换", "Hover to reveal": "悬停显示", "Bar": "条栏", "Color picker": "取色器", "Help & Support": "帮助与支持", "Discussions": "讨论区", "Color generation": "配色生成", "Dotfiles": "配置文件", "Distro": "发行版", "Privacy Policy": "隐私政策", "Documentation": "文档", "Shell & utilities theming must also be enabled": "必须同时启用 Shell 与工具主题", "Ignored if terminal theming is not enabled": "终端主题未启用时不生效", "illogical-impulse": "illogical-impulse", "Donate": "捐助", "Terminal": "终端", "Shell & utilities": "Shell 与工具", "Qt apps": "Qt 应用", "Force dark mode in terminal": "强制终端使用深色模式", "Report a Bug": "报告问题", "Issues": "问题追踪", "Current model: %1\nSet it with %2model MODEL": "当前模型:%1\n使用 %2model MODEL 设置", "Message the model... \"%1\" for commands": "向模型发送消息... “%1” 以查看命令", "The current system prompt is\n\n---\n\n%1": "当前系统提示词为\n\n---\n\n%1", "Model set to %1": "模型已设置为 %1", "Loaded the following system prompt\n\n---\n\n%1": "已加载以下系统提示词\n\n---\n\n%1", "%1 notifications": "%1 条通知", "Save chat to %1": "保存聊天记录到 %1", "Load chat from %1": "从 %1 加载聊天记录", "Load prompt from %1": "从 %1 加载提示词", "%1 • %2 tasks": "%1 • %2 项任务", "Online models disallowed\n\nControlled by `policies.ai` config option": "已禁止在线模型\n\n由 `policies.ai` 配置项控制", "Low battery": "电量低", "Registration failed. Please inspect manually with the warp-cli command": "注册失败。请使用 warp-cli 命令手动检查", "Code saved to file": "代码已保存到文件", "Consider plugging in your device": "请考虑为你的设备充电", "Weather Service": "天气服务", "Cloudflare WARP (1.1.1.1)": "Cloudflare WARP (1.1.1.1)", "Cloudflare WARP": "Cloudflare WARP", "Download complete": "下载完成", "Critically low battery": "电量极低", "Scroll to change brightness": "滚动以调节亮度", "Saved to %1": "已保存到 %1", "Cannot find a GPS service. Using the fallback method instead.": "无法找到 GPS 服务。正在使用备用方法。", "Elements": "元素", "Scroll to change volume": "滚动以调节音量", "Connection failed. Please inspect manually with the warp-cli command": "连接失败。请使用 warp-cli 命令手动检查", "UV Index": "紫外线指数", "Pressure": "气压", "Visibility": "能见度", "Sunrise": "日出", "Sunset": "日落", "Humidity": "湿度", "Wind": "风", "Precipitation": "降水量", "Time to full:": "距离充满:", "Time to empty:": "距离耗尽:", "Fully charged": "已充满电", "Charging:": "充电功率:", "Discharging:": "放电功率:", "No pending tasks": "没有要做的任务", "... and %1 more": "... 还有 %1 个", "Used:": "已用:", "Free:": "可用:", "Total:": "总计:", "Load:": "负载:", "Medium": "适中", "Tint icons": "图标着色", "Performance Profile toggle": "性能配置切换", "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**说明**:登录 Mistral 账户,在侧边栏中选择 Keys,点击 Create new key", "Invalid arguments. Must provide `command`.": "参数无效。必须提供 `command`。", "Thought": "思考", "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "在线 | Google 的模型\n针对成本效益和高吞吐量优化的 Gemini 2.5 Flash 模型。", "Online | Google's model\nFast, can perform searches for up-to-date information": "在线 | Google 的模型\n速度快,可搜索最新信息", "Your package manager is running": "你的包管理器正在运行", "Gives the model search capabilities (immediately)": "为模型提供搜索功能(即时)", "Set the tool to use for the model.": "设置模型使用的工具。", "Night Light | Right-click to toggle Auto mode": "夜间模式 | 右键切换自动模式", "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "在线 | %1 的模型 | 提供快速、响应迅速且格式良好的答案。缺点:不太积极主动;可能编造未知的函数调用", "Depends on workspace": "随工作区移动", "Usage: %1tool TOOL_NAME": "用法:%1tool 工具名称", "Tray": "托盘", "Usage: %1save CHAT_NAME": "用法:%1save 聊天名称", "Approve": "批准", "Depends on sidebars": "随侧边栏移动", "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "执行命令、编辑配置、搜索。\n如果需要,会额外执行一次切换到搜索模式", "Up %1": "运行 %1", "Tool set to: %1": "工具已设置为 %1", "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "在线 | Google 的模型\nGoogle 最先进的多用途模型,在编程和复杂推理任务方面表现卓越。", "Tint app icons": "应用图标着色", "Preferred wallpaper zoom (%)": "首选壁纸缩放比例(%)", "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请使用 %4 命令传递\n\n要查看密钥,请在命令中传递 “get”
\n\n### 对于 %1:\n\n**链接**:%2\n\n%3", "No API key\nSet it with /key YOUR_API_KEY": "无 API 密钥\n使用 /key YOUR_API_KEY 设置", "Total token count\nInput: %1\nOutput: %2": "总词元数\n输入:%1\n输出:%2", "Disable tools": "禁用工具", "API key is set\nChange with /key YOUR_API_KEY": "API 密钥已设置\n使用 /key YOUR_API_KEY 更改", "Usage: %1load CHAT_NAME": "用法:%1load 聊天名称", "Sidebars": "侧边栏", "Temperature\nChange with /temp VALUE": "温度\n使用 /temp VALUE 更改", "Current tool: %1\nSet it with %2tool TOOL": "当前工具:%1\n使用 %2tool TOOL 设置", "There might be a download in progress": "可能有下载正在进行", "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "在线 | Google 的模型\n比前代模型更慢但应该提供更高质量答案的新模型", "EasyEffects | Right-click to configure": "EasyEffects | 右键以配置", "Command rejected by user": "用户拒绝了命令", "Invalid tool. Supported tools:\n- %1": "无效工具。支持的工具:\n- %1", "Keep right sidebar loaded": "保持右侧边栏加载", "Reject": "拒绝", "Enter password": "输入密码", "Automatically hide": "自动隐藏", "**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\n\n**Note**: To use this you will have to set the temperature parameter to 1": "**定价**:提供免费层级,速率有限。详情见 https://docs.github.com/en/billing/concepts/product-billing/github-models\n\n**说明**:生成一个具有 Models 权限的 GitHub 个人访问令牌,并在此处将其设置为 API 密钥\n\n**注意**:使用此提供商时需要将温度参数设置为 1", "Conflicts with the shell's system tray implementation": "与 Shell 的系统托盘实现冲突", "Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I": "祝你愉快!可以随时按 Super+Shift+Alt+/ 重新打开欢迎应用。要打开设置应用,请按 Super+I", "Reset": "重置", "☕ Break: %1 minutes": "☕ 休息:%1 分钟", "Pomodoro": "番茄钟", "Long break": "长休息", "Resume": "继续", "Start": "开始", "Hi there! First things first...": "嗨!先做些重要的事情...", "Refreshing (manually triggered)": "刷新中(手动触发)", "Kill conflicting programs?": "结束冲突程序?", "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help": "启用后会保持右侧边栏内容常驻,以减少打开时的延迟,代价是大约 15MB 的持续内存使用。延迟的影响程度取决于系统性能。使用像 linux-cachyos 这样的自定义内核可能会有所帮助", "Config file": "配置文件", "Stopwatch": "秒表", "Break": "休息", "Shell conflicts killer": "冲突终止程序", "Vertical": "垂直", "🔴 Focus: %1 minutes": "🔴 专注:%1 分钟", "System uptime:": "系统运行时间:", "Focus": "专注", "Attach a file. Only works with Gemini.": "附加文件。仅适用于 Gemini。", "🌿 Long break: %1 minutes": "🌿 长休息:%1 分钟", "Always": "始终", "To Do:": "待办:", "Incorrect password": "密码错误", "Timer": "计时器", "Conflicts with the shell's notification implementation": "与 Shell 的通知实现冲突", "Pause": "暂停", "Feels like %1": "体感温度 %1", "Lap": "计时", "Welcome app": "欢迎应用", "Corner style": "角落样式", "Language": "语言", "Select the language for the user interface.\n\"Auto\" will use your system's locale.": "选择用户界面的语言。\n“自动”将使用系统区域设置。", "Auto (System)": "自动(系统)", "Interface Language": "界面语言", "Paired": "已配对", "Hit \"/\" to search": "按 “/” 以搜索", "Region width": "区域宽度", "Math": "数学", "When this is off you'll have to click": "若关闭则需要点击来打开", "Edit directory": "编辑目录", "Pills": "胶囊", "No active player": "无活动播放器", "Search wallpapers": "搜索壁纸", "Rect": "矩形", "Make sure your player has MPRIS support\nor try turning off duplicate player filtering": "请确保你的播放器支持 MPRIS\n或尝试关闭重复播放器过滤", "Shell command": "Shell 命令", "Screen round corner": "屏幕圆角", "Password": "密码", "Bluetooth devices": "蓝牙设备", "Wallpaper & Colors": "壁纸与配色", "Details": "详细信息", "Connected": "已连接", "Open network portal": "打开网络门户", "Bar style": "条栏样式", "Connect": "连接", "Superpaste": "超级粘贴", "Value scroll": "滚动调整数值", "Line-separated": "线条分隔", "Region height": "区域高度", "Pick a wallpaper": "挑选壁纸", "Visualize region": "显示区域", "Bottom": "底部", "Usage: %1superpaste NUM_OF_ENTRIES[i]\nSupply i when you want images\nExamples:\n%1superpaste 4i for the last 4 images\n%1superpaste 7 for the last 7 entries": "用法:%1superpaste 条目数[i]\n需要图片时加上 i\n示例:\n%1superpaste 4i 粘贴最近 4 张图片\n%1superpaste 7 粘贴最近 7 个条目", "Terminal: Harmony (%)": "终端:协调度(%)", "Forget": "忘记", "Background": "背景", "Top": "顶部", "Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.": "并非所有选项都在此应用中提供。你还应点击左上角的“配置文件”按钮,或手动打开 %1 以查看配置文件。", "General": "通用", "Right": "右侧", "Utility buttons": "工具按钮", "Quick": "快速", "Terminal: Foreground boost (%)": "终端:前景增强(%)", "Left": "左侧", "Tip: right-clicking a group\nalso expands it": "提示:右键点击一个分组\n也可将其展开", "Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner": "稍后可在启动器中通过 /dark、/light、/wallpaper 随时更改\n如果 Shell 的配色没有变化:\n 1. 用 Super+N 打开右侧边栏\n 2. 点击右上角的“重新加载 Hyprland 与 Quickshell”", "Place at bottom": "放置在底部", "Allows you to open sidebars by clicking or hovering screen corners regardless of bar position": "无论条栏位置,都允许通过点击或悬停在屏幕角落来打开侧边栏", "Positioning": "位置", "Disconnect": "断开连接", "Unknown device": "未知设备", "Make icons pinned by default": "默认固定图标", "Bar & screen": "条栏与屏幕", "Corner open": "角落打开", "Hover to trigger": "悬停以触发", "Terminal: Harmonize threshold": "终端:协调阈值", "Bar position": "条栏位置", "Place the corners to trigger at the bottom": "将触发角落放置在底部", "Brightness and volume": "亮度与音量", "Connect to Wi-Fi": "连接到 Wi‑Fi", "Group style": "分组样式", "Launch on startup": "启动时锁屏", "Also unlock keyring": "同时解锁密钥环", "Tip: Close a window with Super+Q": "提示: 使用 Super+Q 关闭窗口", "Remember that on most devices one can always hold the power button to force shutdown\nThis only makes it a tiny bit harder for accidents to happen": "请记住,大多数设备仍可以通过长按电源键强制关机\n此选项只会略微降低误触的可能性而已", "This is usually safe and needed for your browser and AI sidebar anyway\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)": "这通常是安全的,并且对浏览器和 AI 侧边栏也是必要的\n主要对使用“启动时锁屏”而非显示管理器(如 GDM、SDDM 等)执行锁屏的用户有用", "Show \"Locked\" text": "显示“已锁定”字样", "at": "在", "Style: general": "样式:通用", "Pick random from this folder": "从此文件夹随机选择", "Back": "返回", "Cancel wallpaper selection": "取消壁纸选择", "Timeout duration (if not defined by notification) (ms)": "显示时间(若通知未指定)(毫秒)", "Enable blur": "启用模糊", "Click to toggle light/dark mode\n(applied when wallpaper is chosen)": "点击来切换浅色/深色模式\n(仅在选择壁纸后生效)", "Use the system file picker instead\nRight-click to make this the default behavior": "改为使用系统文件选择器\n右键以将此设为默认行为", "Center clock": "居中时钟", "Lock screen": "锁屏", "Crosshair code (in Valorant's format)": "准星代码(瓦罗兰特格式)", "Random: osu! seasonal": "随机:osu! 季节性壁纸", "Work safety": "安全模式", "Require password to power off/restart": "需要密码来关机或重启", "Random osu! seasonal background\nImage is saved to ~/Pictures/Wallpapers": "随机 osu! 季节性壁纸\n图片会保存到 ~/Pictures/Wallpapers", "Open editor": "打开编辑器", "Extra wallpaper zoom (%)": "额外壁纸缩放(%)", "Security": "安全", "Clock style": "时钟样式", "Style: Blurred": "样式:模糊", "Locked": "已锁定", "Wallpaper safety enforced": "已启用壁纸安全模式", "Hour hand": "时针", "Automatic": "自动", "Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate": "想要的语言不在列表内或翻译不完整?\n你可以选择使用 Gemini 为其生成翻译。\n1. 按 Super+A 打开左侧边栏,将模型设置为 Gemini(如果不已经是了的话)\n2. 输入 /key,按 Enter 然后跟随说明\n3. 输入 /key YOUR_API_KEY\n4. 在下方输入你的语言代码并按下生成", "Auto styling with Gemini": "使用 Gemini 自动决定样式", "Audio output | Right-click for volume mixer & device selector": "音频输出 | 右键打开音量合成器与设备选择器", "Most busy": "最繁杂处", "Title font": "标题字体", "Nerd font icons": "Nerd Font 图标", "Could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.": "可能是带有一定边界的图片或屏幕部分。结果不一定总是准确。\n这是通过本地的图片处理算法完成的,过程没有 AI 参与。", "Hollow": "空心", "Turn on from sunset to sunrise": "在日落到日出期间开启", "Notes": "笔记", "It may take a few seconds to update": "可能需要几秒钟来更新", "Google Lens": "Google 智能镜头", "Monospace font": "等宽字体", "Auto, ": "自动,", "Classic": "经典", "Font family name (e.g., JetBrains Mono NF)": "字体名称(如 JetBrains Mono NF)", "Clear all": "全部清除", "Show aim lines": "显示瞄准线", "System sound": "系统声音", "Search for apps": "搜索应用", "Circle to Search": "圈定即搜", "Thin": "纤细", "Content region": "内容区域", "Bubble": "气泡", "Enable opening zoom animation": "启用打开时的缩放动画", "Secured": "安全", "Wi-Fi": "Wi-Fi", "Manage my account": "管理我的账户", "Regenerate": "重新生成", "%1\nInternet access": "%1\nInternet 访问", "Font family name (e.g., Space Grotesk)": "字体名称(如 Space Grotesk)", "Inactive": "未启用", "Least busy": "最空旷处", "Constantly rotate": "持续旋转", "Anti-flashbang (experimental)": "防高亮保护(实验性)", "Bold": "加粗", "Open the shell config file\nAlternatively right-click to copy path": "打开 Shell 配置文件\n或右键以复制路径", "Description font size": "描述字体大小", "Dot": "圆点", "Second hand": "秒针", "Generate translation with Gemini": "使用 Gemini 生成翻译", "Enable GPS based location": "启用基于 GPS 的位置", "Microphone": "麦克风", "Virtual Keyboard": "屏幕键盘", "Shut down": "关机", "Digits in the middle": "在中心显示数字", "If you want to somehow use fingerprint unlock...": "如果你想想办法用指纹解锁的话...", "Couldn't recognize music": "未识别到歌曲", "Split buttons": "拆分按键", "Number style": "数字样式", "Write something here...\nUse '-' to create copyable bullet points, like this:\n\nSheep fricker\n- 4x Slab\n- 1x Boat\n- 4x Redstone Dust\n- 1x Sticky Piston\n- 1x End Rod\n- 4x Redstone Repeater\n- 1x Redstone Torch\n- 1x Sheep": "在这里写些什么...\n使用 '-' 来创建可复制的列表项,比如这样:\n\n羊羊快♂乐机\n- 4x 半砖\n- 1x 船\n- 4x 红石粉\n- 1x 黏性活塞\n- 1x 末地烛\n- 4x 红石中继器\n- 1x 红石火把\n- 1x 绵羊", "Network": "网络", "Overlay: Floating Image": "叠加面板:悬浮图像", "Unpin from taskbar": "从任务栏取消固定", "Sound input": "声音输入", "Not connected": "未连接", "Use macOS-like symbols for mods keys": "为修饰键使用 macOS 风格的图标", "Use symbols for mouse": "使用图标表示鼠标键", "Fonts": "字体", "Circle": "圈选", "Wallpaper selector": "壁纸选择器", "(Plugged in)": "(电源已接通)", "Sound effects": "声音效果", "Intensity": "强度", "Close window": "关闭窗口", "Video Recording Path": "视频录制路径", "Speakers (%1): %2": "扬声器 (%1): %2", "Perhaps what you're listening to is too niche": "也许你听的音乐太小众了", "Animate time change": "时间变化动画", "Set FPS limit": "设置帧数限制", "Identify Music": "识别音乐", "Focusing": "专注", "Sound output": "声音输出", "Type /key to get started with online models\nCtrl+O to expand sidebar\nCtrl+P to pin sidebar\nCtrl+D to detach sidebar": "输入 /key 来开始使用在线模型\nCtrl+O 拓宽侧边栏\nCtrl+P 固定侧边栏\nCtrl+D 分离侧边栏", "Parallax": "视差效果", "EasyEffects": "EasyEffects", "Show only when locked": "仅在锁屏时显示", "Date style": "日期样式", "Font family name (e.g., Google Sans Flex)": "字体名称(如 Google Sans Flex)", "+%1 notifications": "+%1 个通知", "Hide clipboard images copied from sussy sources": "隐藏剪贴板中来自奇奇怪怪来源的图片", "Use Hyprlock (instead of Quickshell)": "使用 Hyprlock(而非 Quickshell)", "Darken screen": "屏幕变暗", "Generating...\nDon't close this window!": "生成中...\n请勿关闭此窗口!", "Dial style": "表盘样式", "Anti-flashbang": "防高亮保护", "Please charge!\nAutomatic suspend triggers at %1%": "请充电!\n将在电量为 %1% 时自动挂起", "Battery full": "电量已充满", "More Bluetooth settings": "更多蓝牙设置", "Center icons": "图标居中", "Generate\nTypically takes 2 minutes": "生成\n通常需要 2 分钟", "Overlay: Crosshair": "叠加面板:准星", "You can also manually edit cheatsheet.superKey": "你也可以手动编辑 cheatsheet.superKey 配置项", "Layers": "显示层", "Sign out": "注销", "Android": "安卓", "Recognize music | Right-click to toggle source": "识别音乐 | 右键以切换源", "Why this is cool:\nFor non-0 values, it won't trigger when you reach the\nscreen corner along the horizontal edge, but it will when\nyou do along the vertical edge": "为什么这很酷:\n对于非 0 的数值,当你沿着水平边缘抵达屏幕角时,它不会触发;\n但当你沿着垂直边缘抵达屏幕角时,它就会触发", "Locale code, e.g. fr_FR, de_DE, zh_CN...": "语言代码,如 fr_FR、de_DE、zh_CN 等", "of %1": "共 %1", "Clock style (locked)": "时钟样式(锁屏时)", "City name": "城市名", "Make sure you have songrec installed": "请确保你已安装 songrec", "Windows": "窗口", "Power Profile": "电源模式", "Used for code and terminal": "用于代码和终端", "Select language": "选择语言", "File Explorer": "文件资源管理器", "Saved ": "已保存 ", "Not secured": "不安全", "Overlay: General": "叠加面板:通用", "Keybind font size": "快捷键字体大小", "Nothing": "空空如也", "Main font": "主字体", "End session": "结束专注", "Listening...": "正在听取...", "Font family name (e.g., Readex Pro)": "字体名称(如 Readex Pro)", "Fill": "填充", "Dark Mode": "深色模式", "Restart": "重启", "Dots": "圆点", "%1 mins": "%1 分钟", "Font used for Nerd Font icons": "用于 Nerd Font 图标的字体", "Region selector (screen snipping/Google Lens)": "区域选择器(屏幕截图与 Google 智能镜头)", "Battery: %1%2": "电池状态:%1%2", "Click to cycle through power profiles": "点击以循环切换电源模式", "When the previous option is off and this is on,\nyou can still hover the corner's end to open sidebar,\nand the remaining area can be used for volume/brightness scroll": "当上一个选项为关闭且此项开启时,你仍然\n可以通过悬停在角落的末端来打开侧边栏,\n剩余的区域将可用于滚动调节音量与亮度", "Widgets": "小组件", "On-screen keyboard": "屏幕键盘", "Used for general UI text": "用于通用 UI 文本", "Line": "线条", "Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"": "如用 󱕐 来表示 “Scroll ↓”,󱕑 “Scroll ↑”,L󰍽 “LMB”,R󰍽 “RMB”,以及 󱕒 “Scroll ↑/↓” 和 ⇞/⇟ 来表示 “Page_↑/↓”", "Unmuted": "已打开", "Path copied": "路径已复制", "Uses Gemini to categorize the wallpaper then picks a preset based on it.\nYou'll need to set Gemini API key on the left sidebar first.\nImages are downscaled for performance, but just to be safe,\ndo not select wallpapers with sensitive information.": "使用 Gemini 对壁纸进行分类,然后根据分类选择一个预设。\n你需要先在左侧边栏设置 Gemini API 密钥。\n图片会被降低分辨率以提高性能, 但为了安全起见,\n请勿选择包含敏感信息的壁纸。", "Unread indicator: show count": "未读指示器:显示数量", "RAM": "内存", "Saving...": "保存中...", "Illegal increment": "超过最大增量限制", "\nLMB to enable/disable\nRMB to toggle size\nScroll to swap position": "\n左键以启用/禁用\n右键以切换尺寸\n滚动以交换位置", "Health:": "电池健康:", "Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")": "使用多个“键帽”显示修饰键和按键(如显示为 “Ctrl + A” 而非 “Ctrl A”,或 “󰘴 + A” 而非 “󰘴 A”)", "Cookie clock settings": "曲奇时钟设置", "Eye protection": "护眼选项", "Tooltips": "悬停提示", "See fewer": "查看更少", "Click to show": "点击以显示", "Circle selection": "圈定选区", "Enter a valid number": "请输入有效的数字", "Music Recognition": "音乐识别", "Sounds": "提示音", "Input device": "输入设备", "On": "开", "Hide sussy/anime wallpapers": "隐藏可疑或动漫壁纸", "Full": "完整", "Image source": "图像来源", "Night Light": "夜间模式", "Digital clock settings": "数字时钟设置", "e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc": "如用 󰘴 来表示 Ctrl,󰘵 来表示 Alt,󰘶 来表示 Shift 等", "Use system file picker": "使用系统文件选择器", "Show hidden icons": "显示隐藏的图标", "Exceeded max allowed": "已超过最高限制", "Please unplug the charger": "请拔掉充电器", "Numbers": "数字", "Example use case: eroge on one workspace, dark Discord window on another": "使用示例:在一个工作区玩小黄游,另一个工作区开着深色的 Discord 窗口", "Enabled": "已打开", "Recognize music": "识别音乐", "Digital": "数字", "Audio input | Right-click for volume mixer & device selector": "音频输入 | 右键打开音量合成器与设备选择器", "Use old sine wave cookie implementation": "使用旧版正弦波形曲奇实现", "Used for displaying numbers": "用于显示数字", "Music Recognized": "识别到歌曲", "Numbers font": "数字字体", "Media": "媒体", "Quick toggles": "快捷设置", "Copy path": "复制路径", "Screenshot Path (leave empty to just copy)": "屏幕截图路径(留空则只复制)", "Draggable": "可移动", "Off": "关", "Super key symbol": "Super 键图标", "Normal": "正常", "Scroll to Bottom": "滚动到底部", "Audio output": "音频输出", "Use varying shapes for password characters": "使用多样形状显示密码字符", "Hour marks": "时标", "Night Light | Right-click to configure": "夜间模式 | 右键以配置", "Edit quick toggles": "编辑快捷设置", "Total duration timeout (s)": "总持续时长(秒)", "Font family name": "字体名称", "Rectangular selection": "矩形选区", "Sides": "边数", "Stroke width": "笔画粗细", "Widget: Clock": "小组件:时钟", "Audio input": "音频输入", "Polling interval (s)": "轮询间隔(秒)", "Used for decorative/expressive text": "用于装饰性或富有表现力的文字", "Enable translator": "启用翻译", "Pin to taskbar": "固定到任务栏", "Active": "已启用", "Used for headings and titles": "用于标题和副标题", "More volume settings": "更多音量设置", "Minute hand": "分针", "Enable if you want clocks to show seconds accurately": "启用以让时钟精准显示秒数", "Keep awake": "保持唤醒", "Local account": "本地账户", "Save paths": "保存路径", "Open recordings folder": "打开录像文件夹", "Muted": "已静音", "Sliders": "滑块", "CPU": "CPU", "Second precision": "精确显秒", "Border": "内圈", "Reading font": "阅读字体", "Press Super+G to open the overlay and pin the crosshair": "按 Super+G 来打开叠加面板,然后固定准星", "Show": "显示", "More Internet settings": "更多 Internet 设置", "Get the latest features and security improvements with\nthe newest feature update.\n\n%1 packages": "通过安装更新获取最新的功能和\n安全改进。\n\n%1 个软件包", "Quote": "语录", "Widget: Weather": "小组件:天气", "Used for reading large blocks of text": "用于阅读大段文字", "with vertical offset": "使用垂直偏移", "Han chars": "汉字", "e.g. 󱊫 for F1, 󱊶 for F12": "如用 󱊫 来表示 F1,󱊶 来表示 F12 等", "Internet": "网络", "Show notifications": "显示通知", "Force hover open at absolute corner": "强制在绝对角落悬停打开", "Use symbols for function keys": "使用符号表示功能键", "Record": "屏幕录制", "Authentication": "身份验证", "Hint target regions": "建议目标区域", "Enable now": "现在启用", "You'll need to enter your Gemini API key first.\nType /key on the sidebar for instructions.": "你需要先输入 Gemini API 密钥。\n在侧边栏输入 /key 以获取说明。", "Output device": "输出设备", "Swap": "虚拟内存", "Full warning": "满电警告", "Padding": "额外边距", "Expressive font": "表现力字体", "Balance brightness based on content": "根据内容更改亮度", "Cookie": "曲奇", "Fahrenheit unit": "华氏度单位", "Roman": "罗马", "Polling interval (m)": "轮询间隔(分钟)", "Close all windows": "关闭所有窗口", "Adjust the color temperature": "调整色温", "Task View": "任务视图", "More comfortable viewing at night": "夜间浏览更舒适", "No new notifications": "没有新通知", "All": "全部", "Move to front": "移到前面", "Emoji": "表情符号", "Best match": "最佳匹配", "Other": "其他", "Unpin from Start": "从“开始”屏幕取消固定", "Polkit": "Polkit", "Productivity": "效率", "Web": "网页", "Apps": "应用", "Manage accounts": "管理账户", "Commands": "命令", "Do you want to allow this app to make changes to your device?": "你要允许此应用对你的设备进行更改吗?", "Actions": "操作", "Open": "打开", "Pinned": "已固定", "Move right": "右移", "Command": "命令", "Utilities & Tools": "实用工具", "Change password": "更改密码", "Command-line-invoked Action": "由命令行执行的操作", "Unknown Application": "未知应用", "No applications": "没有应用", "Creativity": "创意", "Move left": "左移", "Pin to Start": "固定到“开始”屏幕", "Pin": "固定", "Unpin": "取消固定" } ================================================ FILE: dots/.config/quickshell/ii/welcome.qml ================================================ //@ pragma UseQApplication //@ pragma Env QS_NO_RELOAD_POPUP=1 //@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic //@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000 // Adjust this to make the app smaller or larger //@ pragma Env QT_SCALE_FACTOR=1 import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Window import Quickshell import Quickshell.Io import qs.services import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions ApplicationWindow { id: root property string firstRunFilePath: FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`) property string firstRunFileContent: "This file is just here to confirm you've been greeted :>" property real contentPadding: 8 property bool showNextTime: false visible: true onClosing: { Quickshell.execDetached(["notify-send", Translation.tr("Welcome app"), Translation.tr("Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I"), "-a", "Shell"]); Qt.quit(); } title: Translation.tr("illogical-impulse Welcome") Component.onCompleted: { MaterialThemeLoader.reapplyTheme(); Config.readWriteDelay = 0 // Welcome app always only sets one var at a time so delay isn't needed } minimumWidth: 600 minimumHeight: 400 width: 900 height: 650 color: Appearance.m3colors.m3background Process { id: konachanWallProc property string status: "" command: ["bash", "-c", Quickshell.shellPath("scripts/colors/random/random_konachan_wall.sh")] stdout: SplitParser { onRead: data => { console.log(`Konachan wall proc output: ${data}`); konachanWallProc.status = data.trim(); } } } Process { id: translationProc property string locale: "" command: [Directories.aiTranslationScriptPath, translationProc.locale] } ColumnLayout { anchors { fill: parent margins: contentPadding } Item { // Titlebar visible: Config.options?.windows.showTitlebar Layout.fillWidth: true implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) StyledText { id: welcomeText anchors { left: Config.options.windows.centerTitle ? undefined : parent.left horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined verticalCenter: parent.verticalCenter leftMargin: 12 } color: Appearance.colors.colOnLayer0 text: Translation.tr("Hi there! First things first...") font { family: Appearance.font.family.title pixelSize: Appearance.font.pixelSize.title variableAxes: Appearance.font.variableAxes.title } } RowLayout { // Window controls row id: windowControlsRow anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right StyledText { font.pixelSize: Appearance.font.pixelSize.smaller text: Translation.tr("Show next time") } StyledSwitch { id: showNextTimeSwitch checked: root.showNextTime scale: 0.6 Layout.alignment: Qt.AlignVCenter onCheckedChanged: { if (checked) { Quickshell.execDetached(["rm", root.firstRunFilePath]); } else { Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]); } } } RippleButton { buttonRadius: Appearance.rounding.full implicitWidth: 35 implicitHeight: 35 onClicked: root.close() contentItem: MaterialSymbol { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter text: "close" iconSize: 20 } StyledToolTip { text: Translation.tr("Tip: Close a window with Super+Q") } } } } Rectangle { // Content container color: Appearance.m3colors.m3surfaceContainerLow radius: Appearance.rounding.windowRounding - root.contentPadding implicitHeight: contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth Layout.fillWidth: true Layout.fillHeight: true ContentPage { id: contentColumn anchors.fill: parent ContentSection { Layout.fillWidth: true icon: "language" title: Translation.tr("Language") ContentSubsection { title: Translation.tr("Select language") ConfigSelectionArray { id: languageSelector currentValue: Config.options.language.ui onSelected: newValue => { Config.options.language.ui = newValue; } options: [ { displayName: Translation.tr("Auto (System)"), value: "auto" }, ...Translation.allAvailableLanguages.map(lang => { return { displayName: lang, value: lang }; })] } } NoticeBox { Layout.fillWidth: true text: Translation.tr("Language not listed or incomplete translations?\nYou can choose to generate translations for it with Gemini.\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\n2. Type /key, hit Enter and follow the instructions\n3. Type /key YOUR_API_KEY\n4. Type the locale of your language below and press Generate") } ContentSubsection { title: Translation.tr("Generate translation with Gemini") ConfigRow { MaterialTextArea { id: localeInput Layout.fillWidth: true placeholderText: Translation.tr("Locale code, e.g. fr_FR, de_DE, zh_CN...") text: Config.options.language.ui === "auto" ? Qt.locale().name : Config.options.language.ui } RippleButtonWithIcon { id: generateTranslationBtn Layout.fillHeight: true nerdIcon: "" enabled: !translationProc.running || (translationProc.locale !== localeInput.text.trim()) mainText: enabled ? Translation.tr("Generate\nTypically takes 2 minutes") : Translation.tr("Generating...\nDon't close this window!") onClicked: { translationProc.locale = localeInput.text.trim(); translationProc.running = false; translationProc.running = true; } } } } } ContentSection { icon: "screenshot_monitor" title: Translation.tr("Bar") ConfigRow { ContentSubsection { title: Translation.tr("Bar position") ConfigSelectionArray { currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0) onSelected: newValue => { Config.options.bar.bottom = (newValue & 1) !== 0; Config.options.bar.vertical = (newValue & 2) !== 0; } options: [ { displayName: Translation.tr("Top"), icon: "arrow_upward", value: 0 // bottom: false, vertical: false }, { displayName: Translation.tr("Left"), icon: "arrow_back", value: 2 // bottom: false, vertical: true }, { displayName: Translation.tr("Bottom"), icon: "arrow_downward", value: 1 // bottom: true, vertical: false }, { displayName: Translation.tr("Right"), icon: "arrow_forward", value: 3 // bottom: true, vertical: true } ] } } ContentSubsection { title: Translation.tr("Bar style") ConfigSelectionArray { currentValue: Config.options.bar.cornerStyle onSelected: newValue => { Config.options.bar.cornerStyle = newValue; // Update local copy } options: [ { displayName: Translation.tr("Hug"), icon: "line_curve", value: 0 }, { displayName: Translation.tr("Float"), icon: "page_header", value: 1 }, { displayName: Translation.tr("Rect"), icon: "toolbar", value: 2 } ] } } } } ContentSection { icon: "format_paint" title: Translation.tr("Style & wallpaper") ButtonGroup { Layout.alignment: Qt.AlignHCenter LightDarkPreferenceButton { dark: false } LightDarkPreferenceButton { dark: true } } RowLayout { Layout.alignment: Qt.AlignHCenter RippleButtonWithIcon { id: rndWallBtn visible: Config.options.policies.weeb === 1 Layout.alignment: Qt.AlignHCenter buttonRadius: Appearance.rounding.small materialIcon: "ifl" mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan") onClicked: { console.log(konachanWallProc.command.join(" ")); konachanWallProc.running = true; } StyledToolTip { text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers") } } RippleButtonWithIcon { materialIcon: "wallpaper" StyledToolTip { text: Translation.tr("Pick wallpaper image on your system") } onClicked: { Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]); } mainContentComponent: Component { RowLayout { spacing: 10 StyledText { font.pixelSize: Appearance.font.pixelSize.small text: Translation.tr("Choose file") color: Appearance.colors.colOnSecondaryContainer } RowLayout { spacing: 3 KeyboardKey { key: "Ctrl" } KeyboardKey { key: "󰖳" } StyledText { Layout.alignment: Qt.AlignVCenter text: "+" } KeyboardKey { key: "T" } } } } } } NoticeBox { Layout.fillWidth: true text: Translation.tr("Change any time later with /dark, /light, /wallpaper in the launcher\nIf the shell's colors aren't changing:\n 1. Open the right sidebar with Super+N\n 2. Click \"Reload Hyprland & Quickshell\" in the top-right corner") } } ContentSection { icon: "rule" title: Translation.tr("Policies") ConfigRow { Layout.fillWidth: true ContentSubsection { title: "Weeb" ConfigSelectionArray { currentValue: Config.options.policies.weeb onSelected: newValue => { Config.options.policies.weeb = newValue; } options: [ { displayName: Translation.tr("No"), icon: "close", value: 0 }, { displayName: Translation.tr("Yes"), icon: "check", value: 1 }, { displayName: Translation.tr("Closet"), icon: "ev_shadow", value: 2 } ] } } ContentSubsection { title: "AI" ConfigSelectionArray { currentValue: Config.options.policies.ai onSelected: newValue => { Config.options.policies.ai = newValue; } options: [ { displayName: Translation.tr("No"), icon: "close", value: 0 }, { displayName: Translation.tr("Yes"), icon: "check", value: 1 }, { displayName: Translation.tr("Local only"), icon: "sync_saved_locally", value: 2 } ] } } } } ContentSection { icon: "info" title: Translation.tr("Info") Flow { Layout.fillWidth: true spacing: 5 RippleButtonWithIcon { materialIcon: "keyboard_alt" onClicked: { Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "cheatsheet", "toggle"]); } mainContentComponent: Component { RowLayout { spacing: 10 StyledText { font.pixelSize: Appearance.font.pixelSize.small text: Translation.tr("Keybinds") color: Appearance.colors.colOnSecondaryContainer } RowLayout { spacing: 3 KeyboardKey { key: "󰖳" } StyledText { Layout.alignment: Qt.AlignVCenter text: "+" } KeyboardKey { key: "/" } } } } } RippleButtonWithIcon { materialIcon: "help" mainText: Translation.tr("Usage") onClicked: { Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/"); } } RippleButtonWithIcon { materialIcon: "construction" mainText: Translation.tr("Configuration") onClicked: { Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/"); } } } } ContentSection { icon: "monitoring" title: Translation.tr("Useless buttons") Flow { Layout.fillWidth: true spacing: 5 RippleButtonWithIcon { nerdIcon: "󰊤" mainText: Translation.tr("GitHub") onClicked: { Qt.openUrlExternally("https://github.com/end-4/dots-hyprland"); } } RippleButtonWithIcon { materialIcon: "favorite" mainText: "Funny number" onClicked: { Qt.openUrlExternally("https://github.com/sponsors/end-4"); } } } } Item { Layout.fillWidth: true Layout.fillHeight: true } } } } } ================================================ FILE: dots/.config/starship.toml ================================================ # Don't print a new line at the start of the prompt add_newline = false # Pipes ╰─ ╭─ # Powerline symbols                                    # Wedges 🭧🭒 🭣🭧🭓 # Random noise 🬖🬥🬔🬗 # Cool stuff 󰜥    # format = """ # $directory $fill $git_branch $cmd_duration # $character""" format = """ $cmd_duration $directory$git_branch $character""" [fill] symbol = '-' style = 'fg:245' # Replace the "❯" symbol in the prompt with "➜" [character] # The name of the module we are configuring is "character" success_symbol = "[ ](bold fg:243)" error_symbol = "[ ](bold fg:244)" # Disable the package module, hiding it from the prompt completely [package] disabled = true [git_branch] style = "bg: 252" symbol = "󰘬" truncation_length = 12 truncation_symbol = "" format = " 󰜥 [](bold fg:252)[$symbol $branch(:$remote_branch)](fg:235 bg:252)[ ](bold fg:252)" [git_commit] commit_hash_length = 4 tag_symbol = " " [git_state] format = '[\($state( $progress_current of $progress_total)\)]($style) ' cherry_pick = "[🍒 PICKING](bold red)" [git_status] conflicted = " 🏳 " ahead = " 🏎💨 " behind = " 😰 " diverged = " 😵 " untracked = " 🤷 ‍" stashed = " 📦 " modified = " 📝 " staged = '[++\($count\)](green)' renamed = " ✍️ " deleted = " 🗑 " [hostname] ssh_only = false format = "[•$hostname](bg:252 bold fg:235)[](bold fg:252)" trim_at = ".companyname.com" disabled = false [line_break] disabled = false [memory_usage] disabled = true threshold = -1 symbol = " " style = "bold dimmed green" [time] disabled = true format = '🕙[\[ $time \]]($style) ' time_format = "%T" [username] style_user = "bold bg:252 fg:235" style_root = "red bold" format = "[](bold fg:252)[$user]($style)" disabled = false show_always = true [directory] home_symbol = " " read_only = "  " style = "bg:255 fg:240" truncation_length = 2 truncation_symbol = ".../" format = '[](bold fg:255)[󰉋 → $path]($style)[](bold fg:255)' [directory.substitutions] "Desktop" = "  " "Documents" = "  " "Downloads" = "  " "Music" = " 󰎈 " "Pictures" = "  " "Videos" = "  " "GitHub" = " 󰊤 " [cmd_duration] min_time = 0 format = '[](bold fg:252)[󰪢 $duration](bold bg:252 fg:235)[](bold fg:252)' ================================================ FILE: dots/.config/thorium-flags.conf ================================================ --password-store=gnome-libsecret # --ozone-platform-hint=wayland --gtk-version=4 --ignore-gpu-blocklist --enable-features=TouchpadOverscrollHistoryNavigation ================================================ FILE: dots/.config/wlogout/layout ================================================ { "label" : "lock", "action" : "loginctl lock-session", "text" : "lock", "keybind" : "l" } { "label" : "hibernate", "action" : "systemctl hibernate || loginctl hibernate", "text" : "downloading", "keybind" : "h" } { "label" : "logout", "action" : "hyprctl clients -j | jq -r '.[].pid' | xargs kill; pkill Hyprland || pkill sway || pkill niri || loginctl terminate-user $USER", "text" : "logout", "keybind" : "e" } { "label" : "shutdown", "action" : "hyprctl clients -j | jq -r '.[].pid' | xargs kill; systemctl poweroff || loginctl poweroff", "text" : "power_settings_new", "keybind" : "s" } { "label" : "suspend", "action" : "systemctl suspend || loginctl suspend", "text" : "bedtime", "keybind" : "u" } { "label" : "reboot", "action" : "hyprctl clients -j | jq -r '.[].pid' | xargs kill; systemctl reboot || loginctl reboot", "text" : "restart_alt", "keybind" : "r" } ================================================ FILE: dots/.config/wlogout/style.css ================================================ * { all: unset; background-image: none; transition: 400ms cubic-bezier(0.05, 0.7, 0.1, 1); } window { background: rgba(0, 0, 0, 0.5); } button { font-family: 'Material Symbols Outlined'; font-size: 10rem; background-color: rgba(11, 11, 11, 0.4); color: #FFFFFF; margin: 2rem; border-radius: 2rem; padding: 3rem; } button:focus, button:active, button:hover { background-color: rgba(51, 51, 51, 0.5); border-radius: 4rem; } ================================================ FILE: dots/.config/xdg-desktop-portal/hyprland-portals.conf ================================================ [preferred] default = hyprland;gtk org.freedesktop.impl.portal.FileChooser = kde ================================================ FILE: dots/.config/zshrc.d/auto-Hypr.sh ================================================ # Auto start Hyprland on tty1 if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" -eq 1 ]; then mkdir -p ~/.cache exec start-hyprland > ~/.cache/hyprland.log 2>&1 fi ================================================ FILE: dots/.config/zshrc.d/dots-hyprland.zsh ================================================ # Use the generated color scheme if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt; then cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt fi ================================================ FILE: dots/.config/zshrc.d/shortcuts.zsh ================================================ # Created by newuser for 5.9 bindkey '^H' backward-kill-word bindkey '^Z' undo ================================================ FILE: dots/.local/share/konsole/Profile 1.profile ================================================ [Appearance] ColorScheme=MaterialYou [General] Command=/bin/fish Environment=COLORTERM=truecolor Name=Profile 1 Parent=FALLBACK/ [Keyboard] KeyBindings=default ================================================ FILE: dots-extra/emacs/material-theme.el ================================================ ;;; material-theme.el --- Theme using Matugen SCSS variables ;; Copyright (C) 2025 ;; Author: Generated (Improved) ;; Version: 1.2 ;; Package-Requires: ((emacs "24.1")) ;; Keywords: faces ;;; Commentary: ;; A theme using Matugen SCSS variables with quality of life improvements: ;; - Better source block distinction ;; - Improved text visibility when selected ;; - Refined org-mode styling with hidden asterisks ;; - Enhanced contrast and readability ;; - Seamless integration of source blocks with consistent styling ;;; Code: (deftheme material "Theme using Matugen SCSS variables with quality of life improvements.") ;; Define function to read SCSS variables (defun material-get-color-from-scss (var-name) "Extract color value for VAR-NAME from material_colors.scss file." (let* ((scss-file (expand-file-name "~/.local/state/quickshell/user/generated/material_colors.scss")) (scss-content (with-temp-buffer (insert-file-contents scss-file) (buffer-string))) (var-pattern (concat "\\$" var-name ":\\s-+\\(#[0-9a-fA-F]\\{6\\}\\);")) (match (string-match var-pattern scss-content))) (if match (match-string 1 scss-content) (message "Could not find color variable: %s" var-name) "#000000"))) ;; Define function to adjust color brightness (defun material-adjust-color (hex-color factor) "Adjust HEX-COLOR brightness by FACTOR (0-2). Values < 1 darken, values > 1 lighten." (let* ((r (string-to-number (substring hex-color 1 3) 16)) (g (string-to-number (substring hex-color 3 5) 16)) (b (string-to-number (substring hex-color 5 7) 16)) (adjust-channel (lambda (channel) (max 0 (min 255 (round (* channel factor)))))) (new-r (funcall adjust-channel r)) (new-g (funcall adjust-channel g)) (new-b (funcall adjust-channel b))) (format "#%02x%02x%02x" new-r new-g new-b))) ;; Define all the color variables (let* ((bg (material-get-color-from-scss "background")) (err (material-get-color-from-scss "error")) (err-container (material-get-color-from-scss "errorContainer")) (inverse-on-surface (material-get-color-from-scss "inverseOnSurface")) (inverse-primary (material-get-color-from-scss "inversePrimary")) (inverse-surface (material-get-color-from-scss "inverseSurface")) (on-background (material-get-color-from-scss "onBackground")) (on-err (material-get-color-from-scss "onError")) (on-err-container (material-get-color-from-scss "onErrorContainer")) (on-primary (material-get-color-from-scss "onPrimary")) (on-primary-container (material-get-color-from-scss "onPrimaryContainer")) (on-primary-fixed (material-get-color-from-scss "onPrimaryFixed")) (on-primary-fixed-variant (material-get-color-from-scss "onPrimaryFixedVariant")) (on-secondary (material-get-color-from-scss "onSecondary")) (on-secondary-container (material-get-color-from-scss "onSecondaryContainer")) (on-secondary-fixed (material-get-color-from-scss "onSecondaryFixed")) (on-secondary-fixed-variant (material-get-color-from-scss "onSecondaryFixedVariant")) (on-surface (material-get-color-from-scss "onSurface")) (on-surface-variant (material-get-color-from-scss "onSurfaceVariant")) (on-tertiary (material-get-color-from-scss "onTertiary")) (on-tertiary-container (material-get-color-from-scss "onTertiaryContainer")) (on-tertiary-fixed (material-get-color-from-scss "onTertiaryFixed")) (on-tertiary-fixed-variant (material-get-color-from-scss "onTertiaryFixedVariant")) (outline-color (material-get-color-from-scss "outline")) (outline-variant (material-get-color-from-scss "outlineVariant")) (primary (material-get-color-from-scss "primary")) (primary-container (material-get-color-from-scss "primaryContainer")) (primary-fixed (material-get-color-from-scss "primaryFixed")) (primary-fixed-dim (material-get-color-from-scss "primaryFixedDim")) (scrim (material-get-color-from-scss "scrim")) (secondary (material-get-color-from-scss "secondary")) (secondary-container (material-get-color-from-scss "secondaryContainer")) (secondary-fixed (material-get-color-from-scss "secondaryFixed")) (secondary-fixed-dim (material-get-color-from-scss "secondaryFixedDim")) (shadow (material-get-color-from-scss "shadow")) (surface (material-get-color-from-scss "surface")) (surface-bright (material-get-color-from-scss "surfaceBright")) (surface-container (material-get-color-from-scss "surfaceContainer")) (surface-container-high (material-get-color-from-scss "surfaceContainerHigh")) (surface-container-highest (material-get-color-from-scss "surfaceContainerHighest")) (surface-container-low (material-get-color-from-scss "surfaceContainerLow")) (surface-container-lowest (material-get-color-from-scss "surfaceContainerLowest")) (surface-dim (material-get-color-from-scss "surfaceDim")) (surface-tint (material-get-color-from-scss "surfaceTint")) (surface-variant (material-get-color-from-scss "surfaceVariant")) (tertiary (material-get-color-from-scss "tertiary")) (tertiary-container (material-get-color-from-scss "tertiaryContainer")) (tertiary-fixed (material-get-color-from-scss "tertiaryFixed")) (tertiary-fixed-dim (material-get-color-from-scss "tertiaryFixedDim")) (success (material-get-color-from-scss "success")) (on-success (material-get-color-from-scss "onSuccess")) (success-container (material-get-color-from-scss "successContainer")) (on-success-container (material-get-color-from-scss "onSuccessContainer")) (term0 (material-get-color-from-scss "term0")) (term1 (material-get-color-from-scss "term1")) (term2 (material-get-color-from-scss "term2")) (term3 (material-get-color-from-scss "term3")) (term4 (material-get-color-from-scss "term4")) (term5 (material-get-color-from-scss "term5")) (term6 (material-get-color-from-scss "term6")) (term7 (material-get-color-from-scss "term7")) (term8 (material-get-color-from-scss "term8")) (term9 (material-get-color-from-scss "term9")) (term10 (material-get-color-from-scss "term10")) (term11 (material-get-color-from-scss "term11")) (term12 (material-get-color-from-scss "term12")) (term13 (material-get-color-from-scss "term13")) (term14 (material-get-color-from-scss "term14")) (term15 (material-get-color-from-scss "term15"))) (custom-theme-set-faces 'material ;; Basic faces `(default ((t (:background ,bg :foreground ,on-background)))) `(cursor ((t (:background ,primary)))) `(highlight ((t (:background ,primary-container :foreground ,on-primary-container)))) `(region ((t (:background ,primary-container :foreground ,on-primary-container :extend t)))) `(secondary-selection ((t (:background ,secondary-container :foreground ,on-secondary-container :extend t)))) `(isearch ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold)))) `(lazy-highlight ((t (:background ,secondary-container :foreground ,on-secondary-container)))) `(vertical-border ((t (:foreground ,surface-variant)))) `(border ((t (:background ,surface-variant :foreground ,surface-variant)))) `(fringe ((t (:background ,surface :foreground ,outline-variant)))) `(shadow ((t (:foreground ,outline-variant)))) `(link ((t (:foreground ,primary :underline t)))) `(link-visited ((t (:foreground ,tertiary :underline t)))) `(success ((t (:foreground ,success)))) `(warning ((t (:foreground ,secondary)))) `(error ((t (:foreground ,err)))) `(match ((t (:background ,secondary-container :foreground ,on-secondary-container)))) ;; Font-lock `(font-lock-builtin-face ((t (:foreground ,primary)))) `(font-lock-comment-face ((t (:foreground ,outline-color :slant italic)))) `(font-lock-comment-delimiter-face ((t (:foreground ,outline-variant)))) `(font-lock-constant-face ((t (:foreground ,tertiary :weight bold)))) `(font-lock-doc-face ((t (:foreground ,on-surface-variant :slant italic)))) `(font-lock-function-name-face ((t (:foreground ,primary :weight bold)))) `(font-lock-keyword-face ((t (:foreground ,secondary :weight bold)))) `(font-lock-string-face ((t (:foreground ,tertiary)))) `(font-lock-type-face ((t (:foreground ,primary-fixed)))) `(font-lock-variable-name-face ((t (:foreground ,on-surface)))) `(font-lock-warning-face ((t (:foreground ,err :weight bold)))) `(font-lock-preprocessor-face ((t (:foreground ,secondary-fixed-dim)))) `(font-lock-negation-char-face ((t (:foreground ,tertiary-fixed)))) ;; Show paren `(show-paren-match ((t (:background ,primary-container :foreground ,on-primary-container :weight bold)))) `(show-paren-mismatch ((t (:background ,err-container :foreground ,on-err-container :weight bold)))) ;; Mode line - improved status bar styling `(mode-line ((t (:background ,surface-container :foreground ,on-surface :box nil)))) `(mode-line-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil)))) `(mode-line-buffer-id ((t (:foreground ,primary :weight bold)))) `(mode-line-emphasis ((t (:foreground ,primary :weight bold)))) `(mode-line-highlight ((t (:foreground ,primary :box nil)))) ;; Improved Source blocks - make them integrated with the theme `(org-block ((t (:background ,surface-container-low :extend t :inherit fixed-pitch)))) `(org-block-begin-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch)))) `(org-block-end-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch)))) `(org-code ((t (:background ,surface-container-low :foreground ,tertiary-fixed :inherit fixed-pitch)))) `(org-verbatim ((t (:background ,surface-container-low :foreground ,primary-fixed :inherit fixed-pitch)))) `(org-meta-line ((t (:foreground ,outline-color :slant italic)))) ;; Org mode with hidden asterisks `(org-level-1 ((t (:foreground ,primary :weight bold :height 1.2)))) `(org-level-2 ((t (:foreground ,primary-container :weight bold :height 1.1)))) `(org-level-3 ((t (:foreground ,secondary :weight bold)))) `(org-level-4 ((t (:foreground ,secondary-container :weight bold)))) `(org-level-5 ((t (:foreground ,tertiary :weight bold)))) `(org-level-6 ((t (:foreground ,tertiary-container :weight bold)))) `(org-level-7 ((t (:foreground ,primary-fixed :weight bold)))) `(org-level-8 ((t (:foreground ,primary-fixed-dim :weight bold)))) `(org-document-title ((t (:foreground ,primary :weight bold :height 1.3)))) `(org-document-info ((t (:foreground ,primary-container)))) `(org-todo ((t (:foreground ,err :weight bold)))) `(org-done ((t (:foreground ,success :weight bold)))) `(org-headline-done ((t (:foreground ,on-surface-variant)))) `(org-hide ((t (:foreground ,bg)))) ;; Hide leading asterisks `(org-ellipsis ((t (:foreground ,tertiary :underline nil)))) ;; Style for folded content indicator `(org-table ((t (:foreground ,secondary-fixed :inherit fixed-pitch)))) `(org-formula ((t (:foreground ,tertiary :inherit fixed-pitch)))) `(org-checkbox ((t (:foreground ,primary :weight bold :inherit fixed-pitch)))) `(org-date ((t (:foreground ,secondary-fixed :underline t)))) `(org-special-keyword ((t (:foreground ,on-surface-variant :slant italic)))) `(org-tag ((t (:foreground ,outline-color :weight normal)))) ;; Magit `(magit-section-highlight ((t (:background ,surface-container-low)))) `(magit-diff-hunk-heading ((t (:background ,surface-container :foreground ,on-surface-variant)))) `(magit-diff-hunk-heading-highlight ((t (:background ,surface-container-high :foreground ,on-surface)))) `(magit-diff-context ((t (:foreground ,on-surface-variant)))) `(magit-diff-context-highlight ((t (:background ,surface-container-low :foreground ,on-surface)))) `(magit-diff-added ((t (:background ,success-container :foreground ,on-success-container)))) `(magit-diff-added-highlight ((t (:background ,success-container :foreground ,on-success-container :weight bold)))) `(magit-diff-removed ((t (:background ,err-container :foreground ,on-err-container)))) `(magit-diff-removed-highlight ((t (:background ,err-container :foreground ,on-err-container :weight bold)))) `(magit-hash ((t (:foreground ,outline-color)))) `(magit-branch-local ((t (:foreground ,tertiary :weight bold)))) `(magit-branch-remote ((t (:foreground ,primary :weight bold)))) ;; Company `(company-tooltip ((t (:background ,surface-container :foreground ,on-surface)))) `(company-tooltip-selection ((t (:background ,primary-container :foreground ,on-primary-container)))) `(company-tooltip-common ((t (:foreground ,primary)))) `(company-tooltip-common-selection ((t (:foreground ,on-primary-container :weight bold)))) `(company-tooltip-annotation ((t (:foreground ,tertiary)))) `(company-scrollbar-fg ((t (:background ,primary)))) `(company-scrollbar-bg ((t (:background ,surface-variant)))) `(company-preview ((t (:foreground ,on-surface-variant :slant italic)))) `(company-preview-common ((t (:foreground ,primary :slant italic)))) ;; Ido `(ido-first-match ((t (:foreground ,primary :weight bold)))) `(ido-only-match ((t (:foreground ,tertiary :weight bold)))) `(ido-subdir ((t (:foreground ,secondary)))) `(ido-indicator ((t (:foreground ,err)))) `(ido-virtual ((t (:foreground ,outline-color)))) ;; Helm `(helm-selection ((t (:background ,primary-container :foreground ,on-primary-container)))) `(helm-match ((t (:foreground ,primary :weight bold)))) `(helm-source-header ((t (:background ,surface-container-high :foreground ,primary :weight bold :height 1.1)))) `(helm-candidate-number ((t (:foreground ,tertiary :weight bold)))) `(helm-ff-directory ((t (:foreground ,primary :weight bold)))) `(helm-ff-file ((t (:foreground ,on-surface)))) `(helm-ff-executable ((t (:foreground ,tertiary)))) ;; Which-key `(which-key-key-face ((t (:foreground ,primary :weight bold)))) `(which-key-separator-face ((t (:foreground ,outline-variant)))) `(which-key-command-description-face ((t (:foreground ,on-surface)))) `(which-key-group-description-face ((t (:foreground ,secondary)))) `(which-key-special-key-face ((t (:foreground ,tertiary :weight bold)))) ;; Line numbers `(line-number ((t (:foreground ,outline-variant :inherit fixed-pitch)))) `(line-number-current-line ((t (:foreground ,primary :weight bold :inherit fixed-pitch)))) ;; Parenthesis matching `(sp-show-pair-match-face ((t (:background ,primary-container :foreground ,on-primary-container)))) `(sp-show-pair-mismatch-face ((t (:background ,err-container :foreground ,on-err-container)))) ;; Rainbow delimiters `(rainbow-delimiters-depth-1-face ((t (:foreground ,primary)))) `(rainbow-delimiters-depth-2-face ((t (:foreground ,secondary)))) `(rainbow-delimiters-depth-3-face ((t (:foreground ,tertiary)))) `(rainbow-delimiters-depth-4-face ((t (:foreground ,primary-fixed)))) `(rainbow-delimiters-depth-5-face ((t (:foreground ,secondary-fixed)))) `(rainbow-delimiters-depth-6-face ((t (:foreground ,tertiary-fixed)))) `(rainbow-delimiters-depth-7-face ((t (:foreground ,primary-fixed-dim)))) `(rainbow-delimiters-depth-8-face ((t (:foreground ,secondary-fixed-dim)))) `(rainbow-delimiters-depth-9-face ((t (:foreground ,tertiary-fixed-dim)))) `(rainbow-delimiters-mismatched-face ((t (:foreground ,err :weight bold)))) `(rainbow-delimiters-unmatched-face ((t (:foreground ,err :weight bold)))) ;; Dired `(dired-directory ((t (:foreground ,primary :weight bold)))) `(dired-ignored ((t (:foreground ,outline-variant)))) `(dired-flagged ((t (:foreground ,err)))) `(dired-marked ((t (:foreground ,tertiary :weight bold)))) `(dired-symlink ((t (:foreground ,secondary :slant italic)))) `(dired-header ((t (:foreground ,primary :weight bold :height 1.1)))) ;; Terminal colors `(term-color-black ((t (:foreground ,term0 :background ,term0)))) `(term-color-red ((t (:foreground ,term1 :background ,term1)))) `(term-color-green ((t (:foreground ,term2 :background ,term2)))) `(term-color-yellow ((t (:foreground ,term3 :background ,term3)))) `(term-color-blue ((t (:foreground ,term4 :background ,term4)))) `(term-color-magenta ((t (:foreground ,term5 :background ,term5)))) `(term-color-cyan ((t (:foreground ,term6 :background ,term6)))) `(term-color-white ((t (:foreground ,term7 :background ,term7)))) ;; EShell `(eshell-prompt ((t (:foreground ,primary :weight bold)))) `(eshell-ls-directory ((t (:foreground ,primary :weight bold)))) `(eshell-ls-symlink ((t (:foreground ,secondary :slant italic)))) `(eshell-ls-executable ((t (:foreground ,tertiary)))) `(eshell-ls-archive ((t (:foreground ,on-tertiary-container)))) `(eshell-ls-backup ((t (:foreground ,outline-variant)))) `(eshell-ls-clutter ((t (:foreground ,err)))) `(eshell-ls-missing ((t (:foreground ,err)))) `(eshell-ls-product ((t (:foreground ,on-surface-variant)))) `(eshell-ls-readonly ((t (:foreground ,on-surface-variant)))) `(eshell-ls-special ((t (:foreground ,secondary-fixed)))) `(eshell-ls-unreadable ((t (:foreground ,outline-variant)))) ;; Improved markdown mode `(markdown-header-face ((t (:foreground ,primary :weight bold)))) `(markdown-header-face-1 ((t (:foreground ,primary :weight bold :height 1.2)))) `(markdown-header-face-2 ((t (:foreground ,primary-container :weight bold :height 1.1)))) `(markdown-header-face-3 ((t (:foreground ,secondary :weight bold)))) `(markdown-header-face-4 ((t (:foreground ,secondary-container :weight bold)))) `(markdown-inline-code-face ((t (:foreground ,tertiary-fixed :background ,surface-container-low :inherit fixed-pitch)))) `(markdown-code-face ((t (:background ,surface-container-low :extend t :inherit fixed-pitch)))) `(markdown-pre-face ((t (:background ,surface-container-low :inherit fixed-pitch)))) `(markdown-table-face ((t (:foreground ,secondary-fixed :inherit fixed-pitch)))) ;; Web mode `(web-mode-html-tag-face ((t (:foreground ,primary)))) `(web-mode-html-tag-bracket-face ((t (:foreground ,on-surface-variant)))) `(web-mode-html-attr-name-face ((t (:foreground ,secondary)))) `(web-mode-html-attr-value-face ((t (:foreground ,tertiary)))) `(web-mode-css-selector-face ((t (:foreground ,primary)))) `(web-mode-css-property-name-face ((t (:foreground ,secondary)))) `(web-mode-css-string-face ((t (:foreground ,tertiary)))) ;; Flycheck `(flycheck-error ((t (:underline (:style wave :color ,err))))) `(flycheck-warning ((t (:underline (:style wave :color ,secondary))))) `(flycheck-info ((t (:underline (:style wave :color ,tertiary))))) `(flycheck-fringe-error ((t (:foreground ,err)))) `(flycheck-fringe-warning ((t (:foreground ,secondary)))) `(flycheck-fringe-info ((t (:foreground ,tertiary)))) ;; Mini-buffer customization `(minibuffer-prompt ((t (:foreground ,primary :weight bold)))) ;; Improved search highlighting `(lsp-face-highlight-textual ((t (:background ,primary-container :foreground ,on-primary-container :weight bold)))) `(lsp-face-highlight-read ((t (:background ,secondary-container :foreground ,on-secondary-container :weight bold)))) `(lsp-face-highlight-write ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold)))) ;; Info and help modes `(info-title-1 ((t (:foreground ,primary :weight bold :height 1.3)))) `(info-title-2 ((t (:foreground ,primary-container :weight bold :height 1.2)))) `(info-title-3 ((t (:foreground ,secondary :weight bold :height 1.1)))) `(info-title-4 ((t (:foreground ,secondary-container :weight bold)))) `(Info-quoted ((t (:foreground ,tertiary)))) `(info-menu-header ((t (:foreground ,primary :weight bold)))) `(info-menu-star ((t (:foreground ,primary)))) `(info-node ((t (:foreground ,tertiary :weight bold)))) ;; Fixed-pitch faces `(fixed-pitch ((t (:family "monospace")))) `(fixed-pitch-serif ((t (:family "monospace serif")))) ;; Variable-pitch face `(variable-pitch ((t (:family "sans serif")))) )) ;; Add org-mode hooks for hiding leading stars (with-eval-after-load 'org (setq org-hide-leading-stars t) (setq org-startup-indented t)) ;;;###autoload (when load-file-name (add-to-list 'custom-theme-load-path (file-name-as-directory (file-name-directory load-file-name)))) (provide-theme 'material) ;;; material-theme.el ends here ================================================ FILE: dots-extra/fcitx5/conf/classicui.conf ================================================ # Vertical Candidate List Vertical Candidate List=False # Use mouse wheel to go to prev or next page WheelForPaging=True # Font Font="Google Sans Flex 11" # Menu Font MenuFont="Google Sans Flex 11" # Tray Font TrayFont="Google Sans Flex 11" # Prefer Text Icon PreferTextIcon=False # Show Layout Name In Icon ShowLayoutNameInIcon=True # Use input method language to display text UseInputMethodLanguageToDisplayText=True # Theme Theme=plasma # Dark Theme DarkTheme=plasma # Follow system light/dark color scheme UseDarkTheme=False # Follow system accent color if it is supported by theme and desktop UseAccentColor=True # Use Per Screen DPI on X11 PerScreenDPI=False # Force font DPI on Wayland ForceWaylandDPI=0 # Enable fractional scale under Wayland EnableFractionalScale=True ================================================ FILE: dots-extra/fedora/hypr/hyprland/execs.conf ================================================ # Bar, wallpaper exec-once = ~/.config/hypr/hyprland/scripts/start_geoclue_agent.sh exec-once = qs -c $qsConfig & # Input method # exec-once = fcitx5 # Core components (authentication, lock screen, notification daemon) exec-once = gnome-keyring-daemon --start --components=secrets exec-once = hypridle exec-once = dbus-update-activation-environment --all exec-once = sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Some fix idk # Audio exec-once = easyeffects --gapplication-service # Clipboard: history # exec-once = wl-paste --watch cliphist store & exec-once = wl-paste --type text --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update' exec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update' # Cursor exec-once = hyprctl setcursor Bibata-Modern-Classic 24 # Fix dock pinned apps not launching properly (https://github.com/end-4/dots-hyprland/issues/2200) # exec-once = sleep 3.5 && hyprctl reload && sleep 0.5 && touch ~/.config/quickshell/ii/shell.qml # For fedora to setup polkit exec-once = /usr/libexec/kf6/polkit-kde-authentication-agent-1 ================================================ FILE: dots-extra/fontsets/ar/fonts.conf ================================================ none sans-serif Noto Sans Arabic ================================================ FILE: dots-extra/swaylock/config ================================================ daemonize ignore-empty-password font=Rubik font-size=23 clock timestr=%R #datestr=%a, %e of %B color=000000dd #fade-in=0.1 #effect-blur=20x2 #effect-greyscale #effect-scale=0.3 indicator indicator-radius=80 indicator-thickness=10 indicator-caps-lock key-hl-color=d4d4d4 caps-lock-key-hl-color=d4d4d4 separator-color=00000000 inside-color=000000ff inside-clear-color=00000099 inside-caps-lock-color=00000099 inside-ver-color=00000099 inside-wrong-color=00000099 ring-color=1c1c1c ring-clear-color=1c1c1c ring-caps-lock-color=1c1c1c ring-ver-color=1c1c1c ring-wrong-color=400000 line-color=00000000 line-clear-color=d4d4d4 line-caps-lock-color=d4d4d4 line-ver-color=d9d8d8FF line-wrong-color=ee2e24FF text-color=c9c9c9 text-clear-color=c9c9c9 text-ver-color=c9c9c9 text-wrong-color=c9c9c9 bs-hl-color=470400 caps-lock-bs-hl-color=470400 # caps-lock-key-hl-color=ffd204FF # caps-lock-bs-hl-color=ee2e24FF # disable-caps-lock-text text-caps-lock-color=d4d4d4 ================================================ FILE: dots-extra/via-nix/README.md ================================================ This folder contains tweakd configs when --via-nix is specified. ================================================ FILE: dots-extra/via-nix/hypridle.conf ================================================ $lock_cmd = swaylock -c 000000 # $lock_cmd = pidof hyprlock || hyprlock $suspend_cmd = systemctl suspend || loginctl suspend general { lock_cmd = $lock_cmd before_sleep_cmd = loginctl lock-session after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus inhibit_sleep = 3 } listener { timeout = 300 # 5mins on-timeout = loginctl lock-session } listener { timeout = 600 # 10mins on-timeout = hyprctl dispatch dpms off on-resume = hyprctl dispatch dpms on } listener { timeout = 900 # 15mins on-timeout = $suspend_cmd } ================================================ FILE: licenses/LGPL-3.0.txt ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: licenses/MIT.txt ================================================ MIT License Copyright Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: licenses/README.md ================================================ # Licenses This repository contains code from other repositories. Files containing such code should include a license notice, and a copy should be stored in this folder. ================================================ FILE: sdata/README.md ================================================ This folder mainly contains data for the script `setup`. - TODO: output the logs to a temp file, then show the path of the file so users will be able to read it again or upload it to issue. - TODO: unify the message output via functions (for example `log_error`, `log_warning`). ================================================ FILE: sdata/deps-info.md ================================================ This file contains information about the dependencies. It mainly describes about `sdata/dist-arch` which is actively maintained by the devs. Tips: - The packages which name has prefix `illogical-impulse-` are defined with local files `PKGBUILD`. There're two types: - **Meta packages**, which do not have actual content but only include other packages specified in the array `depends`. - **Actual packages**, which not only install dependencies listed in `depends`, but also build packages which have actual content to be installed later. - For each package included in the local `PKGBUILD`s which name does **not** have prefix `illogical-impulse-`, for example `rsync`, it's either from [Arch Linux Packages](https://archlinux.org/packages) or the [AUR](https://aur.archlinux.org/packages). Search the package name on them to get the info (e.g. what executable(s) the package provides). # Meta packages ## illogical-impulse-audio - `cava` - Used in Quickshell config. - `pavucontrol-qt` - Used in Hyprland and Quickshell config. - `wireplumber` - Not explicitly used. - `pipewire-pulse` - not explicitly used. - `libdbusmenu-gtk3` - not explicitly used. - `playerctl` - Used in Hyprland and Quickshell config. ## illogical-impulse-backlight - `geoclue` - Which demo agent used in Quickshell config. - `brightnessctl` - Used in Hyprland and Quickshell config. - `ddcutil` - Used in Quickshell config. ## illogical-impulse-basic - `bc` - Used in `quickshell/ii/scripts/colors/switchwall.sh` for example. - `coreutils` - Too many executables involved, not sure where been used. - `cliphist` - Used in Hyprland and Quickshell config. - `cmake` - Used in building quickshell and MicroTeX. - `curl` - Used in Quickshell config. - `wget` - Used in Quickshell config. - `ripgrep` - Not sure where been used. - `jq` - Widely used. - `xdg-user-dirs` - Used in Hyprland and Quickshell config. - `rsync` - Used in install script. - `go-yq` - Used in install script. ## illogical-impulse-fonts-themes - `adw-gtk-theme-git` - [source](https://github.com/lassekongo83/adw-gtk3) - Used in Quickshell config. - `breeze` - Used in kdeglobals config. - `breeze-plus` - [source](https://github.com/mjkim0727/breeze-plus) - Used in kde-material-you-colors config. - `darkly-bin` - `darkly` is supposed to be set as the theme for Qt apps, just have not figured out how to properly set it yet. - `eza` - Used in Fish config: `alias ls 'eza --icons'` - `fish` - Widely used. - `fontconfig` - Basic component which is nearly a must. - `kitty` - Used in fuzzel, Hyprland, kdeglobals and Quickshell config; kitty config is also included as dots. - `matugen-bin` - Used in Quickshell. - `otf-space-grotesk` - [source](https://events.ccc.de/congress/2024/infos/styleguide.html) - Used in Quickshell and matugen config. - `starship` - Used in Fish config. - `ttf-jetbrains-mono-nerd` - Font name: `JetBrains Mono NF`, `JetBrainsMono Nerd Font`. - Used in foot, kdeglobals, kitty, qt5ct, qt6ct and Quickshell config. - `ttf-material-symbols-variable-git` - Font name: `Material Symbols Rounded`, `Material Symbols Outlined` - Used in Hyprland, matugen, Quickshell and wlogout config. - `ttf-readex-pro` - Font name: `Readex Pro` - Used in Quickshell config. - `ttf-rubik-vf` - Font name: `Rubik`, `Rubik Light` - Used in Hyprland, kdeglobals, matugen, qt5ct, qt6ct and Quickshell config. - `ttf-twemoji` - Not explicitly used, but it may help as fallback for displaying emoji characters. ## illogical-impulse-hyprland - `hyprland` - Surely needed. - `hyprsunset` - Used in Quickshell config. - `wl-clipboard` - Surely needed. ## illogical-impulse-kde - `bluedevil` - Provide command `kcmshell6 kcm_bluetooth` used by Quickshell bluetooth functionality. - `gnome-keyring` - Provide executable `gnome-keyring-daemon`, used in Hyprland and Quickshell config. - `networkmanager` - Basic component. - `plasma-nm` - Provide command `kcmshell6 kcm_networkmanagement` used by Quickshell network functionality. - `polkit-kde-agent` - Basic component. - `dolphin` - Used in Hyprland and Quickshell config. - `systemsettings` - Used in Hyprland `keybinds.conf`. ## illogical-impulse-portal - `xdg-desktop-portal` - Basic component. - `xdg-desktop-portal-kde` - Basic component. - `xdg-desktop-portal-gtk` - Basic component. - `xdg-desktop-portal-hyprland` - Basic component. ## illogical-impulse-python - `clang` - Some python package may need this to be built, e.g. #1235. This may varies on different distros though. - `uv` - Used for python venv. - `gtk4` - Not explicitly used. - `libadwaita` - Not explicitly used. - `libsoup3` - Not explicitly used. - `libportal-gtk4` - Not explicitly used. - `gobject-introspection` - Not explicitly used. ## illogical-impulse-screencapture - `hyprshot` - Used in Hyprland `keybinds.conf` as fallback. - `slurp` - Used in Hyprland and Quickshell config. - `swappy` - Used in Quickshell config. - `tesseract` - Used in Quickshell and Hyprland config. - `tesseract-data-eng` - Used as data for tesseract. - `wf-recorder` - Used in Quickshell config. ## illogical-impulse-toolkit - `upower` - Used in Quickshell config. - `wtype` - Used in Hyprland `scripts/fuzzel-emoji.sh` - `ydotool` - Used in Quickshell config. ## illogical-impulse-widgets - `fuzzel` - Used in Hyprland and Quickshell config; its config is also included. - `glib2` - Provides executable `gsettings` - Used in install script, also in matugen and quickshell config. - `imagemagick` - Provides executable: `magick` - Used in Quickshell config. - `hypridle` - Used for loginctl to lock session. - `hyprlock` - Installed as fallback; its config is also included. - `hyprpicker` - Used in Hyprland and Quickshell config. - `songrec` - Used in Quickshell config. - `translate-shell` - Used in Quickshell config. - `wlogout` - Used in Hyprland config. - `libqalculate` - Used in Quickshell config, providing math ability in searchbar. - Note that `qalc` is the needed executable. In Arch Linux [libqalculate](https://archlinux.org/packages/extra/x86_64/libqalculate) provides it, but in Fedora [qalculate](https://packages.fedoraproject.org/pkgs/libqalculate/qalculate/fedora-43.html#files) does and [libqalculate](https://packages.fedoraproject.org/pkgs/libqalculate/libqalculate/fedora-43.html#files) does not. # Actual packages ## illogical-impulse-quickshell-git - Pinned commit. - Also with extra dependencies (mainly Qt things) needed by the illogical-impulse Quickshell config. Extra dependencies. - `qt6-base` - `qt6-declarative` - `qt6-5compat` - `qt6-avif-image-plugin` - `qt6-imageformats` - `qt6-multimedia` - `qt6-positioning` - `qt6-quicktimeline` - `qt6-sensors` - `qt6-svg` - `qt6-tools` - `qt6-translations` - `qt6-virtualkeyboard` - `qt6-wayland` - `kirigami` - `kdialog` - `syntax-highlighting` - `vulkan-headers` - `libdrm` - `cpptrace` - `jemalloc` - `mesa` ## illogical-impulse-bibata-modern-classic-bin - [source](https://github.com/ful1e5/Bibata_Cursor) - Used in Hyprland config, not necessary. ## illogical-impulse-microtex-git - [source](https://github.com/NanoMichael/MicroTeX) - This package will be installed as `/opt/MicroTeX`. ================================================ FILE: sdata/dist-arch/.gitignore ================================================ /*/*.tar.* /*/pkg/ /*/src/ ================================================ FILE: sdata/dist-arch/README.md ================================================ # Install scripts for Arch Linux - See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/) ## Old Dependency Installation Method The old deps install method mainly involved `./sdata/dependencies.conf` (which has been removed now). ## Current Dependency Installation Local PKGBUILDs under `./sdata/dist-arch/` are used to install dependencies. The mechanism is introduced by [Makrennel](https://github.com/Makrennel) in [PR#570](https://github.com/end-4/dots-hyprland/pull/570). Why is this awesome? - It makes it possible to control version since some packages may involve breaking changes from time to time. - It makes the dependency trackable for package manager, so that you always know why you have installed some package. - As a result, it enables a workable uninstall process. The PKGBUILDs contains two forms of dependencies: - Package name written in dependencies, like a "meta" package. - Normal PKGBUILD content to build dependencies, e.g. AGS, which is often for version controlling. ## Note - `pkgver()` should be removed from `PKGBUILD` cuz it will modify the `PKGBUILD` which is tracked by Git and should not be modified during building. ================================================ FILE: sdata/dist-arch/illogical-impulse-audio/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-audio pkgver=1.0 pkgrel=3 pkgdesc='Illogical Impulse Audio Dependencies' arch=(any) license=(None) depends=( cava pavucontrol-qt wireplumber pipewire-pulse libdbusmenu-gtk3 playerctl ) ================================================ FILE: sdata/dist-arch/illogical-impulse-backlight/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-backlight pkgver=1.0 pkgrel=2 pkgdesc='Illogical Impulse Backlight Dependencies' arch=(any) license=(None) depends=( geoclue brightnessctl ddcutil ) ================================================ FILE: sdata/dist-arch/illogical-impulse-basic/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-basic pkgver=1.0 pkgrel=3 pkgdesc='Illogical Impulse Basic Dependencies' arch=(any) license=(None) depends=( bc coreutils cliphist cmake curl wget ripgrep jq xdg-user-dirs # Used in install script rsync go-yq # https://github.com/mikefarah/yq ) ================================================ FILE: sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-bibata-modern-classic-bin pkgver=2.0.6 pkgrel=2 pkgdesc="Material Based Cursor Theme, installed for illogical-impulse dotfiles" arch=('any') url="https://github.com/ful1e5/Bibata_Cursor" license=('GPL-3.0-or-later') conflicts=("bibata-cursor-theme" "bibata-cursor-theme-bin") options=('!strip') _variant=Bibata-Modern-Classic source=("${pkgname%-bin}-$pkgver.tar.xz::$url/releases/download/v$pkgver/$_variant.tar.xz") sha256sums=('SKIP') package() { install -dm755 "$pkgdir/usr/share/icons" cp -dr --no-preserve=mode $_variant "$pkgdir/usr/share/icons" } ================================================ FILE: sdata/dist-arch/illogical-impulse-fonts-themes/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-fonts-themes pkgver=1.0 pkgrel=6 pkgdesc='Illogical Impulse Fonts and Theming Dependencies' arch=(any) license=(None) depends=( adw-gtk-theme-git breeze breeze-plus darkly-bin eza fish fontconfig kitty matugen otf-space-grotesk starship ttf-jetbrains-mono-nerd ttf-material-symbols-variable-git ttf-readex-pro ttf-rubik-vf ttf-twemoji ) ================================================ FILE: sdata/dist-arch/illogical-impulse-hyprland/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-hyprland pkgver=1.0 pkgrel=5 pkgdesc='Illogical Impulse Hyprland relatated packages' arch=(any) license=(None) depends=( hyprland hyprsunset wl-clipboard ) ================================================ FILE: sdata/dist-arch/illogical-impulse-kde/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-kde pkgver=1.0 pkgrel=3 pkgdesc='Illogical Impulse KDE Dependencies' arch=(any) license=(None) depends=( bluedevil gnome-keyring networkmanager plasma-nm polkit-kde-agent dolphin systemsettings ) ================================================ FILE: sdata/dist-arch/illogical-impulse-microtex-git/.gitignore ================================================ /MicroTeX/ ================================================ FILE: sdata/dist-arch/illogical-impulse-microtex-git/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-microtex-git _pkgname=MicroTeX pkgver=r494.0e3707f pkgrel=3 pkgdesc='MicroTeX for illogical-impulse dotfiles.' #pkgdesc="A dynamic, cross-platform, and embeddable LaTeX rendering library" arch=("x86_64") url="https://github.com/NanoMichael/${_pkgname}" license=('MIT') depends=( tinyxml2 gtkmm3 gtksourceviewmm cairomm ) makedepends=("git" "cmake") source=("git+${url}.git") sha256sums=("SKIP") prepare() { cd $_pkgname sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt sed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt } build() { cd $_pkgname cmake -B build -S . -DCMAKE_BUILD_TYPE=None cmake --build build } package() { cd $_pkgname install -Dm0755 -t "$pkgdir/opt/$_pkgname/" build/LaTeX cp -r build/res "$pkgdir/opt/$_pkgname/" install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE } ================================================ FILE: sdata/dist-arch/illogical-impulse-portal/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-portal pkgver=1.0 pkgrel=3 pkgdesc='Illogical Impulse XDG Desktop Portals' arch=(any) license=(None) depends=( xdg-desktop-portal xdg-desktop-portal-kde xdg-desktop-portal-gtk xdg-desktop-portal-hyprland ) ================================================ FILE: sdata/dist-arch/illogical-impulse-python/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-python pkgver=1.1 pkgrel=5 pkgdesc='Illogical Impulse Python Dependencies' arch=(any) license=(None) depends=( clang uv gtk4 libadwaita libsoup3 libportal-gtk4 gobject-introspection ) ================================================ FILE: sdata/dist-arch/illogical-impulse-quickshell-git/.gitignore ================================================ /quickshell ================================================ FILE: sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD ================================================ _commit='7511545ee20664e3b8b8d3322c0ffe7567c56f7a' # Useful links: # https://git.outfoxxed.me/quickshell/quickshell/commits/branch/master # https://aur.archlinux.org/packages/quickshell-git groups=(illogical-impulse) _prefix='illogical-impulse' _pkgname=quickshell pkgname="$_prefix-$_pkgname-git" pkgver=0.1.0.r1 pkgrel=8 pkgdesc="$_pkgname-git pinned commit and extra deps for $_prefix" arch=(x86_64 aarch64) url='https://git.outfoxxed.me/quickshell/quickshell' options=(!strip) license=('LGPL-3.0-only') depends=( 'cpptrace' 'jemalloc' 'mesa' 'qt6-declarative' 'qt6-base' 'qt6-svg' 'libdrm' 'libpipewire' 'libxcb' 'wayland' # NOTE: Below are custom dependencies of illogical-impulse qt6-5compat qt6-avif-image-plugin qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland kirigami kdialog syntax-highlighting ) makedepends=( 'cli11' 'cmake' 'git' 'ninja' 'qt6-shadertools' 'spirv-tools' 'vulkan-headers' 'wayland' 'wayland-protocols' ) provides=("$_pkgname" "$_pkgname-git") conflicts=("$_pkgname" "$_pkgname-git") _pkgsrc="$_pkgname" source=("$_pkgsrc::git+$url.git#commit=$_commit" quickshell-check.hook) sha256sums=('SKIP' '8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083') build() { cd "$_pkgname" cmake -GNinja -B build \ -DCMAKE_BUILD_TYPE="RelWithDebInfo" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DDISTRIBUTOR="AUR (package: quickshell-git)" \ -DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO \ -DINSTALL_QML_PREFIX=lib/qt6/qml cmake --build build } package() { install -Dm644 "quickshell-check.hook" -t "$pkgdir/usr/share/libalpm/hooks" cd "$_pkgname" DESTDIR="$pkgdir" cmake --install build install -Dm644 "LICENSE" -t "$pkgdir/usr/share/licenses/$_pkgname" } ================================================ FILE: sdata/dist-arch/illogical-impulse-quickshell-git/quickshell-check.hook ================================================ [Trigger] Operation = Upgrade Operation = Install Type = Package Target = qt6-base Target = qt6-wayland [Action] Description = Querying Quickshell compatibility after Qt6 update... When = PostTransaction Exec = /usr/bin/quickshell --private-check-compat ================================================ FILE: sdata/dist-arch/illogical-impulse-screencapture/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-screencapture pkgver=1.0 pkgrel=2 pkgdesc='Illogical Impulse Screenshot and Recording Dependencies' arch=(any) license=(None) depends=( hyprshot slurp swappy tesseract tesseract-data-eng wf-recorder ) ================================================ FILE: sdata/dist-arch/illogical-impulse-toolkit/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-toolkit pkgver=1.0 pkgrel=3 pkgdesc='Illogical Impulse Toolkit Dependencies' arch=(any) license=(None) depends=( upower wtype ydotool ) ================================================ FILE: sdata/dist-arch/illogical-impulse-widgets/PKGBUILD ================================================ groups=(illogical-impulse) pkgname=illogical-impulse-widgets pkgver=1.0 pkgrel=7 pkgdesc='Illogical Impulse Widget Dependencies' arch=(any) license=(None) depends=( fuzzel glib2 imagemagick hypridle hyprlock hyprpicker songrec translate-shell wlogout libqalculate ) ================================================ FILE: sdata/dist-arch/install-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. install-yay(){ x sudo pacman -S --needed --noconfirm base-devel x git clone https://aur.archlinux.org/yay-bin.git /tmp/buildyay x cd /tmp/buildyay x makepkg -o x makepkg -se x makepkg -i --noconfirm x cd ${REPO_ROOT} rm -rf /tmp/buildyay } remove_deprecated_dependencies(){ printf "${STY_CYAN}[$0]: Removing deprecated dependencies:${STY_RST}\n" local list=() list+=(illogical-impulse-{microtex,pymyc-aur,oneui4-icons-git}) list+=(hyprland-qtutils) list+=({quickshell,hyprutils,hyprpicker,hyprlang,hypridle,hyprland-qt-support,hyprland-qtutils,hyprlock,xdg-desktop-portal-hyprland,hyprcursor,hyprwayland-scanner,hyprland}-git) list+=(matugen-bin) for i in ${list[@]};do try sudo pacman --noconfirm -Rdd $i;done } # NOTE: `implicitize_old_dependencies()` was for the old days when we just switch from dependencies.conf to local PKGBUILDs. # However, let's just keep it as references for other distros writing their `sdata/dist-/install-deps.sh`, if they need it. implicitize_old_dependencies(){ # Convert old dependencies to non explicit dependencies so that they can be orphaned if not in meta packages remove_bashcomments_emptylines ./sdata/dist-arch/previous_dependencies.conf ./cache/old_deps_stripped.conf readarray -t old_deps_list < ./cache/old_deps_stripped.conf pacman -Qeq > ./cache/pacman_explicit_packages readarray -t explicitly_installed < ./cache/pacman_explicit_packages echo "Attempting to set previously explicitly installed deps as implicit..." for i in "${explicitly_installed[@]}"; do for j in "${old_deps_list[@]}"; do [ "$i" = "$j" ] && yay -D --asdeps "$i" done; done return 0 } ##################################################################################### if ! command -v pacman >/dev/null 2>&1; then printf "${STY_RED}[$0]: pacman not found, it seems that the system is not ArchLinux or Arch-based distros. Aborting...${STY_RST}\n" exit 1 fi # Keep makepkg from resetting sudo credentials if [[ -z "${PACMAN_AUTH:-}" ]]; then export PACMAN_AUTH="sudo" fi showfun remove_deprecated_dependencies v remove_deprecated_dependencies # Issue #363 case $SKIP_SYSUPDATE in true) sleep 0;; *) v sudo pacman -Syu;; esac # Use yay. Because paru does not support cleanbuild. # Also see https://wiki.hyprland.org/FAQ/#how-do-i-update if ! command -v yay >/dev/null 2>&1;then echo -e "${STY_YELLOW}[$0]: \"yay\" not found.${STY_RST}" showfun install-yay v install-yay fi showfun implicitize_old_dependencies v implicitize_old_dependencies # https://github.com/end-4/dots-hyprland/issues/581 # yay -Bi is kinda hit or miss, instead cd into the relevant directory and manually source and install deps install-local-pkgbuild() { local location=$1 local installflags=$2 x pushd $location source ./PKGBUILD x yay -S --sudoloop $installflags --asdeps "${depends[@]}" # man makepkg: # -A, --ignorearch: Ignore a missing or incomplete arch field in the build script. # -s, --syncdeps: Install missing dependencies using pacman. When build-time or run-time dependencies are not found, pacman will try to resolve them. # -f, --force: build a package even if it already exists in the PKGDEST # -i, --install: Install or upgrade the package after a successful build using pacman(8). # In https://github.com/end-4/dots-hyprland/issues/823#issuecomment-3394774645 it's suggested to use `sudo pacman -U --noconfirm *.pkg.tar.zst` instead of `makepkg -i`, however it's possible that multiple *.pkg.tar.zst exist, which makes this command not reliable. x makepkg -Afsi --noconfirm x popd } # Install core dependencies from the meta-packages metapkgs=(./sdata/dist-arch/illogical-impulse-{audio,backlight,basic,fonts-themes,kde,portal,python,screencapture,toolkit,widgets}) metapkgs+=(./sdata/dist-arch/illogical-impulse-hyprland) metapkgs+=(./sdata/dist-arch/illogical-impulse-microtex-git) metapkgs+=(./sdata/dist-arch/illogical-impulse-quickshell-git) metapkgs+=(./sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin) for i in "${metapkgs[@]}"; do metainstallflags="--needed" $ask && showfun install-local-pkgbuild || metainstallflags="$metainstallflags --noconfirm" v install-local-pkgbuild "$i" "$metainstallflags" done ## Optional dependencies if pacman -Qs ^plasma-browser-integration$ ;then SKIP_PLASMAINTG=true;fi case $SKIP_PLASMAINTG in true) sleep 0;; *) if $ask;then echo -e "${STY_YELLOW}[$0]: NOTE: The size of \"plasma-browser-integration\" is ~600 KiB, but if you don't yet have KDE on your system it'll pull an extra ~600MiB of packages.${STY_RST}" echo -e "${STY_YELLOW}It is needed if you want playtime of media in Firefox to be shown on the music controls widget.${STY_RST}" echo -e "${STY_YELLOW}Install it? [y/N]${STY_RST}" read -p "====> " p else p=y fi case $p in y) x sudo pacman -S --needed --noconfirm plasma-browser-integration ;; *) echo "Ok, won't install" esac ;; esac ================================================ FILE: sdata/dist-arch/outdate-detect-mode ================================================ AUTO ================================================ FILE: sdata/dist-arch/previous_dependencies.conf ================================================ ### This file contains a list of dependencies which were previously explicitly installed that are now installed using meta packages ### Must be one package per line as it needs to be compared against the explicitly installed list from pacman illogical-impulse-ags archlinux-xdg-menu bc coreutils cliphist cmake curl fuzzel rsync wget ripgrep gojq npm typescript gjs xdg-user-dirs tinyxml2 gtkmm3 gtksourceviewmm cairomm python-build python-pillow python-pywal python-setuptools-scm python-wheel xdg-desktop-portal xdg-desktop-portal-gtk xdg-desktop-portal-hyprland-git pavucontrol wireplumber libdbusmenu-gtk3 playerctl swww webp-pixbuf-loader gtk-layer-shell gtk3 gtksourceview3 gobject-introspection upower yad ydotool xdg-user-dirs-gtk polkit-gnome gnome-keyring gnome-control-center blueberry gammastep gnome-bluetooth-3.0 brightnessctl ddcutil dart-sass python-pywayland python-psutil hypridle-git hyprlock-git wlogout wl-clipboard hyprpicker-git adw-gtk3-git qt5ct qt5-wayland fontconfig ttf-readex-pro ttf-jetbrains-mono-nerd ttf-material-symbols-variable-git ttf-space-mono-nerd fish foot starship swappy wf-recorder grim tesseract tesseract-data-eng slurp ================================================ FILE: sdata/dist-arch/uninstall-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. for i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,screencapture,toolkit,widgets} plasma-browser-integration; do v yay -Rns $i done ================================================ FILE: sdata/dist-fedora/.gitignore ================================================ /*/*.tar.* /*/pkg/ /*/src/ user_data.yaml ================================================ FILE: sdata/dist-fedora/README.md ================================================ # Install scripts for Fedora Linux Note: - The scripts here are **not** meant to be executed directly. - This folder should reflect the equivalents of `/sdata/dist-arch/` but under Fedora. - **When `/sdata/dist-arch/` is newer than this folder, an update on this folder is very likely needed.** - Useful link: [Commit history on sdata/dist-arch/](https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-arch) - See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/) ## Contributors - Author: [ririko6z](https://github.com/ririko6z) ## Tested - It has been tested on Fedora Everything 44 on the `x86_64` platform. ## Post installation - Fix the issue of the right column crashing when clicking the `Details` button in Wi-Fi mode. Edit this file: `~/.config/illogical-impulse/config.json` ```diff @@ 44,3 44,3 @@ - "apps": { - "bluetooth": "kcmshell6 kcm_bluetooth", - "network": "kitty -1 fish -c nmtui", + "apps": { + "bluetooth": "kcmshell6 kcm_bluetooth", + "network": "plasmawindowed org.kde.plasma.networkmanagement", ``` ================================================ FILE: sdata/dist-fedora/feddeps.toml ================================================ # COPR repositories list # "solopasha/hyprland" is not up to date to the current Hyprland version, replaced with the fork "sdegler/hyprland" [copr] repos = [ "ririko66z/dots-hyprland", "sdegler/hyprland", "deltacopy/darkly", "alternateved/eza", "atim/starship" ] # Fedora dependencies list # Audio [groups.audio] packages = [ "cava", "pavucontrol", "wireplumber", "libdbusmenu-gtk3-devel", "playerctl" ] # Backlight [groups.backlight] packages = [ "geoclue2", "brightnessctl", "ddcutil" ] install_opts = ["--setopt=install_weak_deps=False"] # Basic [groups.basic] packages = [ "bc", "coreutils", "cliphist", "cmake", "curl", "wget2", "ripgrep", "jq", "xdg-utils", "rsync", "yq" ] # Cursor themes [groups.cursor_themes] packages = [ "bibata-cursor-theme" ] # Fonts & Themes [groups.fonts_and_themes] packages = [ "adw-gtk3-theme", "breeze-cursor-theme", "grub2-breeze-theme", "breeze-icon-theme", "breeze-icon-theme-fedora", "kf6-breeze-icons", "sddm-breeze", "breeze-plus-icon-theme", "darkly", "eza", "fish", "fontconfig", "kitty", "florian-karsten-space-grotesk-fonts", "starship", "jetbrains-mono-nerd-fonts", "google-material-symbols-vf-rounded-fonts", "material-icons-fonts", "readex-pro-fonts-all", "google-rubik-vf-fonts", "twitter-twemoji-fonts", "google-sans-flex-vf-fonts" ] # Hyprland [groups.hyprland] packages = [ "hyprland", "hyprland-guiutils", "hyprland-qt-support", "hyprsunset", "wl-clipboard" ] install_opts = ["--setopt=install_weak_deps=False"] # KDE [groups.kde] packages = [ "bluedevil", "gnome-keyring", "NetworkManager", "plasma-nm", "polkit-kde", "dolphin", "plasma-systemsettings" ] # MicroTeX-git [groups.microtex] packages = [ "microtex" ] # Portal [groups.portal] packages = [ "xdg-desktop-portal", "xdg-desktop-portal-gtk", "xdg-desktop-portal-kde", "xdg-desktop-portal-hyprland" ] # Python [groups.python] packages = [ "clang", "uv", "gtk4-devel", "libadwaita-devel", "libsoup3-devel", "libportal-gtk4", "gobject-introspection-devel", "python3", "python3.12", "python3-devel", "python3.12-devel" ] install_opts = ["--setopt=install_weak_deps=False"] [groups.illogical-impulse] packages = [ "quickshell-git", "matugen" ] # Screencapture [groups.screencapture] packages = [ "hyprshot", "slurp", "swappy", "tesseract", "tesseract-langpack-eng", "tesseract-langpack-chi_sim", "wf-recorder" ] # Toolkit [groups.toolkit] packages = [ "upower", "wtype", "ydotool" ] # Widgets [groups.widgets] packages = [ "fuzzel", "glib2", "ImageMagick", "hypridle", "hyprlock", "hyprpicker", "songrec", "translate-shell", "qalculate", "wlogout" ] # Extra [groups.extra] packages = [ "mpvpaper", "plasma-systemmonitor", "unzip" ] install_opts = ["--setopt=install_weak_deps=False"] ================================================ FILE: sdata/dist-fedora/install-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # ------------------------- # CONFIG # ------------------------- user_config="${REPO_ROOT}/sdata/dist-fedora/user_data.yaml" rpmbuildroot="${REPO_ROOT}/cache/rpmbuild" rpm_specs="${REPO_ROOT}/sdata/dist-fedora/SPECS" deps_data_file="${REPO_ROOT}/sdata/dist-fedora/feddeps.toml" # ------------------------- # FUNCTIONS # ------------------------- # Recording DNF Transaction ID function r() { original_id=$(dnf history info | grep -Po '^Transaction ID\s+:\s+\K\d+') "$@" || { echo -e "${STY_RED}[$0]: Stack Exception...${STY_RST}" } last_id=$(dnf history info | grep -Po '^Transaction ID\s+:\s+\K\d+') [ -f "$user_config" ] || { touch "$user_config" && yq -i ".dnf.original_transaction_id = $original_id" "$user_config"; } || : [ "$original_id" == "$last_id" ] || yq -i ".dnf.transaction_ids += [ $last_id ]" "$user_config" || : } # Init local RPM repo and download rpms from releases there. function init_local_repo() { url="https://api.github.com/repos/end-4/ii-package-builds/releases/tags/packages-fedora" path="$HOME/.cache/illogical-impulse-repo" rm -rf -- "$path" mkdir -p "$path" for file in $(curl -s "$url" | jq -r '.assets[].browser_download_url'); do name=$(basename "$file") echo "Downloading $file" curl --max-time 10 -L --fail --show-error --progress-bar -o "$path/$name" "$file" createrepo_c "$path" done } # ------------------------- # MAIN # ------------------------- if ! command -v dnf >/dev/null 2>&1; then printf "${STY_RED}[$0]: dnf not found, it seems that the system is not Fedora 42 or later distros. Aborting...${STY_RST}\n" exit 1 fi # Update System case $SKIP_SYSUPDATE in true) sleep 0 ;; *) v sudo dnf upgrade --refresh -y ;; esac # Remove version lock v sudo dnf versionlock delete quickshell-git 2>/dev/null # Install yq for parsing config files v sudo dnf install yq -y # Install development tools r v sudo dnf install createrepo_c -y # Install COPR repositories copr_repos_json=$(yq -o=j '.copr.repos // []' "$deps_data_file") eval "$(jq -r '@sh "copr_repos_array+=(\(.[]))"' <<<"$copr_repos_json")" # Fedora distro contains jq for copr in ${copr_repos_array[@]}; do v sudo dnf copr enable "$copr" -y done # Init local repo with prebuilt rpms showfun init_local_repo v init_local_repo # Install packages from toml file deps_data=$(yq -o=j '.' "$deps_data_file") echo "Starting to install packages from $deps_data_file ..." while IFS= read -r deps_list_key; do echo "Installing package list: $deps_list_key" install_opts=$(echo $deps_data | yq ".groups.\"$deps_list_key\" | select(has(\"install_opts\")) | .install_opts[]") package_list=$(echo $deps_data | yq ".groups.\"$deps_list_key\".packages | unique | .[]") if [[ $deps_list_key == 'illogical-impulse' ]]; then install_opts="$install_opts --repofrompath=illogical-impulse,file://$HOME/.cache/illogical-impulse-repo --nogpgcheck" fi r v sudo dnf install -y $install_opts $package_list 0)') # Add back versionlock at the end [ -n $nolock_qs ] || v sudo dnf versionlock add quickshell-git || true echo -e "\n========================================" echo "All installations are completed." echo "========================================" ================================================ FILE: sdata/dist-fedora/outdate-detect-mode ================================================ AUTO ================================================ FILE: sdata/dist-fedora/uninstall-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. user_data="${REPO_ROOT}/sdata/dist-fedora/user_data.yaml" yq eval '.dnf.transaction_ids // [] | reverse[]' "$user_data" | while read -r tx_id; do echo -e "\n========================================" echo "Rolling back DNF Transactions ID: $tx_id" echo "========================================" dnf history info "$tx_id" echo -e "\nProceed to undo this transaction? " v sudo dnf history undo -y "$tx_id" /dev/null; or pipewire & pgrep -x pipewire-pulse >/dev/null; or pipewire-pulse & pgrep -x wireplumber >/dev/null; or wireplumber & # Launch Hyprland with DBus session exec Hyprland end ``` ## Known Issues - If Hyprland is just blank, rebuild Quickshell (`emerge -q gui-apps/quickshell`) - `Hyprland: error while loading shared libraries: libhyprgraphics.so.0: cannot open shared object file: No such file or directory` - The Hyprland live ebuild sometimes has linkage issues, deleting _Hyprland_ and _hyprland_ from `/usr/bin/` and then re-emerging usually fixes this. - When emerging Hyprland if you get an issue relating to `undefined reference to ``Hyprutils::Math::Vector2D::˜Vector2D()`` ` - Clear the cache folder (`rm -fr /var/tmp/portage/gui-wm/hyprland*`) then try again ================================================ FILE: sdata/dist-gentoo/additional-useflags ================================================ ############ Additional needed useflags x11-libs/cairo X dev-cpp/cairomm X dev-python/pycairo X media-libs/libglvnd X x11-libs/libxkbcommon X media-libs/libpulse X media-plugins/alsa-plugins pulseaudio kde-frameworks/kcoreaddons dbus kde-frameworks/prison qml kde-frameworks/kguiaddons X dbus wayland kde-frameworks/kidletime X wayland kde-frameworks/kwindowsystem X wayland kde-frameworks/kconfig dbus qml kde-frameworks/sonnet qml x11-base/xwayland libei sys-apps/dbus X sys-libs/zlib minizip dev-qt/qtquick3d opengl media-video/ffmpeg X bzip2 dav1d drm fontconfig gnutls gpl libass opencl opengl openh264 opus postproc svg truetype vaapi vpx vulkan x264 x265 xml zlib media-libs/leptonica jpeg png zlib tiff webp media-libs/libva X wayland app-text/xmlto text app-crypt/gcr gtk media-video/vlc wayland ogg dbus mp3 dev-libs/qcoro dbus qml media-libs/freetype harfbuzz media-libs/mesa wayland x11-libs/gtk+ wayland ================================================ FILE: sdata/dist-gentoo/hyprland-qtutils-private.patch ================================================ --- a/utils/dialog/CMakeLists.txt +++ b/utils/dialog/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient) +find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate) find_package(PkgConfig REQUIRED) pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils) --- a/utils/donate-screen/CMakeLists.txt +++ b/utils/donate-screen/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient) +find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate) find_package(PkgConfig REQUIRED) pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils) --- a/utils/update-screen/CMakeLists.txt +++ b/utils/update-screen/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient) +find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate) find_package(PkgConfig REQUIRED) pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils) ================================================ FILE: sdata/dist-gentoo/illogical-impulse-audio/illogical-impulse-audio-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Audio Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" media-sound/cava media-sound/pavucontrol-qt media-video/wireplumber dev-libs/libdbusmenu[gtk3] media-sound/playerctl " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-backlight/illogical-impulse-backlight-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Backlight Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" app-misc/geoclue app-misc/brightnessctl app-misc/ddcutil " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r2.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Basic Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" sys-devel/bc sys-apps/coreutils app-misc/cliphist dev-build/cmake net-misc/curl net-misc/wget sys-apps/ripgrep dev-python/jq x11-misc/xdg-user-dirs net-misc/rsync app-misc/yq-go " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-bibata-modern-classic-bin/illogical-impulse-bibata-modern-classic-bin-2.0.6-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Material Based Cursor Theme, installed for illogical-impulse dotfiles" HOMEPAGE="" SRC_URI="https://github.com/ful1e5/Bibata_Cursor/releases/download/v${PV}/Bibata-Modern-Classic.tar.xz -> bibata-modern-classic.tar.xz" LICENSE="GPL-3+" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND="" S="${WORKDIR}/Bibata-Modern-Classic" src_install() { insinto /usr/share/icons doins -r "${S}" } ================================================ FILE: sdata/dist-gentoo/illogical-impulse-fonts-themes/illogical-impulse-fonts-themes-1.0-r2.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Fonts and Theming Dependencies" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" x11-themes/adw-gtk3 kde-plasma/breeze kde-plasma/breeze-plus x11-themes/darkly sys-apps/eza app-shells/fish media-libs/fontconfig x11-terms/kitty x11-misc/matugen media-fonts/space-grotesk app-shells/starship media-fonts/jetbrains-mono media-fonts/material-symbols-variable media-fonts/readex-pro media-fonts/rubik-vf media-fonts/twemoji " ##### CUSTOM EBUILDS # x11-themes/adw-gtk3 # x11-themes/darkly # media-fonts/space-grotesk # media-fonts/material-symbols-variable # media-fonts/readex-pro # media-fonts/rubik-vf ================================================ FILE: sdata/dist-gentoo/illogical-impulse-hyprland/illogical-impulse-hyprland-1.0-r3.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Hyprland related packages" HOMEPAGE="" LICENSE="GPL-2" SLOT="0" KEYWORDS="amd64 arm64 x86" RESTRICT="strip" DEPEND="" RDEPEND=" gui-apps/hyprsunset >=gui-wm/hyprland-0.53.3:= gui-apps/wl-clipboard " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-kde/illogical-impulse-kde-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse KDE Dependencies" HOMEPAGE="" LICENSE="GPL-2" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" kde-plasma/bluedevil gnome-base/gnome-keyring net-misc/networkmanager kde-plasma/plasma-nm kde-plasma/polkit-kde-agent kde-apps/dolphin kde-plasma/systemsettings " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-microtex-git/illogical-impulse-microtex-git-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 MICROTEX_VER="0e3707f" DESCRIPTION="MicroTeX for illogical-impulse dotfiles" HOMEPAGE="https://github.com/NanoMichael/MicroTeX" SRC_URI="https://github.com/NanoMichael/MicroTeX/archive/${MICROTEX_VER}.tar.gz -> MicroTeX-${MICROTEX_VER}.tar.gz" LICENSE="MIT" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" dev-libs/tinyxml2 dev-cpp/gtkmm dev-cpp/gtksourceviewmm dev-cpp/cairomm " S="${WORKDIR}" src_unpack() { # If I don't strip it it has an insane hash tar xvf "${DISTDIR}/MicroTeX-${MICROTEX_VER}.tar.gz" --strip-components=1 -C "${WORKDIR}" } src_prepare() { default cd "${S}" || die # Gentoo doesn't have gtksourceviewmm4 even on testing so I just left it on 3 # sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt sed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt } src_compile() { cd "${S}" mkdir -p build cmake -B build -S . -DCMAKE_BUILD_TYPE=None cmake --build build } src_install() { cd "${S}" || die insinto /opt/illogical-impulse-microtex-git doins -r build/LaTeX doins -r build/res insinto /usr/share/licenses/illogical-impulse-microtex-git doins LICENSE } ================================================ FILE: sdata/dist-gentoo/illogical-impulse-portal/illogical-impulse-portal-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse XDG Desktop Portals" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" DEPEND="" RDEPEND=" sys-apps/xdg-desktop-portal kde-plasma/xdg-desktop-portal-kde sys-apps/xdg-desktop-portal-gtk gui-libs/xdg-desktop-portal-hyprland " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-python/illogical-impulse-python-1.1-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Python Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" DEPEND="" RDEPEND=" dev-python/clang dev-python/uv gui-libs/gtk gui-libs/libadwaita net-libs/libsoup dev-libs/libportal dev-libs/gobject-introspection " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-0.1.0-r6.ebuild ================================================ # Copyright 1999-2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 inherit cmake git-r3 DESCRIPTION="Toolkit for building desktop widgets using QtQuick" HOMEPAGE="https://quickshell.org/" EGIT_REPO_URI="https://github.com/quickshell-mirror/quickshell.git" EGIT_COMMIT="7511545ee20664e3b8b8d3322c0ffe7567c56f7a" KEYWORDS="~amd64 ~arm64 ~x86" LICENSE="LGPL-3" SLOT="0" IUSE="-breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab -i3 -i3-ipc +bluetooth" RDEPEND=" dev-qt/qtbase:6= dev-qt/qtdeclarative:6= dev-qt/qt5compat:6= kde-frameworks/kimageformats:6=[avif] dev-cpp/cpptrace[unwind] dev-qt/qtimageformats:6= dev-qt/qtmultimedia:6= dev-qt/qtpositioning:6= dev-qt/qtquicktimeline:6= dev-qt/qtsensors:6= dev-qt/qtsvg:6= dev-qt/qttools:6= dev-qt/qttranslations:6= dev-qt/qtvirtualkeyboard:6= dev-qt/qtwayland:6= kde-apps/kdialog kde-frameworks/syntax-highlighting:6= kde-frameworks/kirigami:6= jemalloc? ( dev-libs/jemalloc:= ) wayland? ( dev-libs/wayland dev-qt/qtwayland:6= ) screencopy? ( x11-libs/libdrm media-libs/mesa ) X? ( x11-libs/libxcb:= ) pipewire? ( media-video/pipewire:= ) mpris? ( dev-qt/qtdbus:= ) pam? ( sys-libs/pam ) bluetooth? ( net-wireless/bluez ) " DEPEND="${RDEPEND}" BDEPEND=" dev-cpp/cli11 dev-build/cmake dev-vcs/git dev-build/ninja dev-qt/qtshadertools dev-util/spirv-tools wayland? ( dev-util/wayland-scanner dev-libs/wayland-protocols ) virtual/pkgconfig breakpad? ( dev-util/breakpad ) dev-util/vulkan-headers " src_configure(){ mycmakeargs=( -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISTRIBUTOR="Gentoo Illogical-Impulses" -DINSTALL_QML_PREFIX="$(get_libdir)/qt6/qml" -DCRASH_REPORTER=$(usex breakpad ON OFF) -DUSE_JEMALLOC=$(usex jemalloc ON OFF) -DSOCKETS=$(usex sockets ON OFF) -DWAYLAND=$(usex wayland ON OFF) -DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF) -DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF) -DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF) -DSCREENCOPY=$(usex screencopy ON OFF) -DX11=$(usex X ON OFF) -DSERVICE_PIPEWIRE=$(usex pipewire ON OFF) -DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF) -DSERVICE_MPRIS=$(usex mpris ON OFF) -DSERVICE_PAM=$(usex pam ON OFF) -DHYPRLAND=$(usex hyprland ON OFF) -DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts) -DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab) -DI3=$(usex i3 ON OFF) -DI3_IPC=$(usex i3-ipc ON OFF) -DBLUETOOTH=$(usex bluetooth ON OFF) ) cmake_src_configure } ================================================ FILE: sdata/dist-gentoo/illogical-impulse-screencapture/illogical-impulse-screencapture-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Screenshot and Recording Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" DEPEND="" RDEPEND=" gui-apps/hyprshot gui-apps/slurp gui-apps/swappy app-text/tesseract gui-apps/wf-recorder " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-toolkit/illogical-impulse-toolkit-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogical Impulse Toolkit Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" DEPEND="" RDEPEND=" sys-power/upower gui-apps/wtype x11-misc/ydotool " ================================================ FILE: sdata/dist-gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r5.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Illogicall Impulse Widget Dependencies" HOMEPAGE="" LICENSE="metapackage" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" DEPEND="" RDEPEND=" gui-apps/fuzzel dev-libs/glib media-gfx/imagemagick gui-apps/hypridle gui-apps/hyprlock gui-apps/hyprpicker media-sound/songrec app-i18n/translate-shell gui-apps/wlogout sci-libs/libqalculate " ##### CUSTOM EBUILDS # app-misc/songrec ================================================ FILE: sdata/dist-gentoo/import-local-pkgs.sh ================================================ HYPR_DIR="local-pkgs/hyprland" FT_DIR="local-pkgs/fonts-and-themes" WIDGETS_DIR="local-pkgs/widgets" function import_ebuild(){ from_dir="$1" to_dir="$2" ename="$3" x sudo rm -rf "${ebuild_dir}/${to_dir}/${ename}" x sudo mkdir -p "${ebuild_dir}/${to_dir}/${ename}" v sudo cp ./sdata/dist-gentoo/${from_dir}/${ename}*.ebuild "${ebuild_dir}/${to_dir}/${ename}" v sudo ebuild "${ebuild_dir}/${to_dir}/${ename}/${ename}"*.ebuild digest } ############### FONTS AND THEMES import_ebuild "${FT_DIR}" "media-fonts" "material-symbols-variable" import_ebuild "${FT_DIR}" "media-fonts" "readex-pro" import_ebuild "${FT_DIR}" "media-fonts" "rubik-vf" import_ebuild "${FT_DIR}" "media-fonts" "space-grotesk" import_ebuild "${FT_DIR}" "kde-plasma" "breeze-plus" import_ebuild "${FT_DIR}" "x11-themes" "darkly" ================================================ FILE: sdata/dist-gentoo/install-deps.sh ================================================ printf "${STY_YELLOW}" printf "============WARNING/NOTE (1)============\n" printf "Ensure you have a global use flag for elogind or systemd in your make.conf for simplicity\n" printf "Or you can manually add the use flags for each package that requires it\n" printf "${STY_RST}" pause printf "${STY_YELLOW}" printf "============WARNING/NOTE (2)============\n" printf "https://github.com/end-4/dots-hyprland/blob/main/sdata/dist-gentoo/README.md\n" printf "Checkout the above README for potential bug fixes or additional information\n\n" printf "${STY_RST}" pause x sudo emerge --update --quiet app-eselect/eselect-repository x sudo emerge --update --quiet app-portage/smart-live-rebuild # Currently using 3.12 python, this doesn't need to be default though x sudo emerge --update --quiet dev-lang/python:3.12 if [[ -z $(eselect repository list | grep ii-dots) ]]; then v sudo eselect repository create ii-dots v sudo eselect repository enable ii-dots fi if [[ -z $(eselect repository list | grep -E ".*guru \*.*") ]]; then v sudo eselect repository enable guru fi if [[ -z $(eselect repository list | grep -E ".*hyproverlay \*.*") ]]; then v sudo eselect repository enable hyproverlay fi arch=$(portageq envvar ACCEPT_KEYWORDS) # Exclude hyprland, will deal with that separately metapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,quickshell-git,screencapture,toolkit,widgets}) ebuild_dir="/var/db/repos/ii-dots" ########## IMPORT KEYWORDS (START) # Illogical-Impulse x sudo cp ./sdata/dist-gentoo/keywords ./sdata/dist-gentoo/keywords-user x sed -i "s/$/ ~${arch}/" ./sdata/dist-gentoo/keywords-user v sudo cp ./sdata/dist-gentoo/keywords-user /etc/portage/package.accept_keywords/illogical-impulse ########## IMPORT USEFLAGS v sudo cp ./sdata/dist-gentoo/useflags /etc/portage/package.use/illogical-impulse v sudo sh -c 'cat ./sdata/dist-gentoo/additional-useflags >> /etc/portage/package.use/illogical-impulse' ########## UPDATE SYSTEM v sudo emerge --sync v sudo emerge --quiet --newuse --update --deep @world v sudo emerge --quiet @smart-live-rebuild # Remove old ebuilds (if this isn't done the wildcard will fuck upon a version change) x sudo rm -fr ${ebuild_dir}/app-misc/illogical-impulse-* source ./sdata/dist-gentoo/import-local-pkgs.sh ########## INSTALL ILLOGICAL-IMPUSEL EBUILDS for i in "${metapkgs[@]}"; do x sudo mkdir -p ${ebuild_dir}/app-misc/${i} v sudo cp ./sdata/dist-gentoo/${i}/${i}*.ebuild ${ebuild_dir}/app-misc/${i}/ v sudo ebuild ${ebuild_dir}/app-misc/${i}/*.ebuild digest v sudo emerge --update --quiet app-misc/${i} done v sudo emerge --depclean ================================================ FILE: sdata/dist-gentoo/keywords ================================================ app-misc/illogical-impulse-audio app-misc/illogical-impulse-backlight app-misc/illogical-impulse-basic app-misc/illogical-impulse-bibata-modern-classic-bin app-misc/illogical-impulse-fonts-themes app-misc/illogical-impulse-hyprland app-misc/illogical-impulse-kde app-misc/illogical-impulse-microtex-git app-misc/illogical-impulse-portal app-misc/illogical-impulse-python app-misc/illogical-impulse-quickshell-git app-misc/illogical-impulse-screencapture app-misc/illogical-impulse-toolkit app-misc/illogical-impulse-widgets x11-misc/matugen media-fonts/twemoji app-misc/brightnessctl app-misc/cliphist gui-apps/hypridle gui-apps/hyprlock dev-libs/hyprlang gui-apps/hyprpicker gui-apps/hyprsunset gui-libs/xdg-desktop-portal-hyprland gui-apps/hyprshot gui-libs/hyprcursor gui-apps/wf-recorder gui-apps/wtype gui-apps/fuzzel gui-apps/wlogout dev-cpp/sdbus-c++ dev-libs/hyprland-protocols gui-libs/hyprutils gui-libs/hyprwire dev-util/hyprwayland-scanner gui-libs/hyprland-qt-support gui-libs/hyprland-guiutils gui-libs/hyprtoolkit gui-wm/hyprland dev-libs/hyprgraphics gui-libs/aquamarine x11-libs/libxkbcommon dev-util/breakpad dev-libs/linux-syscall-support dev-embedded/libdisasm kde-plasma/breeze-plus x11-themes/darkly x11-themes/adw-gtk3 media-fonts/space-grotesk media-fonts/material-symbols-variable ** media-fonts/readex-pro media-fonts/rubik-vf media-sound/songrec dev-cpp/glaze dev-cpp/cpptrace dev-libs/libdwarf ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/breeze-plus-6.2.5-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 DESCRIPTION="Breeze styled extra icon theme for KDE" HOMEPAGE="https://github.com/mjkim0727/breeze-plus" SRC_URI="https://github.com/mjkim0727/breeze-plus/archive/refs/tags/${PV}.tar.gz -> ${P}.tar.gz" LICENSE="LGPL-2.1" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RDEPEND="kde-plasma/breeze" BDEPEND="" S="${WORKDIR}/${PN}-${PV}" src_install() { insinto /usr/share/icons doins -r src/breeze-plus* } ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/darkly-0.5.24-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 # NOTE: Did not include QT5 backwards compatibility inherit cmake DESCRIPTION="Fork of Lightly - A modern style for Qt applications" HOMEPAGE="https://github.com/Bali10050/Darkly" SRC_URI="https://github.com/Bali10050/darkly/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz" LICENSE="GPL-2+" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" DEPEND=" kde-frameworks/kcoreaddons:6 kde-frameworks/kconfig:6 kde-frameworks/kguiaddons:6 kde-frameworks/ki18n:6 kde-frameworks/kiconthemes:6 kde-frameworks/kwindowsystem:6 kde-frameworks/kcmutils:6 kde-frameworks/frameworkintegration:6 kde-frameworks/kconfigwidgets:6 kde-plasma/kdecoration:6 dev-qt/qtdeclarative:6 " RDEPEND="${DEPEND}" BDEPEND=" dev-build/cmake kde-frameworks/extra-cmake-modules dev-vcs/git " S="${WORKDIR}/Darkly-${PV}" src_configure() { local mycmakeargs=( -DBUILD_TESTING=OFF -DBUILD_QT5=OFF -DBUILD_QT6=ON ) cmake_src_configure } src_install() { cmake_src_install rm -rf "${ED}/usr/$(get_libdir)/cmake" || die } ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/material-symbols-variable-9999.ebuild ================================================ # Copyright 1999-2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 inherit font DESCRIPTION="Material Design icons by Google - variable fonts" HOMEPAGE="https://github.com/google/material-design-icons" BASE_URL="https://github.com/google/material-design-icons/raw/refs/heads/master" SRC_URI=" ${BASE_URL}/variablefont/MaterialSymbolsOutlined%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsOutlined-FILL-GRAD-opsz-wght.ttf ${BASE_URL}/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsRounded-FILL-GRAD-opsz-wght.ttf ${BASE_URL}/variablefont/MaterialSymbolsSharp%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsSharp-FILL-GRAD-opsz-wght.ttf " LICENSE="Apache-2.0" SLOT="0" KEYWORDS="" S="${WORKDIR}" FONT_SUFFIX="ttf" src_unpack() { mkdir -p "${S}" cp "${DISTDIR}/MaterialSymbolsOutlined-FILL-GRAD-opsz-wght.ttf" \ "${S}/MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf" cp "${DISTDIR}/MaterialSymbolsRounded-FILL-GRAD-opsz-wght.ttf" \ "${S}/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf" cp "${DISTDIR}/MaterialSymbolsSharp-FILL-GRAD-opsz-wght.ttf" \ "${S}/MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf" } src_install() { font_src_install } ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/readex-pro-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 inherit font DESCRIPTION="Illogical Impulse Fonts and Theming Dependencies" HOMEPAGE="" SRC_URI="https://github.com/ThomasJockin/readexpro/archive/refs/heads/master.tar.gz -> ${P}-readexpro.tar.gz" LICENSE="GPL-2" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" RESTRICT="strip" S="${WORKDIR}/readexpro-master" src_install() { insinto /usr/share/fonts/ttf-readex-pro doins "${S}"/fonts/ttf/*.ttf } ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/rubik-vf-1.0-r1.ebuild ================================================ # Copyright 2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 COMMIT="e337a5f69a9bea30e58d05bd40184d79cc099628" inherit font DESCRIPTION="A sans serif font family with slightly rounded corners: variable font version" HOMEPAGE="https://github.com/googlefonts/rubik" SRC_URI="https://github.com/googlefonts/rubik/archive/${COMMIT}.tar.gz -> ${P}.tar.gz" LICENSE="OFL-1.1" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" S="${WORKDIR}/rubik-${COMMIT}" FONT_S="${S}/fonts/variable" FONT_SUFFIX="ttf" src_install() { font_src_install dodoc OFL.txt AUTHORS.txt CONTRIBUTORS.txt } ================================================ FILE: sdata/dist-gentoo/local-pkgs/fonts-and-themes/space-grotesk-1.1.4-r1.ebuild ================================================ # Copyright 1999-2025 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 EAPI=8 inherit font DESCRIPTION="Space Grotesk OTF font from 38C3 styleguide" HOMEPAGE="https://events.ccc.de/congress/2024/infos/styleguide.html" SRC_URI="https://events.ccc.de/congress/2024/infos/styleguide/38c3-styleguide-full-v2.zip" LICENSE="OFL-1.1" SLOT="0" KEYWORDS="~amd64 ~arm64 ~x86" BDEPEND="app-arch/unzip" S="${WORKDIR}/fonts/space-grotesk-${PV}" FONT_S="${S}/otf" FONT_SUFFIX="otf" src_install() { font_src_install dodoc OFL.txt } ================================================ FILE: sdata/dist-gentoo/outdate-detect-mode ================================================ AUTO ================================================ FILE: sdata/dist-gentoo/uninstall-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. for i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,screencapture,toolkit,widgets}; do v sudo emerge --unmerge $i done v sudo emerge --depclean ================================================ FILE: sdata/dist-gentoo/useflags ================================================ ################### AUDIO ################### media-sound/cava pipewire -pulseaudio #media-sound/pavucontrol-qt (no use flags) #media-video/wireplumber (no use flags) dev-libs/libdbusmenu gtk3 introspection media-sound/playerctl introspection ################### BACKLIGHT ################### app-misc/geoclue introspection #app-misc/brightnessctl app-misc/ddcutil X ################### BASIC ################### net-misc/axel nls ssl sys-devel/bc readline sys-apps/coreutils acl nls openssl xattr gmp #app-misc/cliphist (no use flags) dev-build/cmake ncurses net-misc/curl adns alt-svvc ftp hsts http2 http3 httpsrr imap openssl pop3 psl quic smtp ssl lftp websockets idn brotli zstd net-misc/rsync acl iconv ssl xattr net-misc/wget nls pore ssl zlib idn sys-apps/ripgrep pcre #dev-python/jq (nothing needed) #dev-build/meson (nothing needed) #x11-misc/xdg-user-dirs (nothing needed) ################### FONTS & THEMES ################### #kde-plasma/breeze (nothing needed) sys-apps/eza git app-shell/fish nls media-libs/fontconfig nls x11-terms/kitty X wayland #x11-misc/matugen #app-shell/starship (nothing needed) media-fonts/jetbrains-mono X media-fonts/twemoji X # The rest are handled in the ebuild ################### HYPRLAND ################### #gui-apps/hypridle (no use flags) #gui-libs/hyprcursor (no use flags) gui-wm/hyprland::hyproverlay X guiutils #gui-libs/hyprland-qtutils (no use flags) #gui-libs/hyprland-qt-support (no use flags) #dev-libs/hyprlang (no use flags) #gui-apps/hyprlock (no use flags) #gui-apps/hyprpicker (no use flags) #gui-apps/hyprsunset (no use flags) #gui-libs/hyprutils (no use flags) #dev-util/hyprwayland-scanner (no use flags) #gui-apps/wl-clipboard (no use fags) ################### KDE ################### kde-plasma/bluedevil -handbook gnome-base/gnome-keyring pam ssh-agent net-misc/networkmanager concheck introspection nss ppp tools wifi -wext -modemmanager #kde-plasma/plasma-nm (nothing needed) #kde-plasma/polkit-kde-agent (nothing needed) kde-apps/dolphin -handbook kde-plasma/systemsettings -handbook ################### MICROTEX-GIT ################### dev-cpp/gtkmm X wayland ################### PORTAL ################### sys-apps/xdg-desktop-portal seccomp #sys-plasma/xdg-desktop-portal-kde (nothing needed) sys-apps/xdg-desktop-portal-gtk X wayland #gui-libs/xdg-desktop-portal-hyprland (elogiind maybe) ################### PYTHON ################### #dev-python/clang (nothing needed) #dev-python/uv (nothing needed) gui-libs/gtk X introspection wayland gui-libs/libadwaita introspection net-libs/libsoup brotli introspection ssl #dev-libs/gobject-introspection (nothing needed) # dart-sassc handled in the ebuild ################### SCREENCAPTURE ################### #gui-apps/hyprshot (no use flags) gui-apps/slurp -man #gui-apps/swappy (no use flags) app-text/tesseract jpeg openmp png webp gui-apps/wf-recorder pipewire ################### TOOLKIT ################### kde-apps/kdialog X dev-qt/qt5compat gui icu qml dev-qt/qtbase X concurrent cups dbus gui icu libinput libproxy network nls opengl sql sqlite ssl udev wayland widgets xml zstd dev-qt/qtdeclarative jit network opengl sql ssl svg widgets #dev-qt/qtimageformats (nothing needed) dev-qt/qtmultimedia X dbus ffmpeg opengl pipewire qml wayland v4l dev-qt/qtpositioning qml geoclue #dev-qt/qtquicktimeline (nothing needed) #dev-qt/qtsensors (nothing needed) #dev-qt/qtsvg (nothing needed) dev-qt/qttools assistant linguist opengl qdbus widgets zstd #dev-qt/qttranslations (no use flags) dev-qt/qtvirtualkeyboard sound spell #dev-qt/qtwayland (nothing needed) #kde-framework/syntax-highlighting (nothing needed) sys-power/upower introspection #gui-apps/wtype (no use flags) #x11-misc/ydotool (no use flags) ################### WIDGETS ################### gui-apps/fuzzel png svg dev-libs/glib dbus elf introspection mime xattr # ngl idk about nm-connection-editor. Works fine without #app-i18n/translate-shell (nothing needed) #gui-apps/wlogout (no use flags) media-gfx/imagemagick xml ################### OTHER ################### dev-cpp/cpptrace unwind ================================================ FILE: sdata/dist-nix/README.md ================================================ # Install scripts using Nix to achieve cross-distros - This directory is currently WIP. - See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/) - See also [#1061](https://github.com/end-4/dots-hyprland/issues/1061) **NOTE: The `dist-nix` is not for NixOS but every distro, using Nix and home-manager.** As we all know Nix and Home-manager has two major functionalities: - Handling dependencies (i.e. package installation) - Handling dotfiles They are discussed in following sections. # Handling dependencies ## Status Partially works. See [Discussion #2382](https://github.com/end-4/dots-hyprland/discussions/2382). ## plan Note that this script must be idempotent. TODO: - [ ] Fix all TODOs inside `dist-nix`. ([search online](https://github.com/search?q=repo%3Aend-4%2Fdots-hyprland+path%3A%2F%5Esdata%5C%2Fdist-nix%5C%2F%2F+TODO&type=code)) - [ ] Since Nix uses a large number of inodes, need to warn user if inode-limited filesystem (typically ext4) is used. - [ ] Deal with error when running `systemctl --user enable ydotool --now`: ```plain Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=@.host --user to connect to bus of other user) ``` - [ ] Handle problem that `pkill qs` and `pkill hyprland` does not work (should be `.quickshell-wra` and `.Hyprland-wrapp` when installed via Nix). ## Attentions ### PAM On non-NixOS distros, programs using PAM (typically screen locker) will not work if installed via Nix, so user has to use their own distro's package for the screen lock. - One problem is that Debian(-based) distros use modified version of PAM which supports `@include` directive in `/etc/pam.d` config files but the PAM from Nix does not support it, see [this comment](https://github.com/NixOS/nixpkgs/issues/128523#issuecomment-1086106614). - Another problem is the location of a suid helper binary that is necessary, see [this comment](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230). As [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230) by @Cu3PO42 , both the problem could be solved by using the system-provided libpam instead. See also [caelestia-dots/shell#668](https://github.com/caelestia-dots/shell/issues/668). ### GPU On non-NixOS distros, packages installed via home-manager have problem accessing GPU, especially Hyprland because it requires GPU acceleration to launch. ~~`nixGL` should be used to address the problem.~~ Since home-manager 25.11, for non-NixOS just set the following: ```nix targets.genericLinux.enable = true; ``` Then during building, home-manager will show a message to tell you running a command manually to configure GPU, like: ```bash sudo /nix/store/-non-nixos-gpu/bin/non-nixos-gpu-setup ``` It runs a bash script with following content: ``` #!/nix/store/-bash-/bin/bash set -e # Install the systemd service file and ensure that the store path won't be # garbage-collected as long as it's installed. unit_path=/etc/systemd/system/non-nixos-gpu.service ln -sf /nix/store/-non-nixos-gpu/resources/non-nixos-gpu.service "$unit_path" ln -sf "$unit_path" "/nix/var/nix"/gcroots/non-nixos-gpu.service systemctl daemon-reload systemctl enable non-nixos-gpu.service systemctl restart non-nixos-gpu.service ``` _Note: it uses `systemctl`, maybe won't work for OpenRC..._ See [gpu-non-nixos](https://nix-community.github.io/home-manager/index.xhtml#sec-usage-gpu-non-nixos). # Handling dot files ## Status Paused, until some suitable method has been confirmed to meet the requirements below. ## Requirements About handling the dotfiles, i.e. `dots/`, if we are doing this using Nix then the following requirements must be fulfilled. **1. Allow modifications over the existing dotfiles.** Current state of `./setup install`: - After finishing running `./setup install`, users can modify any dotfiles in a traditional way, and if they run `./setup install` again to update then they need to skip the steps which overwrite the targets that they have modified and later sync the upgrade manually for such targets by themselves. - For Hyprland, specially we have a `custom` folder along with `~/.config/hypr/hyprland.conf` which will only get overwritten the first time but not the later times running `./setup install`. - This works but is not elegant. An experimental solution is using yaml config to store the selected behavior for each target, see [#2137](https://github.com/end-4/dots-hyprland/issues/2137). If we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation. **2. Allow choosing targets.** This is similar to the above. For example user may want to use their own `~/.config/foot` instead of the files under `dots/.config/foot` entirely. **3. Easy developing dotfiles or at least not worse than current state.** About the current state: - @clsty: "If I were the one who develops the dotfiles, I will make changes to the local Git repo `dots-hyprland` and rerun `./setup install-files -f` to apply the changes to observe the outcome." - @end-4 (who develops the dots; see [comment](https://github.com/end-4/dots-hyprland/pull/2278#issuecomment-3454929577)): "I modify my local copy of stuff, copy the relevant parts over, optionally selectively pick changes then commit. It's.... the most obvious way but I guess not necessarily the cleanest" If we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation. **4. Others** Find out a good method to avoid what @end-4 [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-2954725029): > About home-manager, from my limited understanding of and experience with it, any change to the config files require a rebuild right? If this is indeed the case, switching entirely to this is not okay. Having to wait 20 seconds for each change is absurd. Some information may help, e.g. @darsh032 [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3336839862): > I mean thats not really needed you can use mkOutOfStoreSymlink or use hjem-impure to change the configs without rebuilding And also the "hmrice" [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3353345504) by @Markus328 , and the `flake.nix` (for quickshell only) [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3354387126) by @darsh032 . ================================================ FILE: sdata/dist-nix/home-manager/flake.nix ================================================ # flake.nix { description = "illogical-impulse"; inputs = { nixpkgs.url = "nixpkgs/nixos-25.11"; #nixpkgs.url = "nixpkgs/nixos-unstable"; home-manager = { url = "github:nix-community/home-manager/release-25.11"; #url = "github:nix-community/home-manager/master"; inputs.nixpkgs.follows = "nixpkgs"; }; #nixgl.url = "github:nix-community/nixGL"; quickshell = { url = "github:quickshell-mirror/quickshell/7511545ee20664e3b8b8d3322c0ffe7567c56f7a"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { nixpkgs, home-manager, #nixgl, quickshell, ... }: let home_attrs = rec { username = import ./username.nix; homeDirectory = "/home/${username}"; # Do not edit stateVersion value, see https://github.com/nix-community/home-manager/issues/5794 stateVersion = "25.05"; }; system = "x86_64-linux"; lib = nixpkgs.lib; pkgs = import nixpkgs { inherit system; }; in { homeConfigurations = { illogical_impulse = home-manager.lib.homeManagerConfiguration { inherit pkgs; extraSpecialArgs = { inherit home_attrs #nixgl quickshell; }; modules = [ ./home.nix ]; }; }; }; } ================================================ FILE: sdata/dist-nix/home-manager/home.nix ================================================ { config, lib, pkgs, #nixgl, quickshell, home_attrs, ... }: { programs.home-manager.enable = true; # Necessary for non-NixOS to handle GPU (since home-manager version 25.11) targets.genericLinux.enable = true; #nixGL.packages = nixgl.packages; #nixGL.defaultWrapper = "mesa"; xdg.portal = { enable = true; extraPortals = with pkgs; [ xdg-desktop-portal-gnome xdg-desktop-portal-gtk xdg-desktop-portal-wlr kdePackages.xdg-desktop-portal-kde ]; # The following seems to generate ~/.config/xdg-desktop-portal conflicting with the one under dots/ #config.hyprland = { # default = [ "hyprland" "gtk" ]; # "org.freedesktop.impl.portal.ScreenCast" = [ "gnome" ]; #}; }; # Note: The following generate files under ~/.config/fontconfig/conf.d/ # fontconfig may rely on this to properly find fonts installed via Nix. fonts.fontconfig.enable = true; wayland.windowManager.hyprland = { ## Make sure home-manager not generate ~/.config/hypr/hyprland.conf systemd.enable = false; plugins = []; settings = {}; extraConfig = ""; enable = true; ## Use NixGL #package = config.lib.nixGL.wrap pkgs.hyprland; package = pkgs.hyprland; }; home = { packages = with pkgs; [ ##### Sure ##### ## Basic cli tool ## inetutils: provides hostname, ifconfig, ping, etc. ## libnotify: provides notify-send inetutils libnotify ##### Other MISC ##### dbus xorg.xlsclients # some basic things foot # Used in Quickshell and Hyprland config; its config is also included kdePackages.kconfig # provide kwriteconfig6, used in install script ##### Not work, to be solved ##### # hyprlock pamtester # NOTE: below are migrated from dist-arch. For each package, must know why it's needed and how it's used specifically, cuz things may be need tweak to properly use the package installed by Nix, for example those have hardcoded path /usr/* . See sdata/deps-info.md ### illogical-impulse-audio libcava #cava lxqt.pavucontrol-qt #pavucontrol-qt wireplumber #wireplumber pipewire #pipewire-pulse libdbusmenu-gtk3 #libdbusmenu-gtk3 playerctl #playerctl ### illogical-impulse-backlight (geoclue2.override { withDemoAgent = true; }) #geoclue brightnessctl #brightnessctl ddcutil #ddcutil ### illogical-impulse-basic bc #bc uutils-coreutils-noprefix #coreutils cliphist #cliphist cmake #cmake curlFull #curl wget #wget ripgrep #ripgrep jq #jq xdg-user-dirs #xdg-user-dirs rsync #rsync yq-go #go-yq ### illogical-impulse-bibata-modern-classic-bin bibata-cursors ### illogical-impulse-fonts-themes adw-gtk3 #adw-gtk-theme-git kdePackages.breeze kdePackages.breeze-icons #breeze #breeze-plus (TODO: Not available as nixpkg) darkly darkly-qt5 #darkly-bin eza #eza #fish (Currently install via system PM; TODO: should install via nix in future when authentication problem fixed) fontconfig #fontconfig kitty #kitty (Used in fuzzel, Hyprland, kdeglobals and Quickshell config; kitty config is also included as dots) matugen #matugen-bin (Used in Quickshell) #otf-space-grotesk (TODO: Not available as Nixpkg) starship #starship nerd-fonts.jetbrains-mono #ttf-jetbrains-mono-nerd material-symbols #ttf-material-symbols-variable-git #ttf-readex-pro (TODO: seems not available as nixpkg) rubik #ttf-rubik-vf twemoji-color-font #ttf-twemoji ### illogical-impulse-hyprland #hyprland hyprsunset #hyprsunset wl-clipboard #wl-clipboard ### illogical-impulse-kde kdePackages.bluedevil #bluedevil #gnome-keyring #gnome-keyring (TODO: Install via system PM instead; should install via nix in future when authentication problem fixed) networkmanager #networkmanager kdePackages.plasma-nm #plasma-nm #polkit-kde-agent (TODO: Install via system PM instead; should install via nix in future when authentication problem fixed) kdePackages.dolphin #dolphin kdePackages.systemsettings #systemsettings ### illogical-impulse-microtex-git # TODO: Not available as nixpkg ### illogical-impulse-portal #xdg-desktop-portal (Included elsewhere) #xdg-desktop-portal-kde (Included elsewhere) #xdg-desktop-portal-gtk (Included elsewhere) #xdg-desktop-portal-hyprland (Included elsewhere) ### illogical-impulse-python #clang (Not needed for Nix. However, when cmake is installed by Nix, then pkg-config, cairo etc will be used but they can only be accessible in Nix development environment for example nix-shell, nix develop, etc. See `sdata/uv/shell.nix`. ) uv #uv gtk4 #gtk4 libadwaita #libadwaita libsoup_3 #libsoup3 libportal-gtk4 #libportal-gtk4 gobject-introspection #gobject-introspection ### illogical-impulse-screencapture hyprshot #hyprshot slurp #slurp swappy #swappy tesseract #tesseract #tesseract-data-eng (TODO: Seems not available as nixpkg) wf-recorder #wf-recorder ### illogical-impulse-toolkit upower #upower wtype #wtype ydotool #ydotool ### illogical-impulse-widgets fuzzel #fuzzel glib #glib2 imagemagick #imagemagick hypridle #hypridle #hyprlock (Should not be installed via Nix; TODO: should install via nix in future when authentication problem fixed) hyprpicker #hyprpicker songrec #songrec translate-shell #translate-shell wlogout #wlogout libqalculate #libqalculate ] ++ [ #(config.lib.nixGL.wrap pkgs.hyprland) ### illogical-impulse-quickshell-git #(config.lib.nixGL.wrap quickshell.packages.x86_64-linux.default) (import ./quickshell.nix { inherit pkgs quickshell; #nixGLWrap = config.lib.nixGL.wrap; }) ]; }//home_attrs; } ================================================ FILE: sdata/dist-nix/home-manager/quickshell.nix ================================================ { pkgs, quickshell, #nixGLWrap, ... }: let #qs = nixGLWrap quickshell.packages.x86_64-linux.default; qs = quickshell.packages.x86_64-linux.default; in pkgs.stdenv.mkDerivation { name = "illogical-impulse-quickshell-wrapper"; meta = with pkgs.lib; { #description = "Quickshell wrapped with NixGL + bundled Qt deps for home-manager usage"; description = "Quickshell bundled Qt deps for home-manager usage"; license = licenses.gpl3Only; }; dontUnpack = true; dontConfigure = true; dontBuild = true; nativeBuildInputs = [ pkgs.makeWrapper pkgs.qt6.wrapQtAppsHook ]; buildInputs = with pkgs; [ qs kdePackages.qtwayland kdePackages.qtpositioning kdePackages.qtlocation kdePackages.syntax-highlighting gsettings-desktop-schemas # https://nixos.wiki/wiki/Qt # https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/qt-6/srcs.nix qt6.qtbase #qt6-base qt6.qtdeclarative #qt6-declarative qt6.qt5compat #qt6-5compat #qt6-avif-image-plugin (TODO: seems not available as nixpkg) qt6.qtimageformats #qt6-imageformats qt6.qtmultimedia #qt6-multimedia qt6.qtpositioning #qt6-positioning qt6.qtquicktimeline #qt6-quicktimeline qt6.qtsensors #qt6-sensors qt6.qtsvg #qt6-svg qt6.qttools #qt6-tools qt6.qttranslations #qt6-translations qt6.qtvirtualkeyboard #qt6-virtualkeyboard qt6.qtwayland #qt6-wayland kdePackages.kirigami #kirigami kdePackages.kdialog #kdialog kdePackages.syntax-highlighting #syntax-highlighting vulkan-headers #vulkan-headers libdrm #libdrm cpptrace #cpptrace jemalloc #jemalloc mesa #mesa ]; installPhase = '' mkdir -p $out/bin ls -l ${qs}/bin || true makeWrapper ${qs}/bin/qs $out/bin/qs \ --prefix XDG_DATA_DIRS : ${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name} chmod +x $out/bin/qs ''; } ================================================ FILE: sdata/dist-nix/install-deps.sh ================================================ # This script is meant to be sourced. # It's not for directly running. function vianix-warning(){ printf "${STY_YELLOW}" printf "Currently \"--via-nix\" will run:\n" printf " home-manager switch --flake .#illogical_impulse\n" printf "If you are already using home-manager,\n" printf "it may override your current config,\n" printf "despite that this should be reversible.\n" printf "${STY_RST}" pause } function install_nix(){ # https://github.com/NixOS/experimental-nix-installer local cmd=nix x mkdir -p ${REPO_ROOT}/cache x curl -JLo ${REPO_ROOT}/cache/nix-installer https://artifacts.nixos.org/experimental-installer x sh ${REPO_ROOT}/cache/nix-installer install try source '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' command -v $cmd && return echo "Failed in installing $cmd." echo "Please install it by yourself and then retry." return 1 } function install_home-manager(){ # https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone local cmd=home-manager # Maybe installed already, just not sourced yet try source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh command -v $cmd && return x nix-channel --add https://nixos.org/channels/nixos-25.11 nixpkgs-home x nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz home-manager x nix-channel --update x env NIX_PATH="nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs-home" nix-shell '' -A install command -v $cmd && return echo "Failed in installing $cmd." echo "Please install it by yourself and then retry." echo "" echo "Hint: It's also possible that the installation is actually successful," echo "but your \"\$PATH\" is not properly set." echo "This can happen when you have used \"su user\" to switch user." echo "If this is the problem, use \"su - user\" instead." return 1 } function hm_deps(){ SETUP_HM_DIR="${REPO_ROOT}/sdata/dist-nix/home-manager" SETUP_USERNAME_NIXFILE="${SETUP_HM_DIR}/username.nix" echo "\"$(whoami)\"" > "${SETUP_USERNAME_NIXFILE}" x git add "${SETUP_USERNAME_NIXFILE}" cd $SETUP_HM_DIR x home-manager switch --flake .#illogical_impulse \ --extra-experimental-features nix-command \ --extra-experimental-features flakes x sudo /nix/store/*-non-nixos-gpu/bin/non-nixos-gpu-setup cd $REPO_ROOT x git rm -f "${SETUP_USERNAME_NIXFILE}" } ################################################## ################################################## vianix-warning TEST_CMDS=(curl fish swaylock gnome-keyring) ensure_cmds "${TEST_CMDS[@]}" if ! command -v nix >/dev/null 2>&1;then echo -e "${STY_YELLOW}[$0]: \"nix\" not found.${STY_RST}" showfun install_nix v install_nix fi if ! command -v home-manager >/dev/null 2>&1;then echo -e "${STY_YELLOW}[$0]: \"home-manager\" not found.${STY_RST}" showfun install_home-manager v install_home-manager fi showfun hm_deps v hm_deps ================================================ FILE: sdata/dist-nix/outdate-detect-mode ================================================ WIP ================================================ FILE: sdata/lib/dist-determine.sh ================================================ # This script is meant to be sourced. # It's not for directly running. function print_os_group_id(){ printf "${STY_CYAN}" printf "===INFO===\n" printf "Detected OS_DISTRO_ID: ${OS_DISTRO_ID}\n" printf "Detected OS_DISTRO_ID_LIKE: ${OS_DISTRO_ID_LIKE}\n" printf "Determined OS_GROUP_ID: ${OS_GROUP_ID}\n" printf "==========\n\n" printf "${STY_RST}" } function print_os_group_id_alike(){ printf "${STY_YELLOW}" printf "===WARNING===\n" printf "Your OS_GROUP_ID has been determined by \"alike\" match.\n" printf "Ideally, it should also work for your distro.\n" printf "Still, there is a chance that it not works as expected or even fails.\n" printf "Proceed only at your own risk.\n" printf "=============\n\n" printf "${STY_RST}" } function print_os_group_id_unofficial(){ printf "${STY_PURPLE}" printf "===NOTICE===\n" printf "The support for your distro is provided by the community.\n" printf "It is not officially supported by github:end-4/dots-hyprland .\n" printf "${STY_BOLD}" printf "If you find out any problems about it, PR is welcomed if you are able to address it.\n" printf "Or, create a discussion about it, but please do not submit issue, \n" printf "because the developers do not use this distro, therefore they cannot help.${STY_RST}\n" printf "${STY_PURPLE}" printf "Proceed only at your own risk.\n" printf "============\n\n" printf "${STY_RST}" } function print_os_group_id_unsupported(){ printf "${STY_RED}" printf "===CAUTION===\n" printf "\"--via-nix\" is forcely specified \n" printf "as the only way to try to install on your distro.\n" printf "It is still experimental.\n" printf "Some functionalities are missing.\n" printf "It may also behave unexpectedly.\n" printf "Proceed only at your own risk.\n" printf "=============\n\n" printf "${STY_RST}" sleep 3 } function print_os_group_id_fallback(){ printf "${STY_RED}" printf "===CAUTION===\n" printf "Distro not recognized, determined as fallback.\n" printf "=============\n\n" printf "${STY_RST}" } function print_os_group_id_architecture(){ printf "${STY_RED}" printf "===CAUTION===\n" printf "Detected machine architecture: ${MACHINE_ARCH}\n" printf "This script only supports x86_64.\n" printf "It is very likely to fail when installing dependencies on your machine.\n" printf "=============\n\n" printf "${STY_RST}" } ##################################################################################### #################### # Detect distro # Helpful link(s): # http://stackoverflow.com/questions/29581754 # https://github.com/which-distro/os-release OS_RELEASE_FILE_CUSTOM="${REPO_ROOT}/os-release" if test -f "${OS_RELEASE_FILE_CUSTOM}"; then printf "${STY_YELLOW}Warning: using custom os-release file \"${OS_RELEASE_FILE_CUSTOM}\".${STY_RST}\n" OS_RELEASE_FILE="${OS_RELEASE_FILE_CUSTOM}" elif test -f /etc/os-release; then OS_RELEASE_FILE=/etc/os-release else printf "${STY_RED}/etc/os-release does not exist, aborting...${STY_RST}\n" ; exit 1 fi export OS_DISTRO_ID=$(awk -F'=' '/^ID=/ { gsub(/["\x27]/,"",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null) export OS_DISTRO_ID_LIKE=$(awk -F'=' '/^ID_LIKE=/ { gsub(/["\x27]/,"",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null) #################### # Determine distro ID if [[ "$OS_DISTRO_ID" =~ ^(arch|endeavouros|cachyos)$ ]]; then OS_GROUP_ID="arch" print_os_group_id_functions=(print_os_group_id) elif [[ "$OS_DISTRO_ID_LIKE" == "arch" ]]; then OS_GROUP_ID="arch" print_os_group_id_functions=(print_os_group_id{,_alike}) elif [[ "$OS_DISTRO_ID" == "gentoo" ]]; then OS_GROUP_ID="gentoo" print_os_group_id_functions=(print_os_group_id{,_unofficial}) elif [[ "$OS_DISTRO_ID_LIKE" == "gentoo" ]]; then OS_GROUP_ID="gentoo" print_os_group_id_functions=(print_os_group_id{,_alike,_unofficial}) elif [[ "$OS_DISTRO_ID" == "fedora" ]]; then OS_GROUP_ID="fedora" print_os_group_id_functions=(print_os_group_id{,_unofficial}) elif [[ "$OS_DISTRO_ID_LIKE" == "fedora" ]]; then OS_GROUP_ID="fedora" print_os_group_id_functions=(print_os_group_id{,_alike,_unofficial}) elif [[ "$OS_DISTRO_ID" =~ ^(opensuse-leap|opensuse-tumbleweed)$ ]] || [[ "$OS_DISTRO_ID_LIKE" =~ ^(opensuse|suse)(\ (opensuse|suse))?$ ]]; then OS_GROUP_ID="suse" INSTALL_VIA_NIX=true print_os_group_id_functions=(print_os_group_id{,_unsupported}) elif [[ "$OS_DISTRO_ID" == "debian" || "$OS_DISTRO_ID_LIKE" == "debian" ]]; then OS_GROUP_ID="debian" INSTALL_VIA_NIX=true print_os_group_id_functions=(print_os_group_id{,_unsupported}) else OS_GROUP_ID="fallback" INSTALL_VIA_NIX=true print_os_group_id_functions=(print_os_group_id{,_fallback,_unsupported}) fi #################### # Detect architecture # Helpful link(s): # http://stackoverflow.com/questions/45125516 export MACHINE_ARCH=$(uname -m) case "${MACHINE_ARCH}" in "x86_64") sleep 0;; *) print_os_group_id_functions+=(print_os_group_id_architecture);; esac ================================================ FILE: sdata/lib/environment-variables.sh ================================================ # This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang. XDG_BIN_HOME=${XDG_BIN_HOME:-$HOME/.local/bin} XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache} XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share} XDG_STATE_HOME=${XDG_STATE_HOME:-$HOME/.local/state} STY_RED='\e[31m' STY_GREEN='\e[32m' STY_YELLOW='\e[33m' STY_BLUE='\e[34m' STY_PURPLE='\e[35m' STY_CYAN='\e[36m' STY_BOLD='\e[1m' STY_FAINT='\e[2m' STY_SLANT='\e[3m' STY_UNDERLINE='\e[4m' STY_BLINK='\e[5m' STY_INVERT='\e[7m' STY_RST='\e[00m' # Used by register_temp_file() declare -a TEMP_FILES_TO_CLEANUP=() # Used by install script BACKUP_DIR="${BACKUP_DIR:-$HOME/ii-original-dots-backup}" DOTS_CORE_CONFDIR="${XDG_CONFIG_HOME}/illogical-impulse" INSTALLED_LISTFILE="${DOTS_CORE_CONFDIR}/installed_listfile" FIRSTRUN_FILE="${DOTS_CORE_CONFDIR}/installed_true" ================================================ FILE: sdata/lib/functions.sh ================================================ # This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang. # NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file. # shellcheck shell=bash function try { "$@" || sleep 0; } function v(){ echo -e "####################################################" echo -e "${STY_BLUE}[$0]: Next command:${STY_RST}" echo -e "${STY_GREEN}$*${STY_RST}" local execute=true if $ask;then while true;do echo -e "${STY_BLUE}Execute? ${STY_RST}" echo " y = Yes" echo " e = Exit now" echo " s = Skip this command (NOT recommended - your setup might not work correctly)" echo " yesforall = Yes and don't ask again; NOT recommended unless you really sure" local p; read -p "====> " p case $p in [yY]) echo -e "${STY_BLUE}OK, executing...${STY_RST}" ;break ;; [eE]) echo -e "${STY_BLUE}Exiting...${STY_RST}" ;exit ;break ;; [sS]) echo -e "${STY_BLUE}Alright, skipping this one...${STY_RST}" ;execute=false ;break ;; "yesforall") echo -e "${STY_BLUE}Alright, won't ask again. Executing...${STY_RST}"; ask=false ;break ;; *) echo -e "${STY_RED}Please enter [y/e/s/yesforall].${STY_RST}";; esac done fi if $execute;then x "$@";else echo -e "${STY_YELLOW}[$0]: Skipped \"$*\"${STY_RST}" fi } # When use v() for a defined function, use x() INSIDE its definition to catch errors. function x(){ if "$@";then local cmdstatus=0;else local cmdstatus=1;fi # 0=normal; 1=failed; 2=failed but ignored while [ $cmdstatus == 1 ] ;do echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed." echo -e "You may need to resolve the problem manually BEFORE repeating this command." echo -e "[Tip] If a certain package is failing to install, try installing it separately in another terminal.${STY_RST}" echo " r = Repeat this command (DEFAULT)" echo " e = Exit now" echo " i = Ignore this error and continue (your setup might not work correctly)" local p; read -p " [R/e/i]: " p case $p in [iI]) echo -e "${STY_BLUE}Alright, ignore and continue...${STY_RST}";cmdstatus=2;; [eE]) echo -e "${STY_BLUE}Alright, will exit.${STY_RST}";break;; *) echo -e "${STY_BLUE}OK, repeating...${STY_RST}" if "$@";then cmdstatus=0;else cmdstatus=1;fi ;; esac done case $cmdstatus in 0) echo -e "${STY_BLUE}[$0]: Command \"${STY_GREEN}$*${STY_BLUE}\" finished.${STY_RST}";; 1) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed. Exiting...${STY_RST}";exit 1;; 2) echo -e "${STY_RED}[$0]: Command \"${STY_GREEN}$*${STY_RED}\" has failed but ignored by user.${STY_RST}";; esac } function showfun(){ echo -e "${STY_BLUE}[$0]: The definition of function \"$1\" is as follows:${STY_RST}" printf "${STY_GREEN}" type -a "$1" 2>/dev/null || return 1 printf "${STY_RST}" } function pause(){ if [ ! "$ask" == "false" ];then printf "${STY_FAINT}${STY_SLANT}" local p; read -p "(Ctrl-C to abort, Enter to proceed)" p printf "${STY_RST}" fi } function remove_bashcomments_emptylines(){ echo "pwd=$(pwd)" echo "input=$1" echo "output=$2" mkdir -p "$(dirname "$2")" cat "$1" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > "$2" } function prevent_sudo_or_root(){ case $(whoami) in root) echo -e "${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}";exit 1;; esac } # Initialize sudo session and keep it alive in background # Store PID in a global variable that can be accessed by trap declare -g SUDO_KEEPALIVE_PID="" function sudo_init_keepalive(){ # Check if sudo is available if ! command -v sudo >/dev/null 2>&1; then return 0 fi # Skip if already initialized if [[ -n "$SUDO_KEEPALIVE_PID" ]] && kill -0 "$SUDO_KEEPALIVE_PID" 2>/dev/null; then return 0 fi # Prompt for sudo password once at the beginning echo -e "${STY_CYAN}[$0]: Requesting sudo privileges for installation...${STY_RST}" if ! sudo true; then echo -e "${STY_RED}[$0]: Failed to obtain sudo privileges. Aborting...${STY_RST}" exit 1 fi # Start background process to keep sudo session alive # This updates the sudo timestamp every 60 seconds ( while true; do sleep 60 sudo true 2>/dev/null || exit 0 done ) & SUDO_KEEPALIVE_PID=$! echo -e "${STY_GREEN}[$0]: Sudo session initialized and will be kept alive (PID: $SUDO_KEEPALIVE_PID)${STY_RST}" } # Stop the sudo keepalive background process function sudo_stop_keepalive(){ if [[ -n "$SUDO_KEEPALIVE_PID" ]] && kill -0 "$SUDO_KEEPALIVE_PID" 2>/dev/null; then kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true wait "$SUDO_KEEPALIVE_PID" 2>/dev/null || true SUDO_KEEPALIVE_PID="" fi } function git_auto_unshallow(){ # We need this function for latest_commit_hash to work properly if [[ -f "$(git rev-parse --git-dir)/shallow" ]]; then echo "Shallow clone detected. Unshallowing..." git fetch --unshallow fi } function latest_commit_timestamp(){ local target_path="$1" local result=$(git log -1 --format="%ct" -- "$target_path" 2>/dev/null) if [[ -z "$result" ]]; then echo "[latest_commit_timestamp] The timestamp of \"$target_path\" is empty. Aborting..." >&2 return 1 fi echo "$result" } function log_info() { echo -e "${STY_BLUE}[INFO]${STY_RST} $1" } function log_success() { echo -e "${STY_GREEN}[SUCCESS]${STY_RST} $1" } function log_warning() { echo -e "${STY_YELLOW}[WARNING]${STY_RST} $1" } function log_error() { echo -e "${STY_RED}[ERROR]${STY_RST} $1" >&2 } function log_header() { echo -e "\n${STY_PURPLE}=== $1 ===${STY_RST}" } function log_die() { log_error "$1" exit 1 } # Enhanced: Check if command exists function command_exists() { command -v "$1" >/dev/null 2>&1 } # Enhanced: Require a command or die function require_command() { if ! command_exists "$1"; then log_die "Required command '$1' not found. Please install it first." fi } # Enhanced: Sanitize file paths to prevent directory traversal function sanitize_path() { local path="$1" # Remove null bytes, newlines, and control characters path=$(echo "$path" | tr -d '\000-\037') # Prevent directory traversal beyond current context case "$path" in ..|../*|*/../*|*/..|\.\./*) log_die "Invalid path detected (directory traversal attempt): $path" ;; esac echo "$path" } # Enhanced: Safe file comparison that checks existence first function files_differ() { local file1="$1" local file2="$2" # Check if both files exist if [[ ! -f "$file1" ]] || [[ ! -f "$file2" ]]; then return 0 # Consider them different if either doesn't exist fi # Quick size check first (faster than byte comparison) local size1 size2 if command -v stat &>/dev/null; then # Try both BSD and GNU stat formats size1=$(stat -f%z "$file1" 2>/dev/null || stat -c%s "$file1" 2>/dev/null) size2=$(stat -f%z "$file2" 2>/dev/null || stat -c%s "$file2" 2>/dev/null) if [[ "$size1" != "$size2" ]]; then return 0 # Different sizes = different files fi fi # Then byte-by-byte comparison cmp -s "$file1" "$file2" && return 1 || return 0 } # Enhanced: Create backup of a file with timestamp function backup_file_simple() { local file="$1" local backup_suffix="${2:-.bak}" if [[ ! -f "$file" ]]; then log_warning "Cannot backup non-existent file: $file" return 1 fi local timestamp timestamp=$(date +%Y%m%d-%H%M%S) local backup_name="${file}${backup_suffix}.${timestamp}" if cp -p "$file" "$backup_name" 2>/dev/null; then log_info "Backed up: $file → $backup_name" return 0 else log_error "Failed to backup: $file" return 1 fi } # Enhanced: Validate that a file path is within allowed directory function validate_path_in_directory() { local file_path="$1" local allowed_dir="$2" # Resolve to absolute paths local abs_file local abs_dir abs_file=$(cd "$(dirname "$file_path")" 2>/dev/null && pwd -P)/$(basename "$file_path") || return 1 abs_dir=$(cd "$allowed_dir" 2>/dev/null && pwd -P) || return 1 # Check if file path starts with allowed directory case "$abs_file" in "$abs_dir"/*) return 0 ;; *) log_error "Path validation failed: $file_path is not within $allowed_dir" return 1 ;; esac } # Enhanced: Check if script is running in a CI/CD environment function is_ci_environment() { [[ -n "${CI:-}" ]] || \ [[ -n "${GITHUB_ACTIONS:-}" ]] || \ [[ -n "${GITLAB_CI:-}" ]] || \ [[ -n "${TRAVIS:-}" ]] || \ [[ -n "${CIRCLECI:-}" ]] } # Enhanced: Progress bar (optional, for long operations) function show_progress() { local current="$1" local total="$2" local message="${3:-Processing}" if ! command_exists tput; then return fi local percent=$((current * 100 / total)) local bar_length=40 local filled=$((bar_length * current / total)) local empty=$((bar_length - filled)) printf "\r${message}: [" >&2 printf "%${filled}s" | tr ' ' '=' >&2 printf "%${empty}s" | tr ' ' ' ' >&2 printf "] %d%%" "$percent" >&2 if [[ $current -eq $total ]]; then echo >&2 fi } # Enhanced: Cleanup temporary files on exit #declare -a TEMP_FILES_TO_CLEANUP=() function register_temp_file() { local temp_file="$1" TEMP_FILES_TO_CLEANUP+=("$temp_file") } function cleanup_temp_files() { for temp_file in "${TEMP_FILES_TO_CLEANUP[@]}"; do if [[ -f "$temp_file" ]]; then rm -f "$temp_file" 2>/dev/null || true fi done TEMP_FILES_TO_CLEANUP=() } # Enhanced: Check disk space before operations function check_disk_space() { local path="${1:-.}" local required_mb="${2:-100}" # Default 100MB if ! command_exists df; then log_warning "df command not available, skipping disk space check" return 0 fi local available_kb available_kb=$(df -k "$path" | awk 'NR==2 {print $4}') local available_mb=$((available_kb / 1024)) if [[ $available_mb -lt $required_mb ]]; then log_warning "Low disk space: ${available_mb}MB available, ${required_mb}MB recommended" return 1 fi return 0 } function auto_update_git_submodule(){ if git submodule status --recursive | grep -E '^[+-U]';then # Note: `git pull --recurse-submodules` cannot substitute `git submodule update --init --recursive` cuz it does not init a submodule when needed. x git submodule update --init --recursive fi } function backup_clashing_targets(){ # For non-recursive dirs/files under target_dir, only backup those which clashes with the ones under source_dir # However, ignore the ones listed in ignored_list # Deal with arguments local source_dir="$1" local target_dir="$2" local backup_dir="$3" local -a ignored_list=("${@:4}") # Find clash dirs/files, save as clash_list local clash_list=() local source_list=($(ls -A "$source_dir")) local target_list=($(ls -A "$target_dir")) local -A target_map for i in "${target_list[@]}"; do target_map["$i"]=1 done for i in "${source_list[@]}"; do if [[ -n "${target_map[$i]}" ]]; then clash_list+=("$i") fi done local -A delk for del in "${ignored_list[@]}" ; do delk[$del]=1 ; done for k in "${!clash_list[@]}" ; do [ "${delk[${clash_list[$k]}]-}" ] && unset 'clash_list[k]' done clash_list=("${clash_list[@]}") # Construct args_includes for rsync local args_includes=() for i in "${clash_list[@]}"; do if [[ -d "$target_dir/$i" ]]; then args_includes+=(--include="/$i/") args_includes+=(--include="/$i/**") else args_includes+=(--include="/$i") fi done args_includes+=(--exclude='*') x mkdir -p $backup_dir x rsync -av --progress "${args_includes[@]}" "$target_dir/" "$backup_dir/" } function install_cmds(){ case $OS_GROUP_ID in "arch") local pkgs=() for cmd in "$@";do # For package name which is not cmd name, use "case" syntax to replace case $cmd in ip) pkgs+=(iproute2);; *) pkgs+=($cmd) ;; esac done v sudo pacman -Syu v sudo pacman -S --noconfirm --needed "${pkgs[@]}" ;; "debian") local pkgs=() for cmd in "$@";do # For package name which is not cmd name, use "case" syntax to replace case $cmd in ip) pkgs+=(iproute2);; *) pkgs+=($cmd) ;; esac done v sudo apt update -y v sudo apt install -y "${pkgs[@]}" ;; "fedora") local pkgs=() for cmd in "$@";do # For package name which is not cmd name, use "case" syntax to replace case $cmd in ip) pkgs+=(iproute);; *) pkgs+=($cmd) ;; esac done v sudo dnf install -y "${pkgs[@]}" ;; "suse") local pkgs=() for cmd in "$@";do # For package name which is not cmd name, use "case" syntax to replace case $cmd in ip) pkgs+=(iproute2);; *) pkgs+=($cmd) ;; esac done v sudo zypper refresh v sudo zypper -n install "${pkgs[@]}" ;; *) printf "WARNING\n" printf "No method found to install package providing the commands:\n" printf " $@\n" printf "Please install by yourself.\n" ;; esac } function ensure_cmds(){ local not_found_cmds=() for cmd in "$@"; do if ! command -v $cmd >/dev/null 2>&1;then not_found_cmds+=($cmd) fi done if [[ ${#not_found_cmds[@]} -gt 0 ]]; then echo -e "${STY_YELLOW}[$0]: Not found: ${not_found_cmds[*]}.${STY_RST}" install_cmds "${not_found_cmds[@]}" fi } function dedup_and_sort_listfile(){ if ! test -f "$1"; then echo "File not found: $1" >&2; return 2 else temp="$(mktemp)" sort -u -- "$1" > "$temp" mv -f -- "$temp" "$2" fi } ================================================ FILE: sdata/lib/package-installers.sh ================================================ # This script depends on `functions.sh' . # This script is not for direct execution, instead it should be sourced by other script. It does not need execution permission or shebang. # shellcheck shell=bash # This file is provided for any distros, mainly non-Arch(based) distros. install-Rubik(){ x mkdir -p $REPO_ROOT/cache/Rubik x cd $REPO_ROOT/cache/Rubik try git init -b main try git remote add origin https://github.com/googlefonts/rubik.git x git pull origin main && git submodule update --init --recursive x sudo mkdir -p /usr/local/share/fonts/TTF/ x sudo cp fonts/variable/Rubik*.ttf /usr/local/share/fonts/TTF/ x sudo mkdir -p /usr/local/share/licenses/ttf-rubik/ x sudo cp OFL.txt /usr/local/share/licenses/ttf-rubik/LICENSE x fc-cache -fv x cd $REPO_ROOT } install-Gabarito(){ x mkdir -p $REPO_ROOT/cache/Gabarito x cd $REPO_ROOT/cache/Gabarito try git init -b main try git remote add origin https://github.com/naipefoundry/gabarito.git x git pull origin main && git submodule update --init --recursive x sudo mkdir -p /usr/local/share/fonts/TTF/ x sudo cp fonts/ttf/Gabarito*.ttf /usr/local/share/fonts/TTF/ x sudo mkdir -p /usr/local/share/licenses/ttf-gabarito/ x sudo cp OFL.txt /usr/local/share/licenses/ttf-gabarito/LICENSE x fc-cache -fv x cd $REPO_ROOT } install-bibata(){ x mkdir -p $REPO_ROOT/cache/bibata-cursor x cd $REPO_ROOT/cache/bibata-cursor name="Bibata-Modern-Classic" file="$name.tar.xz" try rm $file x curl -JLO https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file tar -xf $file x sudo mkdir -p /usr/local/share/icons x sudo cp -r $name /usr/local/share/icons x cd $REPO_ROOT } install-MicroTeX(){ x mkdir -p $REPO_ROOT/cache/MicroTeX x cd $REPO_ROOT/cache/MicroTeX try git init -b master try git remote add origin https://github.com/NanoMichael/MicroTeX.git x git pull origin master && git submodule update --init --recursive x mkdir -p build x cd build x cmake .. x make -j32 x sudo mkdir -p /opt/MicroTeX x sudo cp ./LaTeX /opt/MicroTeX/ x sudo cp -r ./res /opt/MicroTeX/ x cd $REPO_ROOT } install-uv(){ x bash <(curl -LJs "https://astral.sh/uv/install.sh") } install-python-packages(){ UV_NO_MODIFY_PATH=1 ILLOGICAL_IMPULSE_VIRTUAL_ENV=$XDG_STATE_HOME/quickshell/.venv x mkdir -p $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV) # we need python 3.12 https://github.com/python-pillow/Pillow/issues/8089 try uv venv --prompt .venv $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV) -p 3.12 x source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate if [[ "$INSTALL_VIA_NIX" = true ]]; then x nix-shell ${REPO_ROOT}/sdata/uv/shell.nix --run "uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt" else x uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt fi x deactivate } ================================================ FILE: sdata/subcmd-checkdeps/0.run.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash # Check whether pkgs exist in AUR or repos of Arch. # Do NOT abuse this since it consumes extra bandwidth from AUR server. pkglistfile=$(mktemp) pkglistfile_orig=${LIST_FILE_PATH} pkglistfile_orig_s=${REPO_ROOT}/cache/dependencies_stripped.conf for cmd in curl gzip pacman;do if ! command -v $cmd;then echo "Please install $cmd first.";exit 1 fi done remove_bashcomments_emptylines $pkglistfile_orig $pkglistfile_orig_s cat $pkglistfile_orig_s | sed "s_\ _\n_g" > $pkglistfile echo "The non-existent pkgs in $pkglistfile_orig are listed as follows." # Borrowed from https://bbs.archlinux.org/viewtopic.php?pid=1490795#p1490795 comm -23 <(sort -u $pkglistfile) <(sort -u <(curl https://aur.archlinux.org/packages.gz | gzip -cd | sort) <(pacman -Ssq)) echo "End of list. If nothing appears, then all pkgs exist." rm $pkglistfile ================================================ FILE: sdata/subcmd-checkdeps/options.sh ================================================ # Handle args for subcmd: checkdeps # shellcheck shell=bash showhelp(){ echo -e "Syntax: $0 checkdeps [OPTIONS] ... Check whether pkgs listed in exist in AUR or repos of Arch. Options: -h, --help Show this help message " } # `man getopt` to see more para=$(getopt \ -o h \ -l help \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 echo $para ##################################################################################### eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) shift;break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done if [[ -f "$1" ]]; then echo "Using list file \"$1\".";LIST_FILE_PATH="$1";shift 1 else echo "Wrong path \"$1\" of list file.";exit 1 fi ================================================ FILE: sdata/subcmd-exp-merge/0.run.sh ================================================ # shellcheck shell=bash set -euo pipefail MERGE_BRANCH="exp-merge-branch" BACKUP_DIR="${REPO_ROOT}/.exp-merge-backups" cleanup_on_exit() { local exit_code=$? if [[ $exit_code -ne 0 ]]; then echo log_warning "Script interrupted or failed" if git status 2>/dev/null | grep -q "rebase in progress"; then echo echo -e "${STY_YELLOW}Rebase is still in progress${STY_RST}" echo "Continue: git rebase --continue" echo "Abort: git rebase --abort" fi fi } trap cleanup_on_exit EXIT INT TERM check_preconditions() { log_header "Checking Preconditions" cd "$REPO_ROOT" || log_die "Failed to change to repository directory" if ! git rev-parse --is-inside-work-tree &>/dev/null; then log_die "Not in a git repository" fi if ! git diff --quiet || ! git diff --cached --quiet; then log_error "You have uncommitted changes in the repository:" git status --short log_die "Please commit or stash your changes before running exp-merge" fi if ! git remote get-url upstream &>/dev/null; then log_die "No remote 'upstream' configured" fi log_success "Precondition checks passed" } fetch_upstream() { if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would fetch from upstream" return fi if [[ "$SKIP_FETCH" == false ]]; then log_info "Fetching from upstream..." git fetch upstream || log_die "Failed to fetch from upstream" log_success "Fetched from upstream" else log_info "Skipping fetch (--skip-fetch flag set)" fi } update_main_with_upstream() { if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would update main from upstream" return fi log_info "Updating main with upstream..." git checkout main git merge --ff-only upstream/main || log_die "Main has diverged from upstream, cannot fast-forward" log_success "Main updated" } switch_to_merge_branch() { if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would switch to merge branch" return fi # check if branch exists if git show-ref --verify --quiet "refs/heads/${MERGE_BRANCH}"; then log_info "Switching to existing merge branch..." git checkout "${MERGE_BRANCH}" else log_info "Creating new merge branch from main..." git checkout -b "${MERGE_BRANCH}" fi log_success "On branch ${MERGE_BRANCH}" } copy_and_commit_user_config() { local user_quickshell="${HOME}/.config/quickshell" local repo_quickshell="${REPO_ROOT}/dots/.config/quickshell" if [[ ! -d "${user_quickshell}" ]]; then log_warning "Quickshell config not found at: ${user_quickshell}" log_info "Skipping" return 1 fi if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would copy and commit user config" return fi # chekc for rebase in progress if git status | grep -q "rebase in progress"; then log_error "Rebase already in progress, resolve it first" return 1 fi log_info "Copying user config..." rm -rf "${repo_quickshell}" cp -r "${user_quickshell}" "${repo_quickshell}" find "${repo_quickshell}" \( -name '.git' -o -name '.gitmodules' \) -exec rm -rf {} + 2>/dev/null || true git add . if git diff --cached --quiet; then log_info "No changes to commit" else git commit -m "user changes" log_success "Committed user changes" fi } rebase_onto_main() { if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would rebase onto main" return fi log_info "Rebasing onto main..." if git rebase main; then log_success "Rebase completed" else log_error "Rebase encountered conflicts" echo echo -e "${STY_YELLOW}Conflicted files:${STY_RST}" git diff --name-only --diff-filter=U echo echo -e "${STY_CYAN}To resolve:${STY_RST}" echo " 1. Edit conflicted files" echo " 2. git add " echo " 3. git rebase --continue" echo " 4. Run this script again" echo echo -e "${STY_CYAN}To abort:${STY_RST}" echo " git rebase --abort" echo return 1 fi } apply_quickshell_config() { log_header "Apply Quickshell Config" local user_quickshell="${HOME}/.config/quickshell" local repo_quickshell="${REPO_ROOT}/dots/.config/quickshell" local timestamp timestamp=$(date +%Y%m%d-%H%M%S) echo echo -e "${STY_CYAN}Your quickshell config has been merged with upstream.${STY_RST}" echo "What to do with merged config:" echo echo "1) Replace current with merged version" echo "2) Backup current, then replace" echo "3) Save merged as quickshell.new" echo "4) Skip" echo local choice read -p "Choice (1-4): " choice case $choice in 1) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would replace config" else rm -rf "${user_quickshell}" cp -r "${repo_quickshell}" "${user_quickshell}" log_success "Config replaced" fi ;; 2) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup and replace" else mkdir -p "${BACKUP_DIR}" local backup_name="quickshell.${timestamp}.bak" cp -r "${user_quickshell}" "${BACKUP_DIR}/${backup_name}" log_success "Backup: ${backup_name}" rm -rf "${user_quickshell}" cp -r "${repo_quickshell}" "${user_quickshell}" log_success "Config replaced" fi ;; 3) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would save as quickshell.new" else local new_config="${HOME}/.config/quickshell.new" rm -rf "${new_config}" cp -r "${repo_quickshell}" "${new_config}" log_success "Saved as quickshell.new" log_info "Current config unchanged" fi ;; 4) log_info "Skipped" ;; *) log_warning "Invalid choice" ;; esac } update_hypr_config() { log_header "Update Hyprland Config" local user_hypr="${HOME}/.config/hypr" local repo_hypr="${REPO_ROOT}/dots/.config/hypr" local timestamp timestamp=$(date +%Y%m%d-%H%M%S) if [[ ! -d "${user_hypr}" ]] || [[ ! -d "${repo_hypr}" ]]; then log_info "Hypr config not found, skipping" return fi echo echo -e "${STY_CYAN}Update hyprland config?${STY_RST}" echo -e "${STY_YELLOW}Note: /custom/ directory will be preserved${STY_RST}" echo echo "1) Update now" echo "2) Backup, then update" echo "3) Skip" echo local choice read -p "Choice (1-3): " choice case $choice in 1) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would update hypr" else local temp_custom="/tmp/hypr-custom-${timestamp}" [[ -d "${user_hypr}/custom" ]] && cp -r "${user_hypr}/custom" "${temp_custom}" rm -rf "${user_hypr}" cp -r "${repo_hypr}" "${user_hypr}" if [[ -d "${temp_custom}" ]]; then rm -rf "${user_hypr}/custom" cp -r "${temp_custom}" "${user_hypr}/custom" rm -rf "${temp_custom}" fi log_success "Hypr updated" fi ;; 2) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup and update" else mkdir -p "${BACKUP_DIR}" local backup_name="hypr.${timestamp}.bak" cp -r "${user_hypr}" "${BACKUP_DIR}/${backup_name}" log_success "Backup: ${backup_name}" local temp_custom="/tmp/hypr-custom-${timestamp}" [[ -d "${user_hypr}/custom" ]] && cp -r "${user_hypr}/custom" "${temp_custom}" rm -rf "${user_hypr}" cp -r "${repo_hypr}" "${user_hypr}" if [[ -d "${temp_custom}" ]]; then rm -rf "${user_hypr}/custom" cp -r "${temp_custom}" "${user_hypr}/custom" rm -rf "${temp_custom}" fi log_success "Hypr updated" fi ;; 3) log_info "Skipped" ;; *) log_warning "Invalid choice" ;; esac } log_header "Experimental Config Merge" if [[ "$SKIP_NOTICE" == false ]]; then log_warning "THIS SCRIPT IS EXPERIMENTAL, ONLY CONTINUE AT YOUR OWN RISK!" log_warning "It might be safer if you want to preserve your modifications and not delete added files," log_warning " but this can cause partial updates and therefore unexpected behavior." log_warning "In general, prefer \"./setup install\" for updates if available." read -p "Continue? (y/N): " response if [[ ! "$response" =~ ^[Yy]$ ]]; then log_error "Merge aborted by user" exit 1 fi fi check_preconditions fetch_upstream update_main_with_upstream log_header "Merging Quickshell Config" switch_to_merge_branch if copy_and_commit_user_config; then if rebase_onto_main; then apply_quickshell_config fi fi update_hypr_config # back to main if [[ "$DRY_RUN" != true ]]; then log_info "Switching back to main..." git checkout main fi log_header "Merge Complete" if [[ "$DRY_RUN" == true ]]; then log_warning "DRY-RUN: No changes made" else log_success "Done" fi [[ -d "${BACKUP_DIR}" ]] && log_info "Backups in: ${BACKUP_DIR}/" echo ================================================ FILE: sdata/subcmd-exp-merge/options.sh ================================================ # Handle args for subcmd: exp-merge # shellcheck shell=bash showhelp(){ echo -e "Syntax: $0 exp-merge [OPTIONS]... Experimental config merging using git rebase. Merges upstream changes with your quickshell config. Options: -n, --dry-run Show what would be done -h, --help Show this help -s, --skip-notice Skip notice about script being experimental --skip-fetch Skip fetching from remote How it works: 1. Fetch from upstream 2. Update main branch 3. Switch to exp-merge-branch (persistent) 4. Copy your ~/.config/quickshell and commit 5. Rebase onto main (3-way merge with history) 6. Prompt to apply merged config 7. Optionally update hypr config (preserves cstom folder) 8. Switch back to main " } para=$(getopt \ -o hns \ -l help,dry-run,skip-notice,skip-fetch \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) break ;; *) shift ;; esac done DRY_RUN=false SKIP_FETCH=false SKIP_NOTICE=false eval set -- "$para" while true ; do case "$1" in -n|--dry-run) DRY_RUN=true;shift log_info "Dry-run mode enabled - no changes will be made" ;; -s|--skip-notice) SKIP_NOTICE=true;shift log_warning "Skipping notice about script being experimental" ;; --skip-fetch) SKIP_FETCH=true;shift log_info "Skipping fetch from remote" ;; --) break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done ================================================ FILE: sdata/subcmd-exp-update/0.run.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash ##################################################################################### # Notes by @clsty: # # I'm not the one who developed this script (see issue#2284 which discussed about the history). # However it contains many unnecessary logics. This is typically what AI will do. # I don't really care if it's AI-generated or not, it's just an extra option in addition to ./setup install, so as long as the users say it works, it should be fine. # However, it's not easy to maintain something like this. # The redundant logic should be cleaned up someday. # # This also applies for exp-update.tester.sh, TBH I don't think that file is really needed, and it also looks like AI-generated. Just guessing though. ##################################################################################### # # exp-update.sh - Enhanced dotfiles update script # # Features: # - Auto-detect repository structure (dots/ prefix or direct config) # - Pull latest commits from remote # - Rebuild packages if PKGBUILD files changed (user choice) # - Handle config file conflicts with user choices # - Respect .updateignore file for exclusions with flexible pattern matching: # - Exact matches (e.g., "path/to/file") # - Directory patterns (e.g., "path/to/dir/") # - Wildcards (e.g., "*.log", "path/*/file") # - Root-relative patterns (e.g., "/.config") # - Substring matching (prefix with "**", e.g., "**temp" matches any path containing "temp") # set -euo pipefail # Note: The detect_repo_structure function below auto-detects the folder layout # Try to find the packages directory (different names in different versions) if which pacman &>/dev/null; then if [[ -d "${REPO_ROOT}/dist-arch" ]]; then ARCH_PACKAGES_DIR="${REPO_ROOT}/dist-arch" elif [[ -d "${REPO_ROOT}/arch-packages" ]]; then ARCH_PACKAGES_DIR="${REPO_ROOT}/arch-packages" elif [[ -d "${REPO_ROOT}/sdata/dist-arch" ]]; then ARCH_PACKAGES_DIR="${REPO_ROOT}/sdata/dist-arch" else ARCH_PACKAGES_DIR="${REPO_ROOT}/dist-arch" # Default fallback fi fi UPDATE_IGNORE_FILE="${REPO_ROOT}/.updateignore" XDG_UPDATE_IGNORE_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/illogical-impulse/updateignore" #TODO: remove in future and add script to migrate to XDG path HOME_UPDATE_IGNORE_FILE="${HOME}/.updateignore" # Legacy support # Global arrays for cached ignore patterns (performance optimization) declare -a IGNORE_PATTERNS=() declare -a IGNORE_SUBSTRING_PATTERNS=() # Track created directories to avoid redundant mkdir calls declare -A CREATED_DIRS # Auto-detect repository structure detect_repo_structure() { local found_dirs=() # Check for dots/ prefixed structure if [[ -d "${REPO_ROOT}/dots/.config" ]]; then found_dirs+=("dots/.config") [[ -d "${REPO_ROOT}/dots/.local/bin" ]] && found_dirs+=("dots/.local/bin") [[ -d "${REPO_ROOT}/dots/.local/share" ]] && found_dirs+=("dots/.local/share") # Check for flat structure elif [[ -d "${REPO_ROOT}/.config" ]]; then found_dirs+=(".config") [[ -d "${REPO_ROOT}/.local/bin" ]] && found_dirs+=(".local/bin") [[ -d "${REPO_ROOT}/.local/share" ]] && found_dirs+=(".local/share") else # Manual detection of common directories for candidate in "dots/.config" ".config" "dots/.local/bin" ".local/bin" "dots/.local/share" ".local/share"; do if [[ -d "${REPO_ROOT}/${candidate}" ]]; then # Avoid duplicates if [[ ! " ${found_dirs[*]} " =~ " ${candidate} " ]]; then found_dirs+=("${candidate}") fi fi done fi if [[ ${#found_dirs[@]} -eq 0 ]]; then echo "ERROR: Could not detect repository structure" >&2 return 1 fi echo "${found_dirs[@]}" } # Directories to monitor for changes (will be auto-detected) MONITOR_DIRS=() # Enhanced safe_read with better terminal handling safe_read() { local prompt="$1" local varname="$2" local default="${3:-}" local input_value="" # In non-interactive mode, use default immediately if [[ "$NON_INTERACTIVE" == true ]]; then if [[ -n "$default" ]]; then printf -v "$varname" '%s' "$default" return 0 else log_error "Non-interactive mode requires default value for: $prompt" return 1 fi fi echo -n "$prompt" # First, try reading from stdin (supports piped input like "yes 1 |") if read -r -t 0.1 input_value 2>/dev/null; then # Successfully read from stdin (piped input) if [[ -n "$input_value" ]]; then printf -v "$varname" '%s' "$input_value" return 0 fi fi # If stdin had no data, try interactive terminal if [[ -t 0 ]]; then # stdin is a terminal read -r input_value elif [[ -r /dev/tty ]]; then # Try reading from tty if read -r input_value /dev/null; then : # Success else input_value="" fi else # No interactive terminal available if [[ -n "$default" ]]; then echo log_warning "No terminal available. Using default: $default" printf -v "$varname" '%s' "$default" return 0 else echo log_error "No terminal available and no default provided" return 1 fi fi if [[ -n "$input_value" ]]; then printf -v "$varname" '%s' "$input_value" return 0 elif [[ -n "$default" ]]; then echo log_warning "Empty input. Using default: $default" printf -v "$varname" '%s' "$default" return 0 else echo log_error "Input required but not provided" return 1 fi } # Load and cache ignore patterns for performance load_ignore_patterns() { IGNORE_PATTERNS=() IGNORE_SUBSTRING_PATTERNS=() for ignore_file in "$UPDATE_IGNORE_FILE" "$XDG_UPDATE_IGNORE_FILE" "$HOME_UPDATE_IGNORE_FILE"; do [[ ! -f "$ignore_file" ]] && continue while IFS= read -r pattern || [[ -n "$pattern" ]]; do # Skip empty lines and comments [[ -z "$pattern" || "$pattern" =~ ^[[:space:]]*# ]] && continue # Remove whitespace pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [[ -z "$pattern" ]] && continue # Separate substring patterns from regular patterns if [[ "${pattern:0:2}" == "**" ]]; then local cleaned_pattern="${pattern#\*\*}" # Strip trailing asterisks while [[ "$cleaned_pattern" == *"*" ]] && [[ "${cleaned_pattern: -1}" == "*" ]]; do cleaned_pattern="${cleaned_pattern%\*}" done # Ensure we have a non-empty pattern if [[ -n "$cleaned_pattern" ]]; then IGNORE_SUBSTRING_PATTERNS+=("$cleaned_pattern") fi else IGNORE_PATTERNS+=("$pattern") fi done < "$ignore_file" done if [[ "$VERBOSE" == true ]]; then log_info "Loaded ${#IGNORE_PATTERNS[@]} ignore patterns and ${#IGNORE_SUBSTRING_PATTERNS[@]} substring patterns" fi } # Optimized should_ignore using cached patterns should_ignore() { local file_path="$1" local relative_path="${file_path#$HOME/}" local repo_relative="" if [[ "$file_path" == "$REPO_ROOT"* ]]; then repo_relative="${file_path#$REPO_ROOT/}" fi # Check regular patterns for pattern in "${IGNORE_PATTERNS[@]}"; do # Exact match if [[ "$relative_path" == "$pattern" ]] || [[ "$repo_relative" == "$pattern" ]]; then return 0 fi # Wildcard patterns (basic glob matching) if [[ "$relative_path" == $pattern ]] || [[ "$repo_relative" == $pattern ]]; then return 0 fi # Directory patterns (ending with /) if [[ "$pattern" == */ ]]; then local dir_pattern="${pattern%/}" if [[ "$relative_path" == "$dir_pattern"/* ]] || [[ "$repo_relative" == "$dir_pattern"/* ]]; then return 0 fi fi # Root-relative patterns (starting with /) if [[ "$pattern" == /* ]]; then local root_pattern="${pattern#/}" if [[ "$relative_path" == "$root_pattern" ]] || [[ "$relative_path" == "$root_pattern"/* ]] || [[ "$repo_relative" == "$root_pattern" ]] || [[ "$repo_relative" == "$root_pattern"/* ]]; then return 0 fi fi # Patterns with wildcards - check parent directories if [[ "$pattern" == *"*"* ]]; then local temp_path="$relative_path" while [[ "$temp_path" == */* ]]; do temp_path="${temp_path%/*}" if [[ "$temp_path" == $pattern ]]; then return 0 fi done fi done # Check substring patterns for substring in "${IGNORE_SUBSTRING_PATTERNS[@]}"; do if [[ -n "$substring" && ("$file_path" == *"$substring"* || "$relative_path" == *"$substring"*) ]]; then return 0 fi done return 1 } # Efficient directory creation with caching ensure_directory() { local dir="$1" # Check if already created in this run if [[ -n "${CREATED_DIRS[$dir]:-}" ]]; then return 0 fi if [[ "$DRY_RUN" != true ]]; then if [[ ! -d "$dir" ]]; then if mkdir -p "$dir" 2>/dev/null; then CREATED_DIRS[$dir]=1 if [[ "$VERBOSE" == true ]]; then log_info "Created directory: $dir" fi else log_error "Failed to create directory: $dir" return 1 fi else CREATED_DIRS[$dir]=1 fi else if [[ "$VERBOSE" == true ]] || [[ -z "${CREATED_DIRS[$dir]:-}" ]]; then log_info "[DRY-RUN] Would create directory: $dir" fi CREATED_DIRS[$dir]=1 fi return 0 } # Function to show file diff show_diff() { local file1="$1" local file2="$2" echo -e "\n${STY_CYAN}Showing differences:${STY_RST}" echo -e "${STY_CYAN}Old file: $file1${STY_RST}" echo -e "${STY_CYAN}New file: $file2${STY_RST}" echo "----------------------------------------" if command -v diff &>/dev/null; then diff -u "$file1" "$file2" || true else echo "diff command not available" fi echo "----------------------------------------" } # Backup file before replacing backup_file() { local file="$1" local backup_dir="${REPO_ROOT}/.update-backups" local timestamp timestamp=$(date +%Y%m%d-%H%M%S) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup: $file" return 0 fi if [[ ! -f "$file" ]]; then log_warning "File does not exist, cannot backup: $file" return 1 fi ensure_directory "$backup_dir" || return 1 local backup_name local relative_name="${file#$HOME/}" backup_name="${relative_name//\//_}.${timestamp}.bak" if cp -p "$file" "${backup_dir}/${backup_name}" 2>/dev/null; then log_info "Backed up to: .update-backups/${backup_name}" return 0 else log_error "Failed to create backup" return 1 fi } # Function to handle file conflicts handle_file_conflict() { local repo_file="$1" local home_file="$2" local filename=$(basename "$home_file") local dirname=$(dirname "$home_file") local choice="" local default_val="${DEFAULT_CHOICE:-6}" # Use DEFAULT_CHOICE or 6 (skip) as fallback # In non-interactive mode, use default directly (acts like pressing Enter) if [[ "$NON_INTERACTIVE" == true ]]; then choice="$default_val" log_info "Using choice $choice for: $home_file" else echo -e "\n${STY_YELLOW}Conflict detected:${STY_RST} $home_file" echo "Repository version differs from your local version." echo echo "Choose an action:" echo "1) Replace local file with repository version" echo "2) Keep local file unchanged" echo "3) Backup local file as ${filename}.old, use repository version" echo "4) Save repository version as ${filename}.new, keep local file" echo "5) Show diff and decide" echo "6) Skip this file" echo "7) Add to ignore and skip" echo "8) Backup to .update-backups/ and replace with repository version" echo while true; do if ! safe_read "Enter your choice (1-8 or name) [${default_val}]: " choice "$default_val"; then echo log_warning "Failed to read input. Skipping file." return fi # Validate choice if [[ "$choice" =~ ^[1-8]$ ]] || [[ "$choice" =~ ^(replace|keep|old|new|diff|skip|ignore|backup)$ ]]; then break else echo "Invalid choice. Please enter 1-8 or a valid name (replace, keep, old ...)." fi done fi case $choice in 1|replace) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would replace $home_file with repository version" else cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi ;; 2|keep) log_info "Keeping local version of $home_file" ;; 3|old) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup local file to ${filename}.old and update with repository version" else mv "$home_file" "${dirname}/${filename}.old" cp -p "$repo_file" "$home_file" log_success "Backed up local file to ${filename}.old and updated with repository version" fi ;; 4|new) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would save repository version as ${filename}.new, keep local file" else cp -p "$repo_file" "${dirname}/${filename}.new" log_success "Saved repository version as ${filename}.new, kept local file" fi ;; 5|diff) show_diff "$home_file" "$repo_file" echo echo "After reviewing the diff, choose:" echo "r) Replace with repository version" echo "k) Keep local version" echo "b) Backup local and use repository version" echo "n) Save repository version as .new" echo "s) Skip this file" echo "i) Add to ignore and skip" echo "B) Backup to .update-backups/ and replace" if ! safe_read "Enter your choice (r/k/b/n/s/i/B): " subchoice "s"; then echo log_warning "Failed to read input. Skipping file." return fi case $subchoice in r) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would replace $home_file with repository version" else cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi ;; k) log_info "Keeping local version of $home_file" ;; b) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would backup local file to ${filename}.old and update" else mv "$home_file" "${dirname}/${filename}.old" cp -p "$repo_file" "$home_file" log_success "Backed up local file to ${filename}.old and updated" fi ;; n) if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would save repository version as ${filename}.new" else cp -p "$repo_file" "${dirname}/${filename}.new" log_success "Saved repository version as ${filename}.new" fi ;; s) log_info "Skipping $home_file" ;; i) local relative_path_to_home="${home_file#$HOME/}" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" else echo "$relative_path_to_home" >>"$XDG_UPDATE_IGNORE_FILE" log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." fi ;; B) if backup_file "$home_file"; then if [[ "$DRY_RUN" != true ]]; then cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi fi ;; *) log_info "Skipping $home_file" ;; esac ;; 6|skip) log_info "Skipping $home_file" ;; 7|ignore) local relative_path_to_home="${home_file#$HOME/}" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE" else echo "$relative_path_to_home" >>"$XDG_UPDATE_IGNORE_FILE" log_success "Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped." fi ;; 8|backup) if backup_file "$home_file"; then if [[ "$DRY_RUN" != true ]]; then cp -p "$repo_file" "$home_file" log_success "Replaced $home_file with repository version" fi fi ;; esac } # Function to check if PKGBUILD has changed check_pkgbuild_changed() { local pkg_dir="$1" local pkgbuild_path="${pkg_dir}/PKGBUILD" [[ ! -f "$pkgbuild_path" ]] && return 1 local relative_path="${pkgbuild_path#$REPO_ROOT/}" if [[ "$FORCE_CHECK" == true ]]; then return 0 fi # Check if HEAD@{1} exists before trying to use it if ! git rev-parse --verify HEAD@{1} &>/dev/null; then # Fresh clone, assume all PKGBUILDs need checking return 0 fi if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q "^${relative_path}$"; then return 0 fi return 1 } # Function to list available packages list_packages() { local available_packages=() local changed_packages=() if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then log_warning "No package directory found" return 1 fi for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do if [[ -f "${pkg_dir}/PKGBUILD" ]]; then local pkg_name=$(basename "$pkg_dir") available_packages+=("$pkg_name") if check_pkgbuild_changed "$pkg_dir"; then changed_packages+=("$pkg_name") fi fi done if [[ ${#available_packages[@]} -eq 0 ]]; then log_info "No packages found in package directory" return 1 fi echo -e "\n${STY_CYAN}Available packages:${STY_RST}" for pkg in "${available_packages[@]}"; do if [[ " ${changed_packages[*]} " =~ " ${pkg} " ]]; then echo -e " ${STY_GREEN}● ${pkg}${STY_RST} (PKGBUILD changed)" else echo -e " ○ ${pkg}" fi done if [[ ${#changed_packages[@]} -gt 0 ]]; then echo -e "\n${STY_YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${STY_RST}" fi return 0 } # Function to build selected packages build_packages() { local build_mode="$1" local packages_to_build=() case "$build_mode" in "changed") for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do if [[ -f "${pkg_dir}/PKGBUILD" ]]; then local pkg_name=$(basename "$pkg_dir") if check_pkgbuild_changed "$pkg_dir"; then packages_to_build+=("$pkg_name") fi fi done ;; "all") for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do if [[ -f "${pkg_dir}/PKGBUILD" ]]; then local pkg_name=$(basename "$pkg_dir") packages_to_build+=("$pkg_name") fi done ;; "select") echo -e "\nEnter package names separated by spaces (or 'all' for all packages):" if ! safe_read "Packages to build: " user_selection ""; then log_warning "Failed to read input. Skipping package builds." return fi if [[ "$user_selection" == "all" ]]; then for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do if [[ -f "${pkg_dir}/PKGBUILD" ]]; then local pkg_name=$(basename "$pkg_dir") packages_to_build+=("$pkg_name") fi done else read -ra packages_to_build <<<"$user_selection" fi ;; esac if [[ ${#packages_to_build[@]} -eq 0 ]]; then log_info "No packages selected for building" return fi echo -e "\n${STY_CYAN}Packages to build: ${packages_to_build[*]}${STY_RST}" if ! safe_read "Proceed with building these packages? (Y/n): " confirm "Y"; then log_warning "Failed to read input. Skipping package builds." return fi if [[ "$confirm" =~ ^[Nn]$ ]]; then log_info "Package building cancelled by user" return fi for pkg_name in "${packages_to_build[@]}"; do pkg_dir="${ARCH_PACKAGES_DIR}/${pkg_name}" if [[ ! -d "$pkg_dir" || ! -f "${pkg_dir}/PKGBUILD" ]]; then log_error "Package not found or missing PKGBUILD: $pkg_name" continue fi log_info "Building package: $pkg_name" if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would build package in temp directory and clean up after" continue fi # Create temp build directory to avoid polluting the repo local build_tmp_dir build_tmp_dir=$(mktemp -d "/tmp/pkgbuild-${pkg_name}-XXXXXX") # Copy package files to temp directory (using /. to include hidden files) cp -r "$pkg_dir"/. "$build_tmp_dir/" || { log_error "Failed to copy package files to temp directory" rm -rf "$build_tmp_dir" continue } cd "$build_tmp_dir" || { log_error "Failed to change to temp build directory: $build_tmp_dir" rm -rf "$build_tmp_dir" continue } if makepkg -sCi --noconfirm; then log_success "Successfully built and installed $pkg_name" ((rebuilt_packages++)) || true else log_error "Failed to build package $pkg_name" fi # Clean up temp build directory cd "$REPO_ROOT" || log_die "Failed to return to repository directory" rm -rf "$build_tmp_dir" log_info "Cleaned up temp build directory" # Also clean any old build artifacts in the original package directory rm -rf "${pkg_dir}/src" "${pkg_dir}/pkg" "${pkg_dir}"/*.pkg.tar.* 2>/dev/null || true done if [[ $rebuilt_packages -eq 0 ]]; then log_warning "No packages were successfully built" else log_success "Successfully rebuilt $rebuilt_packages package(s)" fi } # Optimized function to get list of changed files get_changed_files() { local dir_path="$1" if [[ "$FORCE_CHECK" == true ]]; then find "$dir_path" -type f -print0 2>/dev/null return fi # Try git-based detection first if git rev-parse --verify HEAD@{1} &>/dev/null 2>&1; then local temp_file temp_file=$(mktemp) # Get changed files with specific filters (Added, Copied, Modified, Renamed) git diff --name-only --diff-filter=ACMR HEAD@{1} HEAD 2>/dev/null | \ while IFS= read -r file; do local full_path="${REPO_ROOT}/${file}" if [[ "$full_path" == "$dir_path"/* ]] && [[ -f "$full_path" ]]; then echo "$full_path" fi done > "$temp_file" if [[ -s "$temp_file" ]]; then # Found changes via git tr '\n' '\0' < "$temp_file" rm -f "$temp_file" return fi rm -f "$temp_file" fi # Fallback: check all files find "$dir_path" -type f -print0 2>/dev/null } # Function to check if we have new commits has_new_commits() { if git rev-parse --verify HEAD@{1} &>/dev/null; then [[ "$(git rev-parse HEAD)" != "$(git rev-parse HEAD@{1})" ]] else # Fresh clone or no reflog - assume we want to process files return 0 fi } # Cleanup function for signal handling cleanup_on_exit() { local exit_code=$? # Remove lock file rm -f "${REPO_ROOT}/.update-lock" 2>/dev/null || true if [[ $exit_code -ne 0 ]] && [[ "$DRY_RUN" != true ]]; then echo log_warning "Update interrupted or failed (exit code: $exit_code)" log_info "System may be in an inconsistent state" log_info "Run the update again to complete the process" fi } # Set up signal handling and lock file if [[ "${SOURCE_ONLY:-false}" != true ]]; then trap cleanup_on_exit EXIT INT TERM # Check for concurrent runs if [[ -f "${REPO_ROOT}/.update-lock" ]]; then # Check if the process is still running if kill -0 "$(cat "${REPO_ROOT}/.update-lock" 2>/dev/null)" 2>/dev/null; then log_die "Another update is already running (PID: $(cat "${REPO_ROOT}/.update-lock"))" else log_warning "Found stale lock file, removing..." rm -f "${REPO_ROOT}/.update-lock" fi fi # Create lock file with current PID if [[ "$DRY_RUN" != true ]]; then echo $$ > "${REPO_ROOT}/.update-lock" fi # Main script starts here log_header "Dotfiles Update Script" if [[ "$SKIP_NOTICE" == false ]]; then log_warning "THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!" log_warning "It might be safer if you want to preserve your modifications and not delete added files," log_warning " but this can cause partial updates and therefore unexpected behavior like in #1856." log_warning "In general, prefer \"./setup install\" for updates if available." safe_read "Continue? (y/N): " response "N" if [[ ! "$response" =~ ^[Yy]$ ]]; then log_error "Update aborted by user" exit 1 fi fi # Check if we're in a git repository cd "$REPO_ROOT" || log_die "Failed to change to repository directory" if git rev-parse --is-inside-work-tree &>/dev/null; then log_info "Running in git repository: $(git rev-parse --show-toplevel)" else log_error "Not in a git repository. Please run this script from your dotfiles repository." exit 1 fi # Auto-detect repository structure log_header "Detecting Repository Structure" if detected_dirs=$(detect_repo_structure); then read -ra MONITOR_DIRS <<<"$detected_dirs" log_success "Detected repository structure:" for dir in "${MONITOR_DIRS[@]}"; do if [[ -d "${REPO_ROOT}/${dir}" ]]; then log_info " ✓ ${REPO_ROOT}/${dir}" else log_warning " ✗ ${REPO_ROOT}/${dir} (not found, will skip)" fi done else log_die "Failed to detect repository structure. Make sure you're in the correct directory." fi # Load ignore patterns once at startup (performance optimization) load_ignore_patterns # Step 1: Pull latest commits log_header "Pulling Latest Changes" current_branch=$(git branch --show-current) if [[ -z "$current_branch" ]]; then log_warning "In detached HEAD state. Checking out main/master branch..." if git show-ref --verify --quiet refs/heads/main; then git checkout main current_branch="main" elif git show-ref --verify --quiet refs/heads/master; then git checkout master current_branch="master" else log_die "Could not find main or master branch" fi fi log_info "Current branch: $current_branch" if ! git diff --quiet || ! git diff --cached --quiet; then log_warning "You have uncommitted changes:" git status --short echo if ! safe_read "Do you want to continue? This will stash your changes. (y/N): " response "N"; then echo log_error "Failed to read input. Aborting." exit 1 fi if [[ ! "$response" =~ ^[Yy]$ ]]; then log_die "Aborted by user" fi if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would stash changes" else git stash push -m "Auto-stash before update $(date)" log_info "Changes stashed" fi fi if git remote get-url origin &>/dev/null; then log_info "Pulling changes from origin/$current_branch..." if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would run: git pull --ff-only" else if git pull --ff-only; then log_success "Successfully pulled latest changes" git submodule update --init --recursive # Verify we actually got new commits if git rev-parse --verify HEAD@{1} &>/dev/null; then if [[ "$(git rev-parse HEAD)" == "$(git rev-parse HEAD@{1})" ]]; then log_info "Already up to date with remote" fi fi else log_warning "Failed to pull changes from remote." log_warning "This could be due to:" log_warning " - Network issues" log_warning " - Uncommitted local changes (use 'git stash' first)" log_warning " - Diverged history (may need 'git pull --rebase')" log_info "Continuing with local repository state..." fi fi else log_warning "No remote 'origin' configured. Skipping pull operation." log_info "This appears to be a local-only repository." fi # Step 2: Handle package building rebuilt_packages=0 if [[ "$CHECK_PACKAGES" == true ]]; then log_header "Package Management" # Check if required Arch Linux tools are available if ! command -v pacman &>/dev/null || ! command -v makepkg &>/dev/null; then log_warning "Arch Linux package management tools (pacman/makepkg) not found." log_warning "Skipping package management as this appears to be a non-Arch Linux system." log_warning "Use -p/--packages flag only on Arch Linux systems." PKG_TOOLS_AVAILABLE=false else PKG_TOOLS_AVAILABLE=true fi if [[ "$PKG_TOOLS_AVAILABLE" == true ]]; then if [[ ! -d "$ARCH_PACKAGES_DIR" ]]; then log_warning "No packages directory found (tried: dist-arch, arch-packages, sdata/dist-arch)." log_warning "Skipping package management." else # Scan for changed PKGBUILDs changed_pkgbuilds=() for pkg_dir in "$ARCH_PACKAGES_DIR"/*/; do if [[ -f "${pkg_dir}/PKGBUILD" ]]; then pkg_name=$(basename "$pkg_dir") if check_pkgbuild_changed "$pkg_dir"; then changed_pkgbuilds+=("$pkg_name") fi fi done if [[ ${#changed_pkgbuilds[@]} -gt 0 ]]; then log_info "Found ${#changed_pkgbuilds[@]} package(s) with changed PKGBUILDs: ${changed_pkgbuilds[*]}" echo echo "Package build options:" echo "1) Build only packages with changed PKGBUILDs" echo "2) List all packages and select which to build" echo "3) Build all packages" echo "4) Skip package building" echo if [[ "$NON_INTERACTIVE" == true ]]; then pkg_choice="1" log_info "Non-interactive mode: Using default package option: $pkg_choice" elif safe_read "Choose an option (1-4): " pkg_choice "1"; then if [[ "$VERBOSE" == true ]]; then log_info "User selected package option: $pkg_choice" fi else log_warning "Failed to read input. Skipping package building." pkg_choice="" fi if [[ -n "$pkg_choice" ]]; then case $pkg_choice in 1) build_packages "changed" ;; 2) if list_packages; then build_packages "select" fi ;; 3) build_packages "all" ;; 4|*) log_info "Skipping package building" ;; esac fi else log_info "No PKGBUILDs have changed since last update." echo if [[ "$NON_INTERACTIVE" == true ]]; then check_anyway="N" log_info "Non-interactive mode: Using default for check packages anyway: $check_anyway" elif safe_read "Do you want to check and build packages anyway? (y/N): " check_anyway "N"; then if [[ "$VERBOSE" == true ]]; then log_info "User chose to check packages anyway: $check_anyway" fi else log_warning "Failed to read input. Skipping package management." check_anyway="" fi if [[ -n "$check_anyway" && "$check_anyway" =~ ^[Yy]$ ]]; then if list_packages; then echo echo "Package build options:" echo "1) Select specific packages to build" echo "2) Build all packages" echo "3) Skip package building" if safe_read "Choose an option (1-3): " build_choice "3"; then case $build_choice in 1) build_packages "select" ;; 2) build_packages "all" ;; 3|*) log_info "Skipping package building" ;; esac else log_info "Skipping package building" fi fi else log_info "Skipping package management" fi fi fi fi else log_header "Package Management" log_info "Package checking disabled. Use -p or --packages flag to enable package management." fi # Step 3: Update configuration files log_header "Updating Configuration Files" process_files=false if [[ "$FORCE_CHECK" == true ]]; then process_files=true log_info "Force mode: checking all configuration files" elif has_new_commits; then process_files=true log_info "New commits detected: checking changed configuration files" else log_info "No new commits found and force mode not enabled: skipping file updates" process_files=false fi if [[ "$process_files" == true ]]; then files_processed=0 files_updated=0 files_created=0 # Count total files for progress indication (optional) total_files=0 if [[ "$VERBOSE" == false ]] && command -v tput &>/dev/null 2>&1; then for dir_name in "${MONITOR_DIRS[@]}"; do repo_dir_path="${REPO_ROOT}/${dir_name}" [[ ! -d "$repo_dir_path" ]] && continue total_files=$((total_files + $(find "$repo_dir_path" -type f 2>/dev/null | wc -l))) done fi for dir_name in "${MONITOR_DIRS[@]}"; do repo_dir_path="${REPO_ROOT}/${dir_name}" if [[ ! -d "$repo_dir_path" ]]; then if [[ "$VERBOSE" == true ]]; then log_warning "Skipping non-existent directory: $repo_dir_path" fi continue fi # FIX: Properly handle dots/ prefix mapping if [[ "$dir_name" == dots/* ]]; then # Strip "dots/" prefix for home directory mapping home_subdir="${dir_name#dots/}" home_dir_path="${HOME}/${home_subdir}" else # Direct structure home_dir_path="${HOME}/${dir_name}" fi log_info "Processing directory: $dir_name → ${home_dir_path}" ensure_directory "$home_dir_path" || continue while IFS= read -r -d '' -u 9 repo_file; do # Calculate relative path from the repo source directory rel_path="${repo_file#$repo_dir_path/}" home_file="${home_dir_path}/${rel_path}" if should_ignore "$home_file"; then if [[ "$VERBOSE" == true ]]; then log_info "Ignored: $rel_path (matches ignore pattern)" fi continue fi if [[ "$VERBOSE" == true ]]; then log_info "Processing: $rel_path" fi ((files_processed++)) # Show progress for non-verbose mode if [[ "$VERBOSE" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then printf "\r[INFO] Processing files: %d/%d" "$files_processed" "$total_files" >&2 fi ensure_directory "$(dirname "$home_file")" || continue if [[ -f "$home_file" ]]; then if ! cmp -s "$repo_file" "$home_file"; then # Clear progress line if showing if [[ "$VERBOSE" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then printf "\r%*s\r" "80" "" >&2 fi log_info "Found difference in: $rel_path" if [[ "$DRY_RUN" == true ]]; then log_warning "[DRY-RUN] Conflict detected (would prompt): $home_file" ((files_updated++)) else handle_file_conflict "$repo_file" "$home_file" ((files_updated++)) fi fi else if [[ "$DRY_RUN" == true ]]; then if [[ "$VERBOSE" == true ]]; then log_info "[DRY-RUN] Would create new file: $home_file" fi else cp -p "$repo_file" "$home_file" if [[ "$VERBOSE" == true ]]; then log_success "Created new file: $home_file" fi fi ((files_created++)) fi done 9< <(get_changed_files "$repo_dir_path") || true echo done # Clear progress line if it was shown if [[ "$VERBOSE" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then printf "\r%*s\r" "80" "" >&2 fi echo log_info "File processing summary:" log_info "- Files processed: $files_processed" log_info "- Files with conflicts: $files_updated" log_info "- New files created: $files_created" else log_info "Skipping file updates (no changes detected and not in force mode)" fi # Step 4: Update script permissions log_header "Updating Script Permissions" if [[ -d "${HOME}/.local/bin" ]]; then if [[ "$DRY_RUN" == true ]]; then log_info "[DRY-RUN] Would update script permissions in ~/.local/bin" else find "${HOME}/.local/bin" -type f -exec chmod +x {} \; 2>/dev/null || true log_success "Updated ~/.local/bin script permissions" fi fi log_header "Update Complete" if [[ "$DRY_RUN" == true ]]; then log_warning "DRY-RUN MODE: No changes were actually made" log_info "Run without -n/--dry-run to apply changes" else log_success "Dotfiles update completed successfully!" fi echo echo -e "${STY_CYAN}Summary:${STY_RST}" if command -v git >/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then echo "- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)' 2>/dev/null || echo 'Unknown')" else echo "- Repository: Unknown (git not available)" fi echo "- Branch: ${current_branch:-Unknown}" echo "- Structure: ${MONITOR_DIRS[*]}" echo "- Mode: $([ "$FORCE_CHECK" == true ] && echo "Force check" || echo "Normal")" echo "- Package checking: $([ "$CHECK_PACKAGES" == true ] && echo "Enabled" || echo "Disabled")" if [[ $rebuilt_packages -gt 0 ]]; then echo "- Packages rebuilt: $rebuilt_packages" fi if [[ "$process_files" == true ]]; then echo "- Files processed: $files_processed" echo "- Files updated/conflicted: $files_updated" echo "- New files created: $files_created" fi if [[ ! -f "$XDG_UPDATE_IGNORE_FILE" && ! -f "$UPDATE_IGNORE_FILE" ]]; then echo log_info "Tip: Create ignore files to exclude files from updates:" echo " - Repository ignore: ${REPO_ROOT}/.updateignore" echo " - User ignore: ${XDG_UPDATE_IGNORE_FILE}" echo echo "Example patterns:" echo " *.log # Ignore all .log files" echo " .config/personal/ # Ignore entire directory" echo " secret-config.conf # Ignore specific file" echo " /temp-file # Ignore from root only" echo " **secret** # Ignore files containing 'secret'" fi # Show backup directory if any backups were created if [[ -d "${REPO_ROOT}/.update-backups" ]] && [[ "$DRY_RUN" != true ]]; then echo log_info "Backups stored in: ${REPO_ROOT}/.update-backups/" fi fi echo ================================================ FILE: sdata/subcmd-exp-update/exp-update-tester.sh ================================================ #!/usr/bin/env bash # # exp-update-tester.sh - Test suite for exp-update # set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' TESTS_PASSED=0 TESTS_FAILED=0 TEST_DIR="" ORIGINAL_DIR="$PWD" # Helper functions log_test() { echo -e "${BLUE}[TEST]${NC} $1" } log_pass() { echo -e "${GREEN}[PASS]${NC} $1" ((TESTS_PASSED++)) } log_fail() { echo -e "${RED}[FAIL]${NC} $1" ((TESTS_FAILED++)) } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Setup test environment setup_test_env() { local temp_dir temp_dir=$(mktemp -d -t dotfiles-test.XXXXXX) cd "$temp_dir" || { echo "Failed to cd to test directory"; return 1; } git init -q git config user.email "test@example.com" git config user.name "Test User" git commit --allow-empty -m "Initial commit" -q echo "$temp_dir" } # Cleanup test environment cleanup_test_env() { if [[ -n "${TEST_DIR:-}" && -d "$TEST_DIR" ]]; then rm -rf "$TEST_DIR" TEST_DIR="" fi } # Run a test and handle cleanup run_test() { local test_name="$1" local test_func="$2" # Cleanup before test cleanup_test_env # Run the test if $test_func; then echo "✓ $test_name passed" return 0 else echo "✗ $test_name failed" return 1 fi } # Test 2: Script has no syntax errors test_syntax() { log_test "Checking script syntax" if bash -n setup; then log_pass "No syntax errors found" return 0 else log_fail "Syntax errors detected" return 1 fi } # Test 3: Help option works test_help_option() { log_test "Testing --help option" if ./setup exp-update --help 2>&1 | grep -qiE "(Syntax|Options|exp-update)"; then log_pass "Help option works" return 0 else log_fail "Help option failed" return 1 fi } # Test 4: Test repository structure detection (dots/ prefix) test_dots_structure() { log_test "Testing dots/ prefix structure detection" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } mkdir -p dots/.config/test-app mkdir -p dots/.local/bin echo "test config" > dots/.config/test-app/config.conf git add . git commit -m "Add dots structure" -q cat > test_detection.sh << EOF #!/bin/bash # Mock logging and style functions/variables log_info() { :; } log_warning() { :; } log_error() { :; } log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1"; exit 1; } STY_CYAN="" STY_RST="" STY_YELLOW="" # Set required environment variables for exp-update/0.run.sh SKIP_NOTICE=true REPO_ROOT="\$1" CHECK_PACKAGES=false DRY_RUN=false FORCE_CHECK=false VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" detected_dirs=\$(detect_repo_structure) if [[ -n "\$detected_dirs" ]]; then read -ra MONITOR_DIRS <<<"\$detected_dirs" fi echo "Structure: \${MONITOR_DIRS[*]}" EOF chmod +x test_detection.sh result=$(./test_detection.sh "$test_repo") if [[ "$result" == *"dots/.config"* ]]; then log_pass "Dots structure detected correctly" cd "$ORIGINAL_DIR" return 0 else log_fail "Failed to detect dots structure. Got: $result" cd "$ORIGINAL_DIR" return 1 fi } # Test 5: Test flat structure detection test_flat_structure() { log_test "Testing flat structure detection" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } mkdir -p .config/test-app mkdir -p .local/bin echo "test config" > .config/test-app/config.conf git add . git commit -m "Add flat structure" -q cat > test_detection.sh << EOF #!/bin/bash # Mock logging and style functions/variables source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh" source "$ORIGINAL_DIR/sdata/lib/functions.sh" log_info() { :; } log_warning() { :; } log_error() { :; } log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1"; exit 1; } # Set required environment variables for exp-update SKIP_NOTICE=true REPO_ROOT="\$1" CHECK_PACKAGES=false DRY_RUN=false FORCE_CHECK=false VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" detected_dirs=\$(detect_repo_structure) if [[ -n "\$detected_dirs" ]]; then read -ra MONITOR_DIRS <<<"\$detected_dirs" fi echo "Structure: \${MONITOR_DIRS[*]}" EOF chmod +x test_detection.sh result=$(./test_detection.sh "$test_repo") if [[ "$result" == *".config"* ]] && [[ "$result" != *"dots/"* ]]; then log_pass "Flat structure detected correctly" cd "$ORIGINAL_DIR" return 0 else log_fail "Failed to detect flat structure. Got: $result" cd "$ORIGINAL_DIR" return 1 fi } # Test 6: Test dots prefix mapping to home directory test_dots_mapping() { log_test "Testing dots/ prefix home directory mapping" dir_name="dots/.config" if [[ "$dir_name" == dots/* ]]; then home_subdir="${dir_name#dots/}" home_dir_path="${HOME}/${home_subdir}" else home_dir_path="${HOME}/${dir_name}" fi expected_path="${HOME}/.config" if [[ "$home_dir_path" == "$expected_path" ]]; then log_pass "Dots prefix mapping correct: $dir_name → $home_dir_path" return 0 else log_fail "Dots prefix mapping failed: $dir_name → $home_dir_path (expected: $expected_path)" return 1 fi } # Test 7: Test ignore file patterns - FIXED test_ignore_patterns() { log_test "Testing ignore file pattern matching" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } cat > .updateignore << 'EOF' *.log secrets/ .config/private* *backup* EOF mkdir -p .config mkdir -p secrets cat > test_ignore.sh << EOF #!/bin/bash # Suppress all output from sourced script source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh" source "$ORIGINAL_DIR/sdata/lib/functions.sh" log_info() { :; } log_warning() { :; } log_error() { :; } log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1" >&2; exit 1; } # FIXED: Set REPO_ROOT before sourcing exp-update REPO_ROOT="\$1" export REPO_ROOT # Set other required environment variables SKIP_NOTICE=true CHECK_PACKAGES=false DRY_RUN=false FORCE_CHECK=false VERBOSE=false NON_INTERACTIVE=true UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore" HOME_UPDATE_IGNORE_FILE="/dev/null" # Source the production script to use the real should_ignore function # Redirect all unwanted output to stderr, then to /dev/null source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null test_cases=( "\$REPO_ROOT/app.log:0" "\$REPO_ROOT/secrets/key.txt:0" "\$REPO_ROOT/.config/private-config:0" "\$REPO_ROOT/.config/backup-file:0" "\$REPO_ROOT/normal-config:1" ) all_passed=true for test_case in "\${test_cases[@]}"; do IFS=":" read -r file expected <<< "\$test_case" mkdir -p "\$(dirname "\$file")" touch "\$file" if should_ignore "\$file"; then result=0 else result=1 fi if [[ \$result -ne \$expected ]]; then echo "FAIL: \$file (expected: \$expected, got: \$result)" all_passed=false fi done if [[ "\$all_passed" == true ]]; then echo "PASS" else echo "FAIL" fi EOF chmod +x test_ignore.sh result=$(./test_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)") if [[ "$result" == "PASS" ]]; then log_pass "All ignore pattern tests passed" cd "$ORIGINAL_DIR" return 0 else log_fail "Some ignore pattern tests failed" echo "$result" cd "$ORIGINAL_DIR" return 1 fi } # Test 8: Test safe_read security - FIXED test_safe_read_security() { log_test "Testing safe_read uses secure assignment (printf -v)" local safe_read_function safe_read_function=$(awk '/^safe_read\(\) \{/,/^\}/' "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh") if [[ -z "$safe_read_function" ]]; then log_fail "Could not find safe_read function" return 1 fi # FIXED: Remove comments before checking for eval # The function has a comment mentioning eval, which shouldn't count local function_without_comments function_without_comments=$(echo "$safe_read_function" | sed 's/#.*$//') local has_printf_v=false local has_eval=false if echo "$safe_read_function" | grep -F 'printf -v' > /dev/null; then has_printf_v=true fi # Check for eval in actual code (not comments) if echo "$function_without_comments" | grep -w 'eval' > /dev/null; then has_eval=true fi if [[ "$has_printf_v" == true ]] && [[ "$has_eval" == false ]]; then log_pass "safe_read uses secure printf -v assignment and no eval" return 0 else log_fail "safe_read does not use secure assignment or contains eval (has_printf_v=$has_printf_v, has_eval=$has_eval)" echo "Function content:" echo "$safe_read_function" return 1 fi } # Test 9: Test dry-run mode - FIXED test_dry_run() { log_test "Testing dry-run mode" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } # Copy necessary files for setup to run cp "$ORIGINAL_DIR/setup" . cp -r "$ORIGINAL_DIR/sdata" . cp -r "$ORIGINAL_DIR/dots" . chmod +x setup # Create a test config file in repo mkdir -p dots/.config/test-app echo "test config" > dots/.config/test-app/config.conf git add . git commit -m "Add test config" -q # FIXED: Clean up any existing test files before running test rm -rf "${HOME}/.config/test-app" 2>/dev/null || true # Use non-interactive mode and check for DRY-RUN marker ./setup exp-update -n --skip-notice --non-interactive 2>&1 | tee dry_run_output.txt if grep -q "DRY-RUN" dry_run_output.txt; then log_pass "Dry-run mode detected in output" else log_fail "Dry-run mode not properly indicated" cd "$ORIGINAL_DIR" return 1 fi # FIXED: Check if files were created (they shouldn't be in dry-run) if [[ -f "${HOME}/.config/test-app/config.conf" ]]; then log_fail "Files were created in home during dry-run" rm -rf "${HOME}/.config/test-app" cd "$ORIGINAL_DIR" return 1 else log_pass "No files created in home during dry-run" fi cd "$ORIGINAL_DIR" return 0 } # Test 10: Test command-line flags test_flags() { log_test "Testing command-line flags" # Only test non-interactive flags local flags=("-h" "--help") local all_passed=true for flag in "${flags[@]}"; do if ./setup exp-update "$flag" 2>&1 | grep -qiE "(Syntax|Options|exp-update)"; then log_test " ✓ $flag recognized" else log_test " ✗ $flag not recognized" all_passed=false fi done if [[ "$all_passed" == true ]]; then log_pass "Help flags recognized correctly" return 0 else log_fail "Some flags not recognized properly" return 1 fi } # Test 11: Check for shellcheck test_shellcheck() { log_test "Running shellcheck (if available)" if ! command -v shellcheck &>/dev/null; then log_test "shellcheck not found, skipping static analysis" return 0 fi if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 setup; then log_pass "shellcheck passed" return 0 else log_fail "shellcheck found issues" return 1 fi } # Test 12: Test lock file mechanism test_lock_file() { log_test "Testing lock file mechanism" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } # Copy necessary files cp "$ORIGINAL_DIR/setup" . cp -r "$ORIGINAL_DIR/sdata" . mkdir -p dots/.config chmod +x setup git add . git commit -m "Add files" -q # Create a fake lock file echo "99999" > .update-lock # Try to run update - should fail due to lock if ./setup exp-update --skip-notice --non-interactive > lock_test_output.txt 2>&1; then if grep -q "stale lock" lock_test_output.txt; then log_pass "Lock file mechanism works (detected stale lock)" cd "$ORIGINAL_DIR" return 0 fi fi log_fail "Lock file mechanism did not work as expected" cat lock_test_output.txt # Show output for debugging cd "$ORIGINAL_DIR" return 1 } # Test 13: Test ** substring ignore patterns - FIXED test_substring_ignore_patterns() { log_test "Testing ** substring ignore pattern matching" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } cat > .updateignore << 'EOF' **temp** **backup** **testfile** EOF mkdir -p .config/test-app mkdir -p temp-backup-dir mkdir -p .local/share/test-temp mkdir -p .config/temp-file cat > test_substring_ignore.sh << EOF #!/bin/bash # Suppress all output from sourced script source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh" source "$ORIGINAL_DIR/sdata/lib/functions.sh" log_info() { :; } log_warning() { :; } log_error() { :; } log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1" >&2; exit 1; } # FIXED: Set REPO_ROOT before sourcing exp-update REPO_ROOT="\$1" export REPO_ROOT # Set other required environment variables SKIP_NOTICE=true CHECK_PACKAGES=false DRY_RUN=false FORCE_CHECK=false VERBOSE=false NON_INTERACTIVE=true UPDATE_IGNORE_FILE="\${REPO_ROOT}/.updateignore" HOME_UPDATE_IGNORE_FILE="/dev/null" # Source the production script to use the real should_ignore function source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null # Load patterns into cache load_ignore_patterns test_cases=( "\$REPO_ROOT/temp-backup-dir/file:0" "\$REPO_ROOT/.config/test-app/temp.conf:0" "\$REPO_ROOT/.local/share/test-temp/data:0" "\$REPO_ROOT/.config/temp-file/config:0" "\$REPO_ROOT/normal-config:1" "\$REPO_ROOT/.config/my-testfile.conf:0" ) all_passed=true for test_case in "\${test_cases[@]}"; do IFS=":" read -r file expected <<< "\$test_case" mkdir -p "\$(dirname "\$file")" touch "\$file" if should_ignore "\$file"; then result=0 else result=1 fi if [[ \$result -ne \$expected ]]; then echo "FAIL: \$file (expected: \$expected, got: \$result)" all_passed=false fi done if [[ "\$all_passed" == true ]]; then echo "PASS" else echo "FAIL" fi EOF chmod +x test_substring_ignore.sh result=$(./test_substring_ignore.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)") if [[ "$result" == "PASS" ]]; then log_pass "** substring ignore patterns work correctly" cd "$ORIGINAL_DIR" return 0 else log_fail "** substring ignore patterns failed" echo "$result" cd "$ORIGINAL_DIR" return 1 fi } # Test 14: Test ensure_directory caching test_directory_caching() { log_test "Testing directory creation caching" local test_repo test_repo=$(setup_test_env) TEST_DIR="$test_repo" cd "$test_repo" || { log_fail "Failed to cd to test directory"; return 1; } cat > test_dir_cache.sh << EOF #!/bin/bash source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh" source "$ORIGINAL_DIR/sdata/lib/functions.sh" log_info() { :; } log_warning() { :; } log_error() { :; } log_success() { :; } log_header() { :; } log_die() { echo "ERROR: \$1" >&2; exit 1; } REPO_ROOT="\$1" export REPO_ROOT SKIP_NOTICE=true CHECK_PACKAGES=false DRY_RUN=false FORCE_CHECK=false VERBOSE=false NON_INTERACTIVE=true SOURCE_ONLY=true source "$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh" 2>/dev/null test_dir="/tmp/test-ensure-dir-\$\$" # First call should create ensure_directory "\$test_dir" result1=\$? # Second call should use cache ensure_directory "\$test_dir" result2=\$? # Check if CREATED_DIRS has the entry if [[ -n "\${CREATED_DIRS[\$test_dir]:-}" ]] && [[ \$result1 -eq 0 ]] && [[ \$result2 -eq 0 ]]; then echo "PASS" rm -rf "\$test_dir" else echo "FAIL" fi EOF chmod +x test_dir_cache.sh result=$(./test_dir_cache.sh "$test_repo" 2>&1 | grep -E "^(PASS|FAIL)") if [[ "$result" == "PASS" ]]; then log_pass "Directory creation caching works" cd "$ORIGINAL_DIR" return 0 else log_fail "Directory creation caching failed" cd "$ORIGINAL_DIR" return 1 fi } # Test 15: Test enhanced safe_read with non-interactive mode test_safe_read_noninteractive() { log_test "Testing safe_read in non-interactive mode" cat > test_safe_read.sh << 'EOF' #!/bin/bash source "$ORIGINAL_DIR/sdata/lib/environment-variables.sh" source "$ORIGINAL_DIR/sdata/lib/functions.sh" log_warning() { :; } log_error() { :; } # Simulate the enhanced safe_read function safe_read() { local prompt="$1" local varname="$2" local default="${3:-}" local input_value="" # In non-interactive mode, use default immediately if [[ "$NON_INTERACTIVE" == true ]]; then if [[ -n "$default" ]]; then printf -v "$varname" '%s' "$default" return 0 else log_error "Non-interactive mode requires default value for: $prompt" return 1 fi fi # Regular read logic... printf -v "$varname" '%s' "$default" return 0 } # Test 1: With default in non-interactive mode NON_INTERACTIVE=true if safe_read "Test: " result "default_value"; then if [[ "$result" == "default_value" ]]; then echo "TEST1: PASS" else echo "TEST1: FAIL - got '$result'" fi else echo "TEST1: FAIL - returned error" fi # Test 2: Without default in non-interactive mode (should fail) if safe_read "Test: " result ""; then echo "TEST2: FAIL - should have failed" else echo "TEST2: PASS - correctly failed" fi EOF chmod +x test_safe_read.sh result=$(./test_safe_read.sh 2>&1) if echo "$result" | grep -q "TEST1: PASS" && echo "$result" | grep -q "TEST2: PASS"; then log_pass "Enhanced safe_read handles non-interactive mode correctly" rm -f test_safe_read.sh return 0 else log_fail "Enhanced safe_read non-interactive mode failed" echo "$result" rm -f test_safe_read.sh return 1 fi } # Main test runner main() { echo -e "${BLUE}================================${NC}" echo -e "${BLUE} Update.sh Test Suite (Enhanced)${NC}" echo -e "${BLUE}================================${NC}\n" if [[ ! -f "setup" ]]; then log_error "Please run this test from the directory containing setup" exit 1 fi chmod +x setup 2>/dev/null || true # Define tests tests=( "test_syntax" "test_help_option" "test_dots_structure" "test_flat_structure" "test_dots_mapping" "test_ignore_patterns" "test_substring_ignore_patterns" "test_safe_read_security" "test_dry_run" "test_flags" "test_shellcheck" "test_lock_file" "test_directory_caching" "test_safe_read_noninteractive" ) # Run tests for test in "${tests[@]}"; do if $test; then echo "✓ $test passed" else echo "✗ $test failed" fi echo done # Summary echo -e "${BLUE}================================${NC}" echo -e "${BLUE} Test Summary${NC}" echo -e "${BLUE}================================${NC}" echo -e "${GREEN}Passed: $TESTS_PASSED${NC}" echo -e "${RED}Failed: $TESTS_FAILED${NC}" echo -e "${BLUE}Total: ${#tests[@]}${NC}\n" if [[ $TESTS_FAILED -eq 0 ]]; then echo -e "${GREEN}All tests passed! 🎉${NC}\n" exit 0 else echo -e "${RED}Some tests failed! ❌${NC}\n" exit 1 fi } # Global cleanup cleanup() { echo "Cleaning up test files..." cleanup_test_env rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh test_substring_ignore.sh dry_run_output.txt 2>/dev/null || true rm -f test_caching.sh test_dir_cache.sh 2>/dev/null || true rm -f lock_test_output.txt 2>/dev/null || true rm -rf "${HOME}/.config/test-app" 2>/dev/null || true } trap cleanup EXIT INT TERM if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi ================================================ FILE: sdata/subcmd-exp-update/options.sh ================================================ # Handle args for subcmd: exp-update # shellcheck shell=bash showhelp(){ echo -e "Syntax: $0 exp-update [OPTIONS]... Experimental updating without full reinstall. Updates dotfiles by syncing configuration files to home directory. Options: -f, --force Force check all files even if no new commits -p, --packages Enable package checking and building -n, --dry-run Show what would be done without making changes -v, --verbose Enable verbose output -h, --help Show this help message -s, --skip-notice Skip notice about script being untested --non-interactive Set default choice for file conflicts replace: Replace local keep: Keep local old: Backup as .old new: Save as .new diff: Show diff skip: Skip ignore: Add to ignore backup: Backup and replace This script updates your dotfiles by: 1. Auto-detecting repository structure (dots/ prefix or direct) 2. Pulling latest changes from git remote 3. Optionally rebuilding packages (if -p flag is used) 4. Syncing configuration files to home directory 5. Updating script permissions Ignore file patterns support: - Exact matches (e.g., 'path/to/file') - Directory patterns (e.g., 'path/to/dir/') - Wildcards (e.g., '*.log', 'path/*/file') - Root-relative patterns (e.g., '/.config') - Substring matching (prefix with '**', e.g., '**temp' matches any path containing 'temp') " } # `man getopt` to see more para=$(getopt \ -o hfpnvs \ -l help,force,packages,dry-run,verbose,skip-notice,non-interactive,default-choice: \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### ## getopt Phase 1 # ignore parameter's order, execute options below first eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) break ;; *) shift ;; esac done ##################################################################################### ## getopt Phase 2 FORCE_CHECK=false CHECK_PACKAGES=false DRY_RUN=false VERBOSE=false SKIP_NOTICE=false NON_INTERACTIVE=false DEFAULT_CHOICE="" eval set -- "$para" while true ; do case "$1" in ## Ones without parameter -f|--force) FORCE_CHECK=true;shift log_info "Force check mode enabled - will check all files regardless of git changes" ;; -p|--packages) CHECK_PACKAGES=true;shift log_info "Package checking enabled" ;; -n|--dry-run) DRY_RUN=true;shift log_info "Dry-run mode enabled - no changes will be made" ;; -v|--verbose) VERBOSE=true;shift log_info "Verbose mode enabled" ;; -s|--skip-notice) SKIP_NOTICE=true;shift log_warning "Skipping notice about script being untested" ;; --non-interactive) NON_INTERACTIVE=true;shift log_info "Non-interactive mode enabled" ;; --default-choice) case "$2" in replace) DEFAULT_CHOICE="1" ;; keep) DEFAULT_CHOICE="2" ;; old) DEFAULT_CHOICE="3" ;; new) DEFAULT_CHOICE="4" ;; diff) DEFAULT_CHOICE="5" ;; skip) DEFAULT_CHOICE="6" ;; ignore) DEFAULT_CHOICE="7" ;; backup) DEFAULT_CHOICE="8" ;; *) log_error "Invalid --default-choice value: $2" log_error "Valid values: replace, keep, old, new, diff, skip, ignore, backup" exit 1 ;; esac shift 2 log_info "Default conflict choice set to: $DEFAULT_CHOICE" ;; ## Ending --) break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done ================================================ FILE: sdata/subcmd-install/0.greeting.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash ##################################################################################### printf "${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\n" printf "\n" printf "${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\n" printf "${STY_PURPLE}" printf '# NOTE: illogical-impulse on AGS is no longer supported.\n' printf '# If you were using the old version with AGS and would like to keep it, do not run this script.\n' printf "\n" pause printf "${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\n" printf "${STY_CYAN}" printf " 1. Install dependencies.\n" printf " 2. Setup permissions/services etc.\n" printf " 3. Copying config files.${STY_RST}\n" pause printf "${STY_CYAN}${STY_BOLD}Tips:${STY_RST}\n" printf "${STY_CYAN}" printf " a) It has been designed to be idempotent which means you can run it multiple times.\n" printf " b) Use ${STY_INVERT} --help ${STY_RST}${STY_CYAN} for more options.${STY_RST}\n" printf "${STY_YELLOW}${STY_BOLD}Note: ${STY_RST}" printf "${STY_YELLOW}" printf "It does not handle system-level/hardware stuff like Nvidia drivers. Please do it by yourself.\n" printf "${STY_RST}" printf "\n" pause case $ask in false) sleep 0 ;; *) printf "${STY_BLUE}" printf "${STY_BOLD}Do you want to confirm every time before a command executes?${STY_RST}\n" printf "${STY_BLUE}" printf " y = Yes, ask me before executing each of them. (DEFAULT)\n" printf " n = No, I know everything this script will do, just execute them automatically.\n" printf " a = Abort.\n" read -p "===> [Y/n/a]: " p case $p in n) ask=false ;; a) exit 1 ;; *) ask=true ;; esac printf "${STY_RST}" ;; esac ================================================ FILE: sdata/subcmd-install/1.deps-router.sh ================================================ # This script is meant to be sourced. # It's not for directly running. printf "${STY_CYAN}[$0]: 1. Install dependencies\n${STY_RST}" function outdate_detect(){ # Shallow clone prevent latest_commit_timestamp() from working. x git_auto_unshallow 2>&1>/dev/null local source_path="$1" local target_path="$2" local source_timestamp="$(latest_commit_timestamp $source_path 2>/dev/null)" local target_timestamp="$(latest_commit_timestamp $target_path 2>/dev/null)" local outdate_detect_mode="$(cat ${target_path}/outdate-detect-mode)" # outdate-detect-mode possible modes: # - WIP: Work in progress (should be taken as outdated) # - FORCE_OUTDATED: forcely taken as outdated # - FORCE_UPDATED: forcely taken as updated # - AUTO: Let the script decide automatically # # outdate status possible values: # - WIP,FORCE_OUTDATED,FORCE_UPDATED: Inherited directly from outdate-detect-mode # - EMPTY_SOURCE: source path has empty timestamp, maybe not tracked by git (should be taken as outdated) # - EMPTY_TARGET: target path has empty timestamp, maybe not tracked by git (should be taken as outdated) # - OUTDATED: target path is older than source path. # - UPDATED: target path is not older than source path. # Does target path have an outdate-detect-mode file which content is special? if [[ "${outdate_detect_mode}" =~ ^(WIP|FORCE_OUTDATED|FORCE_UPDATED)$ ]]; then echo "${outdate_detect_mode}" # Does source path has an empty timestamp? elif [ -z "$source_timestamp" ]; then echo "EMPTY_SOURCE" # Does target path has an empty timestamp? elif [ -z "$target_timestamp" ]; then echo "EMPTY_TARGET" # If target path is older than source path, it's outdated. elif [[ "$target_timestamp" -lt "$source_timestamp" ]]; then echo "OUTDATED" else echo "UPDATED" fi } ##################################################################################### if [[ "$INSTALL_VIA_NIX" == "true" ]]; then TARGET_ID=nix printf "${STY_YELLOW}" printf "===WARNING===\n" printf "./sdata/dist-${TARGET_ID}/install-deps.sh will be used.\n" printf "The process is still WIP.\n" printf "Proceed only at your own risk.\n" printf "\n" printf "${STY_RST}" pause source ./sdata/dist-${TARGET_ID}/install-deps.sh elif [[ "$OS_GROUP_ID" =~ ^(arch|gentoo|fedora)$ ]]; then TARGET_ID=$OS_GROUP_ID if ! [[ "${TARGET_ID}" = "arch" ]]; then tmp_update_status="$(outdate_detect sdata/dist-arch sdata/dist-${TARGET_ID})" if [[ "${tmp_update_status}" =~ ^(OUTDATED|EMPTY_TARGET|EMPTY_SOURCE|FORCE_OUTDATED|WIP)$ ]]; then printf "${STY_RED}${STY_BOLD}===URGENT===${STY_RST}\n" printf "${STY_RED}" printf "Status code: ${tmp_update_status}\n" printf "The community provided ./sdata/dist-${TARGET_ID}/ seems to be outdated,\n" printf "which means it probably does not reflect all latest changes of ./sdata/dist-arch/ .\n" printf "In such case it may work unexpectedly.${STY_RST}\n" printf "\n" printf "${STY_RED}It's highly recommended to check the following links before continue.${STY_RST}\n" printf "${STY_RED}1. Normally just check discussion#2140 to see if there's any valid update notice.${STY_RST}\n" printf " ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/discussions/2140${STY_RST}\n" printf " ${STY_RED}Note that the timeliness relies on manual maintenance.${STY_RST}\n" printf "${STY_RED}2. For details please compare the two lists of commit history:${STY_RST}\n" printf " ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-arch${STY_RST}\n" printf " ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-${TARGET_ID}${STY_RST}\n" printf "\n" printf "${STY_PURPLE}PR on ./sdata/dist-${TARGET_ID}/ to properly reflect the latest changes of ./sdata/dist-arch is welcomed.${STY_RST}\n" printf "${STY_PURPLE}${STY_BOLD}Again, do not create any issue,${STY_RST}\n" printf "${STY_PURPLE}but you can create a discussion under \"Extra Distros\" category: ${STY_RST}\n" printf "${STY_PURPLE}${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/discussions/new?category=extra-distros${STY_RST}\n" printf "\n" if [[ "${tmp_update_status}" = "OUTDATED" ]]; then printf "${STY_RED}NOTE: The conclusion above is determined automatically by comparing latest Git commit time,\n" printf "however sometimes the changes on \"dist-arch\" are actually not needed for \"dist-${TARGET_ID}\",\n" printf "in such case you should just ignore it and continue.\n" printf "${STY_RST}\n" fi printf "\n" if ! [[ "$IGNORE_OUTDATE_CHECK" = "true" ]]; then if [ "$ask" = "false" ]; then printf "${STY_RED}Urgent problem encountered, aborting...${STY_RST}\n";exit 1 else printf "${STY_RED}Still proceed?${STY_RST}\n" read -p "[y/N]: " p case "$p" in [yY])sleep 0;; *)echo "Aborting...";exit 1;; esac fi fi fi fi printf "./sdata/dist-${TARGET_ID}/install-deps.sh will be used.\n" source ./sdata/dist-${TARGET_ID}/install-deps.sh fi ================================================ FILE: sdata/subcmd-install/2.setups.sh ================================================ # This script is meant to be sourced. # It's not for directly running. function prepare_systemd_user_service(){ if [[ ! -e "/usr/lib/systemd/user/ydotool.service" ]]; then x sudo ln -s /usr/lib/systemd/{system,user}/ydotool.service fi } function setup_user_group(){ if [[ -z $(getent group i2c) ]] && [[ "$OS_GROUP_ID" != "fedora" ]]; then # On Fedora this is not needed. Tested with desktop computer with NVIDIA video card. x sudo groupadd i2c fi if [[ "$OS_GROUP_ID" == "fedora" ]]; then x sudo usermod -aG video,input "$(whoami)" else x sudo usermod -aG video,i2c,input "$(whoami)" fi } ##################################################################################### # These python packages are installed using uv into the venv (virtual environment). Once the folder of the venv gets deleted, they are all gone cleanly. So it's considered as setups, not dependencies. showfun install-python-packages v install-python-packages showfun setup_user_group v setup_user_group if [[ ! -z $(systemctl --version) ]]; then # For Fedora, uinput is required for the virtual keyboard to function, and udev rules enable input group users to utilize it. if [[ "$OS_GROUP_ID" == "fedora" ]]; then v bash -c "echo uinput | sudo tee /etc/modules-load.d/uinput.conf" v bash -c 'echo SUBSYSTEM==\"misc\", KERNEL==\"uinput\", MODE=\"0660\", GROUP=\"input\" | sudo tee /etc/udev/rules.d/99-uinput.rules' else v bash -c "echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf" fi # TODO: find a proper way for enable Nix installed ydotool. When running `systemctl --user enable ydotool, it errors "Failed to enable unit: Unit ydotool.service does not exist". if [[ ! "${INSTALL_VIA_NIX}" == true ]]; then if [[ "$OS_GROUP_ID" == "fedora" ]]; then v prepare_systemd_user_service fi # When $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR are empty, it commonly means that the current user has been logged in with `su - user` or `ssh user@hostname`. In such case `systemctl --user enable ` is not usable. It should be `sudo systemctl --machine=$(whoami)@.host --user enable ` instead. if [[ ! -z "${DBUS_SESSION_BUS_ADDRESS}" ]]; then v systemctl --user enable ydotool --now else v sudo systemctl --machine=$(whoami)@.host --user enable ydotool --now fi fi v sudo systemctl enable bluetooth --now elif [[ ! -z $(openrc --version) ]]; then v bash -c "echo 'modules=i2c-dev' | sudo tee -a /etc/conf.d/modules" v sudo rc-update add modules boot v sudo rc-update add ydotool default v sudo rc-update add bluetooth default x sudo rc-service ydotool start x sudo rc-service bluetooth start else printf "${STY_RED}" printf "====================INIT SYSTEM NOT FOUND====================\n" printf "${STY_RST}" pause fi if [[ "$OS_GROUP_ID" == "gentoo" ]]; then v sudo chown -R $(whoami):$(whoami) ~/.local/ fi v gsettings set org.gnome.desktop.interface font-name 'Google Sans Flex Medium 11 @opsz=11,wght=500' v gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' v kwriteconfig6 --file kdeglobals --group KDE --key widgetStyle Darkly ================================================ FILE: sdata/subcmd-install/3.files-exp.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # See https://github.com/end-4/dots-hyprland/issues/2137 # # Stage 1 todos: # TODO: Properly handle hyprland config, ~/.config/hypr/hyprland.conf should be overwritten only when firstrun # TODO: add --exp-files-path Use instead of the default yaml config # TODO: add --exp-files-regen Force copy the default config to ${EXP_FILE_PATH} (auto do this when not existed) # TODO: Implement versioning, i.e. when user-defined yaml config file has version number mismatch with the default one, produce error. If only minor version number is not the same, the error can be ommitted via --exp-file-no-strict . # TODO: add --exp-files-no-strict Ignore error when minor version number is not the same # TODO: When --via-nix is specified, use dots-extra/vianix/hypridle.conf instead # # Stage 2 todos: # TODO: Implement bool key symlink (both read-write and read-only), when the value of `symlink` is true, then instead using `rsync` or `cp`, use `ln`. # TODO: add --exp-file-reset-symlink Try to remove all symlink in .config and .local, which point to the local repo # TODO: Update help and doc about `--exp-files` and the yaml config, including the possible values of mode. # # Stage 3 todos: # TODO: Implement user-define yaml with merging (override) ability for user who only wants little customization and is satisfied with most of the defaults. User can use `./install-files.yaml` as custom config. When `./install-files.yaml` exists and have correct major version number, merge it together with `sdata/step/3.install-files.yaml` to generate a `cache/install-files.final.yaml` to determine how to copy files. About how to merge two yaml files, I know some software such as rime input method and docker supports a override yaml config, which we may reference from. See also https://github.com/mikefarah/yq/discussions/1437 # TODO: Implement variants like keybindings, terminals, etc under user_preferences. # Configuration file CONFIG_FILE="sdata/subcmd-install/3.files-exp.yaml" # ============================================================================= wizard_update_preferences() { echo -e "${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}" # Get current preferences current_shell=$(yq '.user_preferences.shell // "fish"' "$CONFIG_FILE") current_terminal=$(yq '.user_preferences.terminal // "kitty"' "$CONFIG_FILE") current_keybindings=$(yq '.user_preferences.keybindings // "default"' "$CONFIG_FILE") echo "Current preferences:" echo " Shell: $current_shell" echo " Terminal: $current_terminal" echo " Keybindings: $current_keybindings" echo # Shell selection echo "Which shell do you prefer?" echo "1) fish (default)" echo "2) zsh" read -p "Enter choice [1-2]: " shell_choice case "$shell_choice" in 1|"") shell="fish" ;; 2) shell="zsh" ;; *) echo "Invalid choice, using fish"; shell="fish" ;; esac # Terminal selection echo echo "Which terminal do you prefer?" echo "1) kitty (default)" echo "2) foot" read -p "Enter choice [1-2]: " terminal_choice case "$terminal_choice" in 1|"") terminal="kitty" ;; 2) terminal="foot" ;; *) echo "Invalid choice, using kitty"; terminal="kitty" ;; esac # Keybindings selection echo echo "Which keybinding style do you prefer?" echo "1) default (arrow keys)" echo "2) vim (H/J/K/L)" read -p "Enter choice [1-2]: " keybind_choice case "$keybind_choice" in 1|"") keybindings="default" ;; 2) keybindings="vim" ;; *) echo "Invalid choice, using default"; keybindings="default" ;; esac # Update YAML in-place yq -i ".user_preferences.shell = \"$shell\"" "$CONFIG_FILE" yq -i ".user_preferences.terminal = \"$terminal\"" "$CONFIG_FILE" yq -i ".user_preferences.keybindings = \"$keybindings\"" "$CONFIG_FILE" echo echo "Preferences updated!" } # Get user preference get_pref() { yq -r ".user_preferences.$1" "$CONFIG_FILE" } # Check if pattern should be processed based on user preferences should_process_pattern() { local pattern="$1" local condition=$(echo "$pattern" | yq '.condition // "true"') # If no condition or condition is "true", always process if [[ "$condition" == "true" ]]; then return 0 fi # Extract the preference type and value from condition local type=$(echo "$condition" | yq '.type') local value=$(echo "$condition" | yq '.value') [[ "$(get_pref "$type")" == "$value" ]] } # Compare hashes of files/directories, return true if they are the same, false otherwise files_are_same() { local path1="$1" local path2="$2" # Check if paths exist if [[ ! -e "$path1" || ! -e "$path2" ]]; then return 1 fi # For directories, use find + md5sum to compare recursively # For files, use md5sum directly if [[ -d "$path1" && -d "$path2" ]]; then # Compare directory contents using find and md5sum local hash1=$(find "$path1" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}') local hash2=$(find "$path2" -type f -exec md5sum {} \; | sort -k 2 | md5sum | awk '{print $1}') [[ "$hash1" == "$hash2" ]] elif [[ -f "$path1" && -f "$path2" ]]; then # Compare file hashes local hash1=$(md5sum "$path1" | awk '{print $1}') local hash2=$(md5sum "$path2" | awk '{print $1}') [[ "$hash1" == "$hash2" ]] else # One is a file, one is a directory - different types return 1 fi } # Find next backup number get_next_backup_number() { local base_path="$1" local counter=1 while [[ -e "${base_path}.old.${counter}" ]]; do ((counter++)) done echo $counter } # ============================================================================= # MAIN EXECUTION # ============================================================================= # Run user preference wizard case "$ask" in false) sleep 0 ;; *) wizard_update_preferences ;; esac # Read patterns from YAML file readarray patterns < <(yq -o=j -I=0 '.patterns[]' "$CONFIG_FILE") # Process each pattern for pattern in "${patterns[@]}"; do from=$(echo "$pattern" | yq '.from' - | envsubst) to=$(echo "$pattern" | yq '.to' - | envsubst) mode=$(echo "$pattern" | yq '.mode' - | envsubst) condition=$(echo "$pattern" | yq '.condition // "true"') # Handle fontconfig fontset override # If FONTSET_DIR_NAME is set and this is the fontconfig pattern, use the fontset instead if [[ "$from" == "dots/.config/fontconfig" ]] && [[ -n "${FONTSET_DIR_NAME:-}" ]]; then from="dots-extra/fontsets/${FONTSET_DIR_NAME}" echo "Using fontset \"${FONTSET_DIR_NAME}\" for fontconfig" fi # Check if pattern should be processed if ! should_process_pattern "$pattern"; then # Format condition message nicely if [[ "$condition" != "true" ]]; then cond_type=$(echo "$condition" | yq -r '.type // ""') cond_value=$(echo "$condition" | yq -r '.value // ""') if [[ -n "$cond_type" && -n "$cond_value" ]]; then echo "Skipping $from -> $to (condition not met: $cond_type == '$cond_value')" else echo "Skipping $from -> $to (condition not met)" fi else echo "Skipping $from -> $to (condition not met)" fi continue fi echo "Processing: $from -> $to (mode: $mode)" # Build exclude arguments for rsync excludes=() if echo "$pattern" | yq -e '.excludes' >/dev/null 2>&1; then while IFS= read -r exclude; do excludes+=(--exclude "$exclude") done < <(echo "$pattern" | yq -r '.excludes[]') fi # Check if source exists if [[ ! -e "$from" ]]; then echo "Warning: Source does not exist: $from (skipping)" continue fi # Ensure destination directory exists for files if [[ -f "$from" ]]; then v mkdir -p "$(dirname "$to")" fi # Execute based on mode case "$mode" in "sync") if [[ -d "$from" ]]; then warning_overwrite v rsync -av --delete "${excludes[@]}" "$from/" "$to/" else warning_overwrite # For files, don't use trailing slash and don't use --delete v rsync -av "${excludes[@]}" "$from" "$to" fi ;; "soft") warning_overwrite if [[ -d "$from" ]]; then v rsync -av "${excludes[@]}" "$from/" "$to/" else # For files, don't use trailing slash v rsync -av "${excludes[@]}" "$from" "$to" fi ;; "hard") v cp -r "$from" "$to" ;; "hard-backup") if [[ -e "$to" ]]; then if files_are_same "$from" "$to"; then echo "Files are identical, skipping backup" else backup_number=$(get_next_backup_number "$to") v mv "$to" "$to.old.$backup_number" v cp -r "$from" "$to" fi else v cp -r "$from" "$to" fi ;; "soft-backup") if [[ -e "$to" ]]; then if files_are_same "$from" "$to"; then echo "Files are identical, skipping backup" else v cp -r "$from" "$to.new" fi else v cp -r "$from" "$to" fi ;; "skip") echo "Skipping $from" ;; "skip-if-exists") if [[ -e "$to" ]]; then echo "Skipping $from (destination exists)" else v cp -r "$from" "$to" fi ;; *) echo "Unknown mode: $mode" ;; esac done ================================================ FILE: sdata/subcmd-install/3.files-exp.yaml ================================================ # The possible values of `mode`: (by default `sync`) # - `sync`: Make the destination completely the same as the source. # - `soft`: Skip existing files when copying # - `hard`: Overwrite existing files when copying # - `soft-backup`: If target file exists, copy source file to `*.new`. Do not create `*.new` but skip coyping when source and target HASH values are exactly the same. # - `hard-backup`: If target file exists, create backup by renaming it to `*.old.` where `` is a number increment from 1 to prevent backup gets overwritten when running twice. (Keep in mind that this script must be idempotent.) Also must compare the actual file content by using MD5 HASH value to prevent generating lots of `*.old.`s when runnng multiple times. Do not create backup but skip coyping when source and target HASH values are exactly the same. # - `skip`: Skip this step # - `skip-if-exists`: Skip this step if target exists version: "1.0" user_preferences: shell: "fish" # fish | zsh terminal: "foot" # kitty | foot keybindings: "default" # default | vim patterns: # Always install these files - from: "dots/.config/quickshell/ii" to: "$XDG_CONFIG_HOME/quickshell/ii" mode: "sync" # Conditionally install these files - from: "dots/.config/fish" to: "$XDG_CONFIG_HOME/fish" mode: "sync" excludes: ["conf.d"] condition: type: "shell" value: "fish" - from: "dots/.config/zshrc.d" to: "$XDG_CONFIG_HOME/zshrc.d" mode: "sync" condition: type: "shell" value: "zsh" - from: "dots/.config/foot" to: "$XDG_CONFIG_HOME/foot" mode: "sync" condition: type: "terminal" value: "foot" - from: "dots/.config/kitty" to: "$XDG_CONFIG_HOME/kitty" mode: "sync" condition: type: "terminal" value: "kitty" # Hyprland - from: "dots/.config/hypr" to: "$XDG_CONFIG_HOME/hypr" mode: "sync" excludes: ["custom", "hyprlock.conf", "hypridle.conf"] # Hyprland special files - from: "dots/.config/hypr/hypridle.conf" to: "$XDG_CONFIG_HOME/hypr/hypridle.conf" mode: "soft-backup" - from: "dots/.config/hypr/hyprlock.conf" to: "$XDG_CONFIG_HOME/hypr/hyprlock.conf" mode: "soft-backup" - from: "dots/.config/hypr/custom" to: "$XDG_CONFIG_HOME/hypr/custom" mode: "skip-if-exists" - from: "dots/.local/share/icons" to: "$XDG_DATA_HOME/icons" mode: "soft" - from: "dots/.local/share/konsole" to: "$XDG_DATA_HOME/konsole" mode: "soft" # Fontconfig (default - fontsets handled separately if FONTSET_DIR_NAME is set) - from: "dots/.config/fontconfig" to: "$XDG_CONFIG_HOME/fontconfig" mode: "sync" # MISC config directories (other .config directories) - from: "dots/.config/fuzzel" to: "$XDG_CONFIG_HOME/fuzzel" mode: "sync" - from: "dots/.config/kde-material-you-colors" to: "$XDG_CONFIG_HOME/kde-material-you-colors" mode: "sync" - from: "dots/.config/Kvantum" to: "$XDG_CONFIG_HOME/Kvantum" mode: "sync" - from: "dots/.config/matugen" to: "$XDG_CONFIG_HOME/matugen" mode: "sync" - from: "dots/.config/mpv" to: "$XDG_CONFIG_HOME/mpv" mode: "sync" - from: "dots/.config/qt5ct" to: "$XDG_CONFIG_HOME/qt5ct" mode: "sync" - from: "dots/.config/qt6ct" to: "$XDG_CONFIG_HOME/qt6ct" mode: "sync" - from: "dots/.config/wlogout" to: "$XDG_CONFIG_HOME/wlogout" mode: "sync" - from: "dots/.config/xdg-desktop-portal" to: "$XDG_CONFIG_HOME/xdg-desktop-portal" mode: "sync" # MISC config files (individual files in .config) - from: "dots/.config/chrome-flags.conf" to: "$XDG_CONFIG_HOME/chrome-flags.conf" mode: "soft" - from: "dots/.config/code-flags.conf" to: "$XDG_CONFIG_HOME/code-flags.conf" mode: "soft" - from: "dots/.config/darklyrc" to: "$XDG_CONFIG_HOME/darklyrc" mode: "soft" - from: "dots/.config/dolphinrc" to: "$XDG_CONFIG_HOME/dolphinrc" mode: "soft" - from: "dots/.config/kdeglobals" to: "$XDG_CONFIG_HOME/kdeglobals" mode: "soft" - from: "dots/.config/konsolerc" to: "$XDG_CONFIG_HOME/konsolerc" mode: "soft" - from: "dots/.config/starship.toml" to: "$XDG_CONFIG_HOME/starship.toml" mode: "soft" - from: "dots/.config/thorium-flags.conf" to: "$XDG_CONFIG_HOME/thorium-flags.conf" mode: "soft" ================================================ FILE: sdata/subcmd-install/3.files-legacy.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash ##################################################################################### # MISC (For dots/.config/* but not quickshell, not fish, not Hyprland, not fontconfig) case "${SKIP_MISCCONF}" in true) sleep 0;; *) for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' ! -name 'fontconfig' -exec basename {} \;); do # i="dots/.config/$i" echo "[$0]: Found target: dots/.config/$i" if [ -d "dots/.config/$i" ];then install_dir__sync "dots/.config/$i" "$XDG_CONFIG_HOME/$i" elif [ -f "dots/.config/$i" ];then install_file "dots/.config/$i" "$XDG_CONFIG_HOME/$i" fi done install_dir "dots/.local/share/konsole" "${XDG_DATA_HOME}"/konsole ;; esac case "${SKIP_QUICKSHELL}" in true) sleep 0;; *) # Should overwriting the whole directory not only ~/.config/quickshell/ii/ cuz https://github.com/end-4/dots-hyprland/issues/2294#issuecomment-3448671064 install_dir__sync dots/.config/quickshell "$XDG_CONFIG_HOME"/quickshell ;; esac case "${SKIP_FISH}" in true) sleep 0;; *) install_dir__sync_exclude dots/.config/fish "$XDG_CONFIG_HOME"/fish "conf.d" ;; esac case "${SKIP_FONTCONFIG}" in true) sleep 0;; *) case "$FONTSET_DIR_NAME" in "") install_dir__sync dots/.config/fontconfig "$XDG_CONFIG_HOME"/fontconfig ;; *) install_dir__sync dots-extra/fontsets/$FONTSET_DIR_NAME "$XDG_CONFIG_HOME"/fontconfig ;; esac;; esac # For Hyprland case "${SKIP_HYPRLAND}" in true) sleep 0;; *) install_dir__sync dots/.config/hypr/hyprland "$XDG_CONFIG_HOME"/hypr/hyprland if [ -f "${XDG_CONFIG_HOME}/hypr/hyprland.conf" ]; then mv "${XDG_CONFIG_HOME}/hypr/hyprland.conf" "${XDG_CONFIG_HOME}/hypr/hyprland.conf.old" # disable old config echo 'hyprland.conf has been renamed to hyprland.conf.old. This is to allow the new lua config to load.' fi for i in hyprlock.conf ; do install_file__auto_backup "dots/.config/hypr/$i" "${XDG_CONFIG_HOME}/hypr/$i" done for i in hyprland.lua ; do case "${SKIP_HYPRLAND_ENTRY}" in true) sleep 0;; *) install_file "dots/.config/hypr/$i" "${XDG_CONFIG_HOME}/hypr/$i" ;; esac done for i in hypridle.conf ; do if [[ "${INSTALL_VIA_NIX}" == true ]]; then install_file__auto_backup "dots-extra/via-nix/$i" "${XDG_CONFIG_HOME}/hypr/$i" else install_file__auto_backup "dots/.config/hypr/$i" "${XDG_CONFIG_HOME}/hypr/$i" fi done if [ "$OS_GROUP_ID" = "fedora" ];then v bash -c "printf \"# For fedora to setup polkit\nexec-once = /usr/libexec/kf6/polkit-kde-authentication-agent-1\n\" >> ${XDG_CONFIG_HOME}/hypr/hyprland/execs.conf" fi install_dir__ignore_existing "dots/.config/hypr/custom" "${XDG_CONFIG_HOME}/hypr/custom" ;; esac install_file "dots/.local/share/icons/illogical-impulse.svg" "${XDG_DATA_HOME}"/icons/illogical-impulse.svg ================================================ FILE: sdata/subcmd-install/3.files.sh ================================================ # This script is meant to be sourced. # It's not for directly running. printf "${STY_CYAN}[$0]: 3. Copying config files\n${STY_RST}" # shellcheck shell=bash function warning_overwrite(){ printf "${STY_YELLOW}" printf "The command below overwrites the destination.\n" printf "${STY_RST}" } function auto_backup_configs(){ local backup=false case $ask in false) if [[ ! -d "$BACKUP_DIR" ]]; then local backup=true;fi;; *) printf "${STY_RED}" printf "Would you like to backup clashing dirs/files to \"$BACKUP_DIR\"?\n" printf "${STY_RST}" while true;do echo " y = Yes, backup" echo " n/s = No, skip to next" local p; read -p "====> " p case $p in [yY]) echo -e "${STY_BLUE}OK, doing backup...${STY_RST}" local backup=true;break ;; [nNsS]) echo -e "${STY_BLUE}Alright, skipping...${STY_RST}" local backup=false;break ;; *) echo -e "${STY_RED}Please enter [y/n/s].${STY_RST}";; esac done ;; esac if $backup;then backup_clashing_targets dots/.config $XDG_CONFIG_HOME "${BACKUP_DIR}/.config" backup_clashing_targets dots/.local/share $XDG_DATA_HOME "${BACKUP_DIR}/.local/share" printf "${STY_BLUE}Backup into \"${BACKUP_DIR}\" finished.${STY_RST}\n" fi } function gen_firstrun(){ x mkdir -p "$(dirname ${FIRSTRUN_FILE})" x touch "${FIRSTRUN_FILE}" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" realpath -se "${FIRSTRUN_FILE}" >> "${INSTALLED_LISTFILE}" } cp_file(){ # NOTE: This function is only for using in other functions x mkdir -p "$(dirname $2)" x cp -f "$1" "$2" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" realpath -se "$2" >> "${INSTALLED_LISTFILE}" } rsync_dir(){ # NOTE: This function is only for using in other functions x mkdir -p "$2" local dest="$(realpath -se $2)" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" rsync -a --out-format='%i %n' "$1"/ "$2"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}" } rsync_dir__ignore_existing(){ # NOTE: This function is only for using in other functions x mkdir -p "$2" local dest="$(realpath -se $2)" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" rsync -a --ignore-existing --out-format='%i %n' "$1"/ "$2"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}" } rsync_dir__sync(){ # NOTE: This function is only for using in other functions # `--delete' for rsync to make sure that # original dotfiles and new ones in the SAME DIRECTORY # (eg. in ~/.config/hypr) won't be mixed together x mkdir -p "$2" local dest="$(realpath -se $2)" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" rsync -a --delete --out-format='%i %n' "$1"/ "$2"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}" } rsync_dir__sync_exclude(){ # NOTE: This function is only for using in other functions # Same as rsync_dir__sync but with exclude patterns support # Usage: rsync_dir__sync_exclude [ ...] local src="$1" local dest_dir="$2" shift 2 local excludes=() for pattern in "$@"; do excludes+=(--exclude "$pattern") done x mkdir -p "$dest_dir" local dest="$(realpath -se $dest_dir)" x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" rsync -a --delete "${excludes[@]}" --out-format='%i %n' "$src"/ "$dest_dir"/ | awk -v d="$dest" '$1 ~ /^>/{ sub(/^[^ ]+ /,""); printf d "/" $0 "\n" }' >> "${INSTALLED_LISTFILE}" } function install_file(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -f $t ];then warning_overwrite fi v cp_file $s $t } function install_file__auto_backup(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -f $t ];then echo -e "${STY_YELLOW}[$0]: \"$t\" already exists.${STY_RST}" if ${INSTALL_FIRSTRUN};then echo -e "${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}" v mv $t $t.old v cp_file $s $t else echo -e "${STY_BLUE}[$0]: It seems not a firstrun.${STY_RST}" v cp_file $s $t.new fi else echo -e "${STY_GREEN}[$0]: \"$t\" does not exist yet.${STY_RST}" v cp_file $s $t fi } function install_dir(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -d $t ];then warning_overwrite fi v rsync_dir $s $t } function install_dir__sync(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -d $t ];then warning_overwrite fi v rsync_dir__sync $s $t } function install_dir__skip_ifexist(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -d $t ];then echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}" else echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" v rsync_dir $s $t fi } function install_dir__ignore_existing(){ # NOTE: Do not add prefix `v` or `x` when using this function local s=$1 local t=$2 if [ -d $t ];then echo -e "${STY_BLUE}[$0]: \"$t\" already exists, will not do anything.${STY_RST}" else echo -e "${STY_YELLOW}[$0]: \"$t\" does not exist yet.${STY_RST}" v rsync_dir__ignore_existing $s $t fi } function install_dir__sync_exclude(){ # NOTE: Do not add prefix `v` or `x` when using this function # Sync directory with exclude patterns # Usage: install_dir__sync_exclude [ ...] local s=$1 local t=$2 shift 2 if [ -d $t ];then warning_overwrite fi v rsync_dir__sync_exclude $s $t "$@" } function install_google_sans_flex(){ local font_name="Google Sans Flex" local src_name="google-sans-flex" local src_url="https://github.com/end-4/google-sans-flex" local src_dir="$REPO_ROOT/cache/$src_name" local target_dir="${XDG_DATA_HOME}/fonts/illogical-impulse-$src_name" if fc-list | grep -qi "$font_name"; then return; fi x mkdir -p $src_dir x cd $src_dir try git init -b main try git remote add origin $src_url x git pull origin main x git submodule update --init --recursive warning_overwrite rsync_dir "$src_dir" "$target_dir" x fc-cache -fv x cd $REPO_ROOT x mkdir -p "$(dirname ${INSTALLED_LISTFILE})" realpath -se "$target_dir" >> "${INSTALLED_LISTFILE}" } ##################################################################################### # In case some dirs does not exists for i in "$XDG_BIN_HOME" "$XDG_CACHE_HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME"; do if ! test -e "$i"; then v mkdir -p "$i" fi done case "${INSTALL_FIRSTRUN}" in # When specify --firstrun true) sleep 0 ;; # When not specify --firstrun *) if test -f "${FIRSTRUN_FILE}"; then INSTALL_FIRSTRUN=false else INSTALL_FIRSTRUN=true fi ;; esac showfun auto_update_git_submodule v auto_update_git_submodule # Backup if [[ ! "${SKIP_BACKUP}" == true ]]; then auto_backup_configs; fi case "${EXPERIMENTAL_FILES_SCRIPT}" in true)source sdata/subcmd-install/3.files-exp.sh;; *)source sdata/subcmd-install/3.files-legacy.sh;; esac if [[ ! "$OS_GROUP_ID" == "fedora" ]]; then showfun install_google_sans_flex v install_google_sans_flex fi ##################################################################################### v gen_firstrun v dedup_and_sort_listfile "${INSTALLED_LISTFILE}" "${INSTALLED_LISTFILE}" # Prevent hyprland from not fully loaded sleep 1 try hyprctl reload ##################################################################################### printf "\n" printf "\n" printf "\n" printf "${STY_CYAN}[$0]: Finished${STY_RST}\n" printf "\n" printf "${STY_CYAN}When starting Hyprland from your display manager (login screen) ${STY_RED} DO NOT SELECT UWSM ${STY_RST}\n" printf "\n" printf "${STY_CYAN}If you are already running Hyprland,${STY_RST}\n" printf "${STY_CYAN}Press ${STY_INVERT} Ctrl+Super+T ${STY_RST}${STY_CYAN} to select a wallpaper${STY_RST}\n" printf "${STY_CYAN}Press ${STY_INVERT} Super+/ ${STY_RST}${STY_CYAN} for a list of keybinds${STY_RST}\n" printf "\n" printf "${STY_CYAN}For suggestions/hints after installation:${STY_RST}\n" printf "${STY_CYAN}${STY_UNDERLINE} https://ii.clsty.link/en/ii-qs/01setup/#post-installation ${STY_RST}\n" printf "\n" if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then printf "\n${STY_RED}[$0]: \!! Important \!! : Please ensure environment variable ${STY_RST} \$ILLOGICAL_IMPULSE_VIRTUAL_ENV ${STY_RED} is set to proper value (by default \"~/.local/state/quickshell/.venv\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.${STY_RST}\n" fi ================================================ FILE: sdata/subcmd-install/options.sh ================================================ # Handle args for subcmd: install # shellcheck shell=bash showhelp(){ printf "Syntax: $0 install [OPTIONS]... Idempotent installation for dotfiles. Options for install: -h, --help Print this help message and exit -f, --force (Dangerous) Force mode without any confirm -F, --fisrtrun Act like it is the first run -c, --clean Clean the build cache first --skip-allgreeting Skip the whole process greeting --skip-alldeps Skip the whole process installing dependency --skip-allsetups Skip the whole process setting up permissions/services etc --skip-allfiles Skip the whole process copying configuration files --ignore-outdate Ignore outdate checking for community supported \"dist-*\". -s, --skip-sysupdate Skip system package upgrade e.g. \"sudo pacman -Syu\" --skip-plasmaintg Skip installing plasma-browser-integration --skip-backup Skip backup conflicting files --skip-quickshell Skip installing the config for Quickshell --skip-hyprland Skip installing the config for Hyprland --skip-hyprland-entry Skip installing the entry config for Hyprland --skip-fish Skip installing the config for Fish --skip-fontconfig Skip installing the config for fontconfig --skip-miscconf Skip copying the dirs and files to \".configs\" except for Quickshell, Fish and Hyprland --core Alias of --skip-{plasmaintg,fish,miscconf,fontconfig} --fontset Use a set of pre-defined font and config (currently only fontconfig). Possible values of : $(ls -A ${REPO_ROOT}/dots-extra/fontsets) ${STY_CYAN} New features (experimental): --exp-files Use yaml-based config for the third step copying files. This feature is ${STY_YELLOW}still on early stage${STY_CYAN}, feedback and contribution welcomed, see https://github.com/end-4/dots-hyprland/issues/2137 for details. --via-nix Use Nix and Home-manager to install dependencies. This feature is ${STY_RED}working in progress${STY_CYAN}. Contribution is welcomed, see https://github.com/end-4/dots-hyprland/issues/1061 for details. ${STY_RST}" } cleancache(){ rm -rf "${REPO_ROOT}/cache" } # `man getopt` to see more para=$(getopt \ -o hfFk:cs \ -l help,force,firstrun,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,ignore-outdate,skip-sysupdate,skip-plasmaintg,skip-backup,skip-quickshell,skip-fish,skip-hyprland,skip-hyprland-entry,skip-fontconfig,skip-miscconf,core,exp-files,via-nix \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### ## getopt Phase 1 # ignore parameter's order, execute options below first eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; -c|--clean) cleancache;shift;; --) shift;break ;; *) shift ;; esac done ##################################################################################### ## getopt Phase 2 eval set -- "$para" while true ; do case "$1" in ## Already processed in phase 1, but not exited -c|--clean) shift;; ## Ones without parameter -f|--force) ask=false;shift;; -F|--firstrun) INSTALL_FIRSTRUN=true;shift;; --skip-allgreeting) SKIP_ALLGREETING=true;shift;; --skip-alldeps) SKIP_ALLDEPS=true;shift;; --skip-allsetups) SKIP_ALLSETUPS=true;shift;; --skip-allfiles) SKIP_ALLFILES=true;shift;; -s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;; --ignore-outdate) IGNORE_OUTDATE_CHECK=true;shift;; --skip-plasmaintg) SKIP_PLASMAINTG=true;shift;; --skip-backup) SKIP_BACKUP=true;shift;; --skip-hyprland) SKIP_HYPRLAND=true;shift;; --skip-hyprland-entry) SKIP_HYPRLAND_ENTRY=true;shift;; --skip-fish) SKIP_FISH=true;shift;; --skip-quickshell) SKIP_QUICKSHELL=true;shift;; --skip-fontconfig) SKIP_FONTCONFIG=true;shift;; --skip-miscconf) SKIP_MISCCONF=true;shift;; --core) SKIP_PLASMAINTG=true;SKIP_FISH=true;SKIP_FONTCONFIG=true;SKIP_MISCCONF=true;shift;; --exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;; --via-nix) INSTALL_VIA_NIX=true;shift;; ## Ones with parameter --fontset) if [[ -d "${REPO_ROOT}/dots-extra/fontsets/$2" ]]; then echo "Using fontset \"$2\".";FONTSET_DIR_NAME="$2";shift 2 else echo "Wrong argument for $1.";exit 1 fi;; ## Ending --) shift;break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done ================================================ FILE: sdata/subcmd-resetfirstrun/0.run.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash try rm "${FIRSTRUN_FILE}" ================================================ FILE: sdata/subcmd-resetfirstrun/options.sh ================================================ # Handle args for subcmd: checkdeps # shellcheck shell=bash showhelp(){ echo -e "Syntax: $0 resetfirstrun [OPTIONS] Reset firstrun state. Options: -h, --help Show this help message and exit " } # `man getopt` to see more para=$(getopt \ -o c \ -l help \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) shift;break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done ================================================ FILE: sdata/subcmd-uninstall/0.run.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash printf "${STY_RED}" printf "===CAUTION===\n" printf "This script will try to revert changes made by \"./setup install\".\n" printf "However:\n" printf "1. It is far from enough to precisely revert all changes.\n" printf "2. It has not been fully tested, use at your own risk.\n" printf "${STY_RST}" pause ############################################################################################################################## # Undo Step 3 printf "${STY_CYAN}Undo install step 3...\n${STY_RST}" function view_listfile(){ local listfile="$1" if command -v less >/dev/null; then less "$listfile" else cat "$listfile" fi } function edit_listfile(){ local listfile="$1" for ed in "$EDITOR" nano vim nvim vi; do if command -v $ed >/dev/null; then x $ed "$listfile" return fi done printf "Failed to find an available editor, please manually edit \"$listfile\".\n" } function delete_targets(){ local listfile="$1" local targets=() readarray -t targets < "$listfile" for path in "${targets[@]}"; do if [[ ! -e "$path" ]]; then printf "${STY_YELLOW}Target \"$path\" inexists, skipping...${STY_RST}\n" continue elif [[ "$path" == "$HOME"* ]]; then if [[ -d "$path" ]]; then x rm -r -- "$path" else x rm -- "$path" fi else while true; do printf "WARNING: Target \"$path\" is not under \$HOME. Still delete it?\ny=Yes, delete it;\nn=No, skip this one\n" read -n1 -p "> " ans < /dev/tty echo case "$ans" in y|Y) if [[ -d "$path" ]]; then x rm -r -- "$path" else x rm -- "$path" fi break 1 ;; n|N) break 1 ;; *) ;; esac done fi done } function deletion_prompt(){ local listfile="$1" while true; do printf "Every target which path as a line inside the list \"$listfile\" will be deleted permanently.\n" printf "Please choose:\nv=View the list\ne=Edit the list\nq=Quit\ny=Perform deletion now\n" read -n1 -p "> " choice < /dev/tty echo case "$choice" in q|Q) printf "Quiting...\n" break ;; y|Y) delete_targets "$listfile" break ;; v|V) view_listfile "$listfile" ;; e|E) edit_listfile "$listfile" ;; *) ;; esac done } deletion_prompt "${INSTALLED_LISTFILE}" empty_dir_listfile=$(mktemp) scan_paths=(${XDG_CONFIG_HOME} "${XDG_DATA_HOME}"/konsole) for dir in "${scan_paths[@]}"; do find "$dir" -type d -empty -print >> $empty_dir_listfile done x dedup_and_sort_listfile "$empty_dir_listfile" "$empty_dir_listfile" deletion_prompt "$empty_dir_listfile" ############################################################################################################################## printf "${STY_CYAN}Undo install step 2...\n${STY_RST}" user=$(whoami) warn_undo_break_system(){ printf "${STY_YELLOW}WARNING: The command below could break your system functionality. If you are unsure about it, just skip the command.${STY_RST}\n" } warn_undo_break_system v sudo gpasswd -d "$user" video warn_undo_break_system v sudo gpasswd -d "$user" i2c warn_undo_break_system v sudo gpasswd -d "$user" input warn_undo_break_system v sudo rm /etc/modules-load.d/i2c-dev.conf ############################################################################################################################## printf "${STY_CYAN}Undo install step 1...\n${STY_RST}" if test -f sdata/dist-$OS_GROUP_ID/uninstall-deps.sh; then source sdata/dist-$OS_GROUP_ID/uninstall-deps.sh else printf "${STY_YELLOW}Automatic depedencies uninstallation is not yet avaible for your distro. Skipping...${STY_RST}\n" fi printf "${STY_CYAN}Uninstall script finished.\n${STY_RST}" printf "${STY_CYAN}Hint: If you had agreed to backup when you ran \"./setup install\", you should be able to find it under \"$BACKUP_DIR\".\n${STY_RST}" ================================================ FILE: sdata/subcmd-uninstall/options.sh ================================================ # Handle args for subcmd: uninstall # shellcheck shell=bash showhelp(){ echo -e "Syntax: $0 uninstall [OPTIONS]... Unintall dots. Options: -h, --help Show this help message " } # `man getopt` to see more para=$(getopt \ -o h \ -l help \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### ## getopt Phase 1 # ignore parameter's order, execute options below first eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) break ;; *) shift ;; esac done ================================================ FILE: sdata/subcmd-virtmon/0.run.sh ================================================ # This script is meant to be sourced. # It's not for directly running. # shellcheck shell=bash ensure_cmds wayvnc lsof jq ip start_hypr_mon_guard(){ if ! pgrep -x hypr_mon_guard >/dev/null 2>&1; then if PATH=$PATH:${REPO_ROOT}/sdata/subcmd-virtmon command -v hypr_mon_guard ; then echo "Running hypr_mon_guard." PATH=$PATH:${REPO_ROOT}/sdata/subcmd-virtmon setsid hypr_mon_guard > $(mktemp) 2>&1 & else echo "Script hypr_mon_guard not found." exit 1 fi fi } if ! [[ "${DISABLE_HYPR_MON_GUARD}" = true ]]; then start_hypr_mon_guard fi readarray -t vmon_ids < <(hyprctl -j monitors all | jq -r '.[] | select(.name | test("^TESTER-")) | .name | sub("^TESTER-"; "")') if [[ "${CLEAN_TESTER_MONITORS}" = true ]]; then echo "Cleaning tester monitors..." for i in "${vmon_ids[@]}"; do echo "Removing tester monitor: TESTER-$i..." x hyprctl output remove "TESTER-$i" done echo "Cleaning tester wayvnc sessions..." for i in /tmp/wayvncctl_tester_* ; do # When no target is matched, * will not be expanded [ -e "$i" ] || continue x bash -c "wayvncctl --socket=$i -r wayvnc-exit || rm $i" done echo "Cleaning complete, exit..." exit 0 fi echo "Finding an unused port..." for port in {5900..5999}; do if ! lsof -nP -iTCP:"$port" -sTCP:LISTEN -t >/dev/null 2>&1; then vnc_port="$port" break fi done if [ -z "$vnc_port" ];then echo "No available port for vnc server, aborting..."; exit 1 fi # The name of tester_socket can be anything, the following just borrows $vnc_port as ID tester_socket=/tmp/wayvncctl_tester_$vnc_port # In case this exists for some reason try rm $tester_socket vmon_tester=TESTER-$vnc_port echo "Creating tester monitor..." x hyprctl output create headless ${vmon_tester} echo "Setting properties of tester monitor..." x hyprctl keyword monitor ${vmon_tester},${VMON_RESOLUTION}@${VMON_FPS},${VMON_POSITION},${VMON_SCALE}${VMON_EXTRA} e="%s${STY_RST}\n" printf "${STY_YELLOW}=========================================$e" printf "${STY_CYAN}The status of the virtual monitor:$e" printf "${STY_BLUE}Resolution: ${STY_UNDERLINE}${STY_INVERT}${VMON_RESOLUTION}$e" printf "${STY_BLUE}Frame rate: ${STY_UNDERLINE}${STY_INVERT}${VMON_FPS}$e" printf "${STY_CYAN}Use a VNC client to connect to the virtual monitor.$e" printf "${STY_BLUE}Port: ${STY_UNDERLINE}${STY_INVERT}$vnc_port$e" printf "${STY_BLUE}IP: use a suitable one from below:$e" printf ${STY_PURPLE} LANG=C LC_ALL=C ip -o addr show up | grep -v -E 'docker|veth|virbr' | awk '{split($4,a,"/"); print $2"\t"a[1]}' printf ${STY_RST} printf "${STY_CYAN}Hint:$e" printf "${STY_GREEN} The VNC client will ask you about server address,$e" printf "${STY_GREEN} either joined as : or separately.$e" printf "${STY_GREEN} As for username and password, just leave them as empty.$e" printf "${STY_YELLOW}=========================================$e" if [ "$RUNNING_IN_BACKGROUND" = true ];then echo "wayvnc now running in background. Run again with --clean to cleanup." nohup wayvnc ${WAYVNC_EX_ARGS} --socket=$tester_socket -f=${VMON_FPS} -o=${vmon_tester} --log-level=${WAYVNC_LOGLEVEL} 0.0.0.0 $vnc_port > $(mktemp) 2>&1 & disown else echo "wayvnc now running, press Ctrl-C to quit." wayvnc ${WAYVNC_EX_ARGS} --socket=$tester_socket -f=${VMON_FPS} -o=${vmon_tester} --log-level=${WAYVNC_LOGLEVEL} 0.0.0.0 $vnc_port echo "wayvnc stopped. Cleaning..." hyprctl output remove "${vmon_tester}" fi ================================================ FILE: sdata/subcmd-virtmon/hypr_mon_guard ================================================ #!/usr/bin/bash # This script is to prevent hyprland from not responding to any input when no monitor is enabled. # When this script is running in background, # it will be safe to temporarily disable monitors using hyprctl. # The shebang cannot be #!/usr/bin/env bash , idk why while true; do readarray -t enabled_mons < <(hyprctl -j monitors all | jq -r '.[] | select(.disabled == false) | .name') #if -z "$enabled_mons"; then if [ ${#enabled_mons[@]} -eq 0 ]; then hyprctl reload fi sleep 30 done ================================================ FILE: sdata/subcmd-virtmon/options.sh ================================================ # Handle args for subcmd: checkdeps # shellcheck shell=bash VMON_RESOLUTION=1920x1080 VMON_FPS=60 VMON_POSITION=auto VMON_SCALE=1 VMON_EXTRA="" WAYVNC_LOGLEVEL=${WAYVNC_LOGLEVEL:-quiet} showhelp(){ echo -e "Syntax: $0 virtmon [OPTIONS] Create virtual monitor for testing multi-monitors. Options: -h, --help Show this help message and exit -c, --clean Clean all tester monitors and wayvnc sessions and exit -d, --daemon Run in background --no-guard Disable hypr_mon_guard. Tip: this process can also be terminated using ${STY_BOLD}pkill hypr_mon_guard${STY_RST} For the syntax of following options, see also Hyprland Wiki: https://wiki.hypr.land/Configuring/Monitors --res Resolution, by default ${STY_UNDERLINE}$VMON_RESOLUTION${STY_RST} --fps Refresh rate and FPS, by default ${STY_UNDERLINE}$VMON_FPS${STY_RST} --pos Position, by default ${STY_UNDERLINE}$VMON_POSITION${STY_RST} Examples: ${STY_UNDERLINE}auto-left${STY_RST}, ${STY_UNDERLINE}0x-1080${STY_RST} --sca Scale, by default ${STY_UNDERLINE}$VMON_SCALE${STY_RST} --ext Extra properties, e.g. ${STY_UNDERLINE}transform, 1${STY_RST} Note 1: The virtual monitor will be served via wayvnc. You need a VNC client to connect to it. Recommended VNC client: - Android: AVNC (https://github.com/gujjwal00/avnc) - Linux X11, Windows and MacOS: TigerVNC (https://github.com/TigerVNC/tigervnc) - Linux Wayland: Remmina-VNC (https://remmina.org/remmina-vnc) Note 2: You can run this subcommand multiple times to create multiple virtual monitor. To do this, use ${STY_UNDERLINE}-d${STY_RST} to run wayvnc in background and repeat again, or just open extra terminal/shell and run the subcommand again. Note 3: If you do not have a spare device connected to the same network, it's still possible to test multi-monitor by using VNC client on this device, in which case the should be ${STY_UNDERLINE}localhost${STY_RST} or ${STY_UNDERLINE}127.0.0.1${STY_RST}. " } # `man getopt` to see more para=$(getopt \ -o hcd \ -l help,clean,daemon,no-guard,res:,fps:,pos:,sca:,ext: \ -n "$0" -- "$@") [ $? != 0 ] && echo "$0: Error when getopt, please recheck parameters." && exit 1 ##################################################################################### eval set -- "$para" while true ; do case "$1" in -h|--help) showhelp;exit;; --) shift;break ;; *) shift ;; esac done eval set -- "$para" while true ; do case "$1" in -c|--clean) CLEAN_TESTER_MONITORS=true;shift;; -d|--daemon) RUNNING_IN_BACKGROUND=true;shift;; --no-guard) DISABLE_HYPR_MON_GUARD=true;shift;; --res) VMON_RESOLUTION="$2";shift 2;; --fps) VMON_FPS="$2";shift 2;; --pos) VMON_POSITION="$2";shift 2;; --sca) VMON_SCALE="$2";shift 2;; --ext) VMON_EXTRA=", $2";shift 2;; --) shift;break ;; *) echo -e "$0: Wrong parameters.";exit 1;; esac done ================================================ FILE: sdata/uv/README.md ================================================ ## Why is this important? Instead of installing python packages via system package manager, we should install them into virtual environment. This is important because there has been so many complaints about the failure installing/updating python packages via system package manager, see [#1017](https://github.com/end-4/dots-hyprland/issues/1017). ## How to add/remove python package? 1. Edit `requirements.in`. You may refer to [PyPI](https://pypi.org/) for possible package names. - If PyPI does not have the needed package, we probably need to build it manually inside the venv. In such case we need to edit the install scripts. 2. Run `uv pip compile requirements.in -o requirements.txt` in this folder. **Notes:** - For reference see [uv doc](https://docs.astral.sh/uv/pip/dependencies/#using-requirementsin). - `requirements.txt` is included in Git. It's for locking package versions to enhance stability and reproducibility.[^1] [^1]: In fact, including package version lock file in Git is also the most common way for similar situations, for example the `package-lock.json` of Node.js projects (see also [this stackoverflow question](https://stackoverflow.com/questions/48524417/should-the-package-lock-json-file-be-added-to-gitignore)). Although there are some situations when it's not suitable to include the lock file, for example [the poetry document](https://python-poetry.org/docs/basic-usage/#committing-your-poetrylock-file-to-version-control) recommend application developers to include package version lock file in Git, but library developers should consider more, such as not including the lock file or including it but refreshing regularly. ## How will the python packages get installed? For summary: - They will be installed to the virtual environment `$ILLOGICAL_IMPULSE_VIRTUAL_ENV`. - The default value of `$ILLOGICAL_IMPULSE_VIRTUAL_ENV` is `$XDG_STATE_HOME/quickshell/.venv`. - The default value of `$XDG_STATE_HOME` is `$HOME/.local/state`. - Currently we use `env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv` in `~/.config/hypr/hyprland/env.conf` to set this environment variable.[^2] For details: see the function `install-python-packages()` defined in `/sdata/lib/package-installers.sh`. [^2]: Hyprland seems to have weird problem dealing with recursive variable, so we can not use `$XDG_STATE_HOME/quickshell/.venv` even if we had set `$XDG_STATE_HOME` to `~/.local/state` explicitly, else `$XDG_STATE_HOME` will possibly not get expanded but get recognised as literally `$XDG_STATE_HOME`. This problem never happens for some users, but according to some issues when we were using recursive variable setting in the past, it's possible to happen for other users. Reason unknown. ## How to use the python packages installed through here? Basically you'll need to activate the virtual environment first: ```bash source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate ``` It will add the python executable located in the venv to `$PATH` and give it the highest priority. Run `which python` and you'll understand. This python executable will also search and use the python package inside the venv, which enables running any python script or running command provided via python package using the venv. After that you probably need to deactivate it: ```bash deactivate ``` ### Situation 1: As a single command **Description:** At someplace which accept a single command, - run a python script, - or run a command provided by python package. Example: In `~/‎.config/quickshell/ii/screenshot.qml`: ```qml Process { id: imageDetectionProcess command: ["bash", "-c", `${Directories.scriptPath}/images/find_regions.py ` + `--hyprctl ` + `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' ` + `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} ` ``` In this example, python script `find_regions.py` is called and receives some arguments. #### Solution A: shebang Add the shebang below to the beginning of python script: ```python #!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" ``` And that's it! **Note:** This is the simplest solution as it only modifies the shebang of python script. **However:** - It's only for python script, not the command provided by python package. - It can not deal with complex argument (e.g. filename containing spaces) passed to the python script. - If we apply this solution to the example above, it may cause problem, considering that `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}'` could be a rather complex argument passed to `find_regions.py`. - This solution rely on shebang to activate the correct python venv, but the shebang will be ignored if the script is directly passed to the interpreter, e.g. `python3 foo.py`. #### Solution B: bash script as wrapper First make sure the python script is using the shebang `#!/usr/bin/env python3`, instead of `#!/usr/bin/python3` or something else. Then write a wrapper script in bash. Let's continue the `screenshot.qml` example, in the same directory as `find_regions.py`, write a `find-regions-venv.sh`: ```bash #!/usr/bin/env bash # Specify the path of the python script. # The example below only applies when `find_regions.py` and this wrapper script are under the same folder. PY_SCRIPT="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)/find_regions.py" source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate "$PY_SCRIPT" "$@" deactivate ``` **Not done yet!** Do not forget to update the code calling the original python script. In this example, in `~/‎.config/quickshell/ii/screenshot.qml` we should modify `find_regions.py` to the wrapper script `find-regions-venv.sh`: ```qml Process { id: imageDetectionProcess command: ["bash", "-c", `${Directories.scriptPath}/images/find-regions-venv.sh ` + `--hyprctl ` + `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' ` + `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} ` ``` ### Situation 2: Inside a bash script Note: the solutions for `Situation 1: As a single command` also apply here; but **not** vice versa. **Description:** Inside a bash script, - run a python script, - or run a command provided by python package. **Solution:** - Add "activation command" before the target line, - Also add "deactivation command" after the target line. **Example:** For running a python script, take `generate_colors_material.py` as example: ```bash source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" python3 "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \ > "$STATE_DIR"/user/generated/material_colors.scss "$SCRIPT_DIR"/applycolor.sh ``` For running a python script provided by python package, take `kde-material-you-colors` as example: ```bash source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" kde-material-you-colors "$mode_flag" --color "$color" -sv "$sv_num" deactivate ``` ================================================ FILE: sdata/uv/requirements.in ================================================ build pillow setuptools-scm wheel pywayland psutil kde-material-you-colors materialyoucolor libsass material-color-utilities setproctitle click loguru pycairo pygobject tqdm numpy opencv-contrib-python google-auth requests ================================================ FILE: sdata/uv/requirements.txt ================================================ # This file was autogenerated by uv via the following command: # uv pip compile requirements.in -o requirements.txt build==1.2.2.post1 # via -r requirements.in certifi==2026.2.25 # via requests cffi==1.17.1 # via # cryptography # pywayland charset-normalizer==3.4.7 # via requests click==8.2.1 # via -r requirements.in cryptography==46.0.0 # via google-auth dbus-python==1.4.0 # via kde-material-you-colors google-auth==2.49.1 # via -r requirements.in idna==3.11 # via requests kde-material-you-colors==1.10.1 # via -r requirements.in libsass==0.23.0 # via -r requirements.in loguru==0.7.3 # via -r requirements.in material-color-utilities==0.2.1 # via -r requirements.in materialyoucolor==2.0.10 # via # -r requirements.in # kde-material-you-colors numpy==2.2.2 # via # -r requirements.in # kde-material-you-colors # material-color-utilities # opencv-contrib-python opencv-contrib-python==4.12.0.88 # via -r requirements.in packaging==24.2 # via # build # setuptools-scm pillow==11.1.0 # via # -r requirements.in # kde-material-you-colors # material-color-utilities psutil==6.1.1 # via -r requirements.in pyasn1==0.6.3 # via pyasn1-modules pyasn1-modules==0.4.2 # via google-auth pycairo==1.28.0 # via # -r requirements.in # pygobject pycparser==2.22 # via cffi pygobject==3.52.3 # via -r requirements.in pyproject-hooks==1.2.0 # via build pywayland==0.4.18 # via -r requirements.in requests==2.33.1 # via -r requirements.in setproctitle==1.3.4 # via -r requirements.in setuptools==80.9.0 # via setuptools-scm setuptools-scm==8.1.0 # via -r requirements.in tqdm==4.67.1 # via -r requirements.in urllib3==2.6.3 # via requests wheel==0.45.1 # via -r requirements.in ================================================ FILE: sdata/uv/shell.nix ================================================ { pkgs ? import {} }: pkgs.mkShell { buildInputs = with pkgs; [ pkg-config meson ninja cairo dbus dbus-glib glib ]; } ================================================ FILE: setup ================================================ #!/usr/bin/env bash cd "$(dirname "$0")" # Use REPO_ROOT instead of base - when scripts are sourced they do not need export to inherit vars REPO_ROOT="$(pwd)" source ./sdata/lib/environment-variables.sh source ./sdata/lib/functions.sh source ./sdata/lib/package-installers.sh source ./sdata/lib/dist-determine.sh prevent_sudo_or_root set -e ##################################################################################### showhelp_global(){ printf "${STY_CYAN}NOTE: The old \"./install.sh\" is now \"./setup install\" The old \"./update.sh\" is now \"./setup exp-update\" The old \"./uninstall.sh\" is now \"./setup uninstall\"${STY_RST} [$0]: Handle setup for illogical-impulse. Syntax: $0 [OPTIONS]... Subcommands: install (Re)Install/Update illogical-impulse. Note: To update to the latest, manually run \"git stash && git pull\" first. install-deps Run the install step \"1. Install dependencies\" install-setups Run the install step \"2. Setup for permissions/services etc\" install-files Run the install step \"3. Copying config files\" resetfirstrun Reset firstrun state. uninstall Uninstall illogical-impulse. exp-update (Experimental) Update illogical-impulse without fully reinstall. exp-merge (Experimental) Merge upstream changes with local configs using git rebase. virtmon (For dev only) Create virtual monitors for testing multi-monitors. checkdeps (For dev only) Check whether pkgs exist in AUR or repos of Arch. help Show this help message. For each , use -h for details: $0 -h ${STY_BOLD}${STY_CYAN}Access ${STY_UNDERLINE}https://ii.clsty.link${STY_RST} ${STY_BOLD}${STY_CYAN}for documentation about illogical-impulse.${STY_RST} " } case $1 in # Global help ""|help|--help|-h)showhelp_global;exit;; # Correct subcommand install|uninstall|exp-update|exp-merge|resetfirstrun|checkdeps|virtmon) SUBCMD_NAME=$1 SUBCMD_DIR=./sdata/subcmd-$1 shift;; # Correct subcommand but not using ./sdata/subcmd-$1 install-deps|install-setups|install-files) SUBCMD_NAME=$1 SUBCMD_DIR=./sdata/subcmd-install shift;; # Wrong subcommand *)printf "${STY_RED}Unknown subcommand \"$1\".${STY_RST}\n";showhelp_global;exit 1;; esac ##################################################################################### if [[ -f "${SUBCMD_DIR}/options.sh" ]]; then source "${SUBCMD_DIR}/options.sh" fi case ${SUBCMD_NAME} in install) for function in ${print_os_group_id_functions[@]}; do $function done pause # Initialize sudo keepalive for the entire install process sudo_init_keepalive # Set trap to cleanup when this subcommand exits trap sudo_stop_keepalive EXIT INT TERM if [[ "${SKIP_ALLGREETING}" != true ]]; then source ${SUBCMD_DIR}/0.greeting.sh fi if [[ "${SKIP_ALLDEPS}" != true ]]; then source ${SUBCMD_DIR}/1.deps-router.sh fi if [[ "${SKIP_ALLSETUPS}" != true ]]; then source ${SUBCMD_DIR}/2.setups.sh fi if [[ "${SKIP_ALLFILES}" != true ]]; then source ${SUBCMD_DIR}/3.files.sh fi ;; install-deps) for function in ${print_os_group_id_functions[@]}; do $function done pause # Initialize sudo keepalive for dependency installation sudo_init_keepalive # Set trap to cleanup when this subcommand exits trap sudo_stop_keepalive EXIT INT TERM if [[ "${SKIP_ALLDEPS}" != true ]]; then source ${SUBCMD_DIR}/1.deps-router.sh fi ;; install-setups) for function in ${print_os_group_id_functions[@]}; do $function done pause # Initialize sudo keepalive for setup steps sudo_init_keepalive # Set trap to cleanup when this subcommand exits trap sudo_stop_keepalive EXIT INT TERM if [[ "${SKIP_ALLSETUPS}" != true ]]; then source ${SUBCMD_DIR}/2.setups.sh fi ;; install-files) for function in ${print_os_group_id_functions[@]}; do $function done pause if [[ "${SKIP_ALLFILES}" != true ]]; then source ${SUBCMD_DIR}/3.files.sh fi ;; *) source ${SUBCMD_DIR}/0.run.sh exit ;; esac