6 Commits

Author SHA1 Message Date
Domipoke 44b9c40c44 24/07/2026 2026-07-24 12:16:36 +02:00
Domipoke 59c8a994bf Merge remote-tracking branch 'refs/remotes/origin/main' 2026-06-25 20:02:47 +02:00
Domipoke 955801c666 noctalia 2026-06-25 19:58:23 +02:00
Domipoke 89e7ff0ec3 Update hypr/privatesettings.lua 2026-06-25 19:49:51 +02:00
Domipoke 6a2f2623b2 Add privatesettings.lua 2026-06-25 19:49:34 +02:00
Domipoke 372c622025 hyprfix 2026-06-25 19:46:22 +02:00
160 changed files with 19826 additions and 65 deletions
+2
View File
@@ -0,0 +1,2 @@
privatesettings.lua
monitors.lua
+3 -3
View File
@@ -26,12 +26,12 @@ hl.on("hyprland.start", function()
-- hl.exec_cmd("blueman-tray") -- hl.exec_cmd("blueman-tray")
-- For keyboard -- For keyboard
hl.exec_cmd("fcitx5 -D") -- hl.exec_cmd("fcitx5 -D")
-- Start plugins -- Start plugins
hl.exec_cmd("hyprpm reload") hl.exec_cmd("hyprpm reload")
hl.exec_cmd("kded6") hl.exec_cmd("kded6")
hl.exec_cmd("kbuildsycoca6") hl.exec_cmd("kbuildsycoca6")
hl.exec_cmd("qs -c noctalia-shell") hl.exec_cmd("noctalia")
--hl.exec_cmd("noctalia")
end) end)
+25
View File
@@ -0,0 +1,25 @@
NUM_KEYS = {
{code = "87"}, -- 1
{code = "88"}, -- 2
{code = "89"}, -- 3
{code = "83"}, -- 4
{code = "84"}, -- 5
{code = "85"}, -- 6
{code = "79"}, -- 7
{code = "80"}, -- 8
{code = "81"}, -- 9
{code = "90"} -- 0
}
Terminal = "kitty"
FileManager = "dolphin"
Browser = "vivaldi"
Editor = "code"
Spotify = "spotify"
Instagram="vivaldi --app-id=akpamiohjfcnimfljfndmaldlcfphjmp"
X="vivaldi --app-id=lodlkdfmihgonocnmddehnfgiljnadcf"
Whatsapp="vivaldi --app-id=hnpfjngllnobngcgfapefoaidbinmjnm"
Discord="vesktop"
+41
View File
@@ -0,0 +1,41 @@
local reset = "reset"
local hub = "hub"
local social = "hub_social"
local mail = "hub_mail"
local music = "hub_music"
hl.bind(mainMod .. " + code:" .. NUM_KEYS[10].code, hl.dsp.submap(hub))
hl.define_submap(hub, function ()
hl.bind("S", hl.dsp.submap(social))
-- hl.bind("G", hl.dsp.submap(mail))
hl.bind("M", hl.dsp.submap(music))
hl.bind("Escape", hl.dsp.submap(reset))
end)
-- Social
hl.define_submap(social, function ()
hl.bind("I", hl.dsp.exec_cmd(Instagram))
hl.bind("I", hl.dsp.submap(reset))
hl.bind("X", hl.dsp.exec_cmd(X))
hl.bind("X", hl.dsp.submap(reset))
hl.bind("D", hl.dsp.exec_cmd(Discord))
hl.bind("D", hl.dsp.submap(reset))
hl.bind("W", hl.dsp.exec_cmd(Whatsapp))
hl.bind("W", hl.dsp.submap(reset))
hl.bind("Escape", hl.dsp.submap(reset))
end)
hl.define_submap(music, function ()
hl.bind("S", hl.dsp.exec_cmd(Spotify))
hl.bind("Escape", hl.dsp.submap(reset))
end)
+13 -1
View File
@@ -1,4 +1,5 @@
hl.env("AQ_DRM_DEVICES", "/dev/dri/card1") require("config")
require("privatesettings")
-- Setup gestures -- Setup gestures
hl.gesture({ hl.gesture({
@@ -101,6 +102,14 @@ require("keybinds")
require("windowrule") require("windowrule")
require("workspace") require("workspace")
-- ###########
-- ### HUB ###
-- ###########
require("hub")
-- ################## -- ##################
-- ### EXTENSIONS ### -- ### EXTENSIONS ###
-- ################## -- ##################
@@ -108,3 +117,6 @@ require("workspace")
-- This loads Noctalia-generated Hyprland colors. -- This loads Noctalia-generated Hyprland colors.
dofile("/home/domenico/.config/hypr/noctalia/noctalia-colors.lua") dofile("/home/domenico/.config/hypr/noctalia/noctalia-colors.lua")
-- For Noctalia Color templates
require("noctalia").apply_theme()
+11
View File
@@ -0,0 +1,11 @@
max-gamma = 150
profile {
time = 7:00
identity = true
}
profile {
time = 01:00
temperature = 5000
gamma = 0.65
}
+18 -16
View File
@@ -1,17 +1,17 @@
local terminal = "kitty"
local fileManager = "dolphin"
local browser = "vivaldi"
local editor = "code"
noctalia = "qs -c noctalia-shell ipc call "
--noctalia = "qs -c noctalia-shell ipc call "
noctalia = "noctalia msg "
mainMod = "SUPER" mainMod = "SUPER"
-- Exec -- Exec
hl.bind(mainMod .. " + RETURN", hl.dsp.exec_cmd(terminal), { description = "Open terminal" }) hl.bind(mainMod .. " + RETURN", hl.dsp.exec_cmd(Terminal), { description = "Open terminal" })
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager), { description = "Open file manager" }) hl.bind(mainMod .. " + SHIFT + RETURN", hl.dsp.exec_cmd(Terminal .. " --title \"Floating\""), {description = "Open a floating terminal"})
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(browser), { description = "Open browser" }) hl.bind(mainMod .. " + E", hl.dsp.exec_cmd("kitty bash -c yazi"), { description = "Open file manager" })
hl.bind(mainMod .. " + C", hl.dsp.exec_cmd(editor), { description = "Open editor" }) hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(Browser), { description = "Open browser" })
hl.bind(mainMod .. " + V", hl.dsp.exec_cmd(noctalia .. "plugin:clipper toggle"), {description = "Open Clipboard history"}) hl.bind(mainMod .. " + C", hl.dsp.exec_cmd(Editor), { description = "Open editor" })
hl.bind(mainMod .. " + V", hl.dsp.exec_cmd(noctalia .. "panel-toggle clipboard"), {description = "Open Clipboard history"})
hl.bind(mainMod .. " + D", hl.dsp.exec_cmd(FileManager), {description = "Open file manager"})
hl.bind("ALT + L", hl.dsp.exec_cmd("hyprlock --grace 5"), { description = "Lock screen" }) hl.bind("ALT + L", hl.dsp.exec_cmd("hyprlock --grace 5"), { description = "Lock screen" })
hl.bind("SUPER + SHIFT + C", hl.dsp.exec_cmd("bash ~/.config/hypr/scripts/hyprPicker.sh"), hl.bind("SUPER + SHIFT + C", hl.dsp.exec_cmd("bash ~/.config/hypr/scripts/hyprPicker.sh"),
@@ -98,10 +98,12 @@ hl.bind("SUPER + SHIFT + M", hl.dsp.exec_cmd("bash ~/.config/hypr/scripts/nightm
-- Noctalia shortcut -- Noctalia shortcut
hl.bind(mainMod .. " + Backslash", hl.dsp.exec_cmd(noctalia .. "launcher toggle"), { description = "Toggle quickshell bar" }) hl.bind(mainMod .. " + Backslash", hl.dsp.exec_cmd(noctalia .. "panel-toggle launcher"), { description = "Toggle quickshell bar" })
--hl.bind(mainMod .. " + N", hl.dsp.global(noctalia .. ""), { description = "Toggle quickshell hub" }) --hl.bind(mainMod .. " + N", hl.dsp.global(noctalia .. ""), { description = "Toggle quickshell hub" })
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(noctalia .. "plugin:wallcards toggle"), { description = "Toggle wall picker" }) --hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(noctalia .. "plugin wallcards toggle"), { description = "Toggle wall picker" })
hl.bind(mainMod .. " + K", hl.dsp.global("quickshell:keybindingsToggle"), { description = "Toggle keybindings" }) --hl.bind(mainMod .. " + K", hl.dsp.global("quickshell:keybindingsToggle"), { description = "Toggle keybindings" })
hl.bind(mainMod .. " + L", hl.dsp.global("quickshell:changeBarLayout"), { description = "Change bar layout" }) --hl.bind(mainMod .. " + L", hl.dsp.global("quickshell:changeBarLayout"), { description = "Change bar layout" })
hl.bind("ALT + Tab", hl.dsp.exec_cmd(noctalia .. "window-switcher"))
hl.bind(mainMod .. " + SHIFT + N", hl.dsp.exec_cmd(noctalia .. "notification-dnd-toggle"))
hl.bind(mainMod .. " + N", hl.dsp.exec_cmd(noctalia .. "panel-toggle control-center notifications"))
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(noctalia .. "panel-toggle wallpaper"))
+13 -25
View File
@@ -10,33 +10,21 @@ hl.monitor({
cm = "srgb" cm = "srgb"
}) })
-- hl.monitor({ if os.getenv("HYPR_NVIDIA") == "1" then
-- output = "HDMI-A-1",
-- mode = "1920x1080@60",
-- position = "0x1280",
-- scale = "1",
-- mirror = "eDP-1"
-- })
-- hl.monitor({
-- output = "DP-3",
-- mode = "1920x1080@60",
-- position = "0x450",
-- scale = "1"
-- })
-- hl.monitor({
-- output = "DP-2",
-- mode = "1920x1080@144",
-- position = "1920x380",
-- scale = "1",
-- })
hl.monitor({ hl.monitor({
output = "HDMI-A-1", output = "HDMI-A-1",
mode = "1920x1080@60", mode = "1920x1080@60",
position = "3840x0", position = "1920x0",
scale = "1", scale = "1",
transform = true transform = 0,
}) })
elseif os.getenv("HYPR_NVIDIA") == "2" then
hl.monitor({
output = "HDMI-A-1",
mode = "1920x1080@60",
position = "1920x0",
scale = "1",
transform = 0,
})
end
+45
View File
@@ -0,0 +1,45 @@
-- Generated by Noctalia
local primary = "rgb(e0b6ff)"
local surface = "rgb(1d1013)"
local secondary = "rgb(ddb9f8)"
local error = "rgb(ffb4ab)"
local function apply_theme()
hl.config({
general = {
col = {
active_border = primary,
inactive_border = surface,
},
},
group = {
col = {
border_active = secondary,
border_inactive = surface,
border_locked_active = error,
border_locked_inactive = surface,
},
groupbar = {
col = {
active = secondary,
inactive = surface,
locked_active = error,
locked_inactive = surface,
},
},
},
})
end
return {
colors = {
primary = primary,
surface = surface,
secondary = secondary,
error = error,
},
apply_theme = apply_theme
}
+5
View File
@@ -0,0 +1,5 @@
--hl.env("AQ_DRM_DEVICES", "/dev/dri/card1")
-- Check the environment variable
--hl.env("AQ_DRM_DEVICES", "/dev/dri/card1:/dev/dri/renderD128")
+741
View File
@@ -0,0 +1,741 @@
{
"settingsVersion": 59,
"bar": {
"barType": "simple",
"position": "top",
"monitors": [],
"density": "default",
"showOutline": false,
"showCapsule": true,
"capsuleOpacity": 1,
"capsuleColorKey": "none",
"widgetSpacing": 6,
"contentPadding": 2,
"fontScale": 1,
"enableExclusionZoneInset": true,
"backgroundOpacity": 0.93,
"useSeparateOpacity": false,
"marginVertical": 4,
"marginHorizontal": 4,
"frameThickness": 8,
"frameRadius": 12,
"outerCorners": true,
"hideOnOverview": false,
"displayMode": "always_visible",
"autoHideDelay": 500,
"autoShowDelay": 150,
"showOnWorkspaceSwitch": true,
"widgets": {
"left": [
{
"colorizeSystemIcon": "none",
"colorizeSystemText": "none",
"customIconPath": "",
"enableColorization": false,
"icon": "rocket",
"iconColor": "none",
"id": "Launcher",
"useDistroLogo": false
},
{
"clockColor": "primary",
"customFont": "",
"formatHorizontal": "HH:mm ddd, MMM dd",
"formatVertical": "HH mm - dd MM",
"id": "Clock",
"tooltipFormat": "HH:mm ddd, MMM dd",
"useCustomFont": false
},
{
"compactMode": true,
"diskPath": "/",
"iconColor": "none",
"id": "SystemMonitor",
"showCpuCores": false,
"showCpuFreq": false,
"showCpuTemp": true,
"showCpuUsage": true,
"showDiskAvailable": false,
"showDiskUsage": false,
"showDiskUsageAsPercent": false,
"showGpuTemp": false,
"showLoadAverage": false,
"showMemoryAsPercent": false,
"showMemoryUsage": true,
"showNetworkStats": true,
"showSwapUsage": false,
"textColor": "none",
"useMonospaceFont": true,
"usePadding": false
},
{
"colorizeIcons": false,
"hideMode": "hidden",
"id": "ActiveWindow",
"maxWidth": 145,
"scrollingMode": "hover",
"showIcon": true,
"showText": true,
"textColor": "none",
"useFixedWidth": false
}
],
"center": [
{
"characterCount": 4,
"colorizeIcons": false,
"emptyColor": "secondary",
"enableScrollWheel": true,
"focusedColor": "primary",
"followFocusedScreen": false,
"fontWeight": "bold",
"groupedBorderOpacity": 1,
"hideUnoccupied": false,
"iconScale": 0.8,
"id": "Workspace",
"labelMode": "name",
"occupiedColor": "secondary",
"pillSize": 0.6,
"showApplications": true,
"showApplicationsHover": true,
"showBadge": true,
"showLabelsOnlyWhenOccupied": true,
"unfocusedIconsOpacity": 1
}
],
"right": [
{
"compactMode": false,
"hideMode": "hidden",
"hideWhenIdle": false,
"id": "MediaMini",
"maxWidth": 145,
"panelShowAlbumArt": true,
"scrollingMode": "hover",
"showAlbumArt": true,
"showArtistFirst": true,
"showProgressRing": true,
"showVisualizer": false,
"textColor": "none",
"useFixedWidth": false,
"visualizerType": "linear"
},
{
"blacklist": [
"nm-applet"
],
"chevronColor": "none",
"colorizeIcons": false,
"drawerEnabled": true,
"hidePassive": false,
"id": "Tray",
"pinned": []
},
{
"hideWhenZero": false,
"hideWhenZeroUnread": false,
"iconColor": "none",
"id": "NotificationHistory",
"showUnreadBadge": true,
"unreadBadgeColor": "primary"
},
{
"deviceNativePath": "__default__",
"displayMode": "graphic-clean",
"hideIfIdle": false,
"hideIfNotDetected": true,
"id": "Battery",
"showNoctaliaPerformance": false,
"showPowerProfiles": false
},
{
"displayMode": "onhover",
"iconColor": "none",
"id": "Volume",
"middleClickCommand": "pwvucontrol || pavucontrol",
"textColor": "none"
},
{
"applyToAllMonitors": false,
"displayMode": "onhover",
"iconColor": "none",
"id": "Brightness",
"textColor": "none"
},
{
"colorizeDistroLogo": false,
"colorizeSystemIcon": "none",
"colorizeSystemText": "none",
"customIconPath": "",
"enableColorization": false,
"icon": "noctalia",
"id": "ControlCenter",
"useDistroLogo": false
}
]
},
"mouseWheelAction": "none",
"reverseScroll": false,
"mouseWheelWrap": true,
"middleClickAction": "none",
"middleClickFollowMouse": false,
"middleClickCommand": "",
"rightClickAction": "controlCenter",
"rightClickFollowMouse": true,
"rightClickCommand": "",
"screenOverrides": []
},
"general": {
"avatarImage": "/home/domenico/.face",
"dimmerOpacity": 0.2,
"showScreenCorners": false,
"forceBlackScreenCorners": false,
"scaleRatio": 1,
"radiusRatio": 1,
"iRadiusRatio": 1,
"boxRadiusRatio": 1,
"screenRadiusRatio": 1,
"animationSpeed": 1,
"animationDisabled": false,
"compactLockScreen": false,
"lockScreenAnimations": false,
"lockOnSuspend": true,
"showSessionButtonsOnLockScreen": true,
"showHibernateOnLockScreen": false,
"enableLockScreenMediaControls": false,
"enableShadows": true,
"enableBlurBehind": true,
"shadowDirection": "bottom_right",
"shadowOffsetX": 2,
"shadowOffsetY": 3,
"language": "",
"allowPanelsOnScreenWithoutBar": true,
"showChangelogOnStartup": true,
"telemetryEnabled": true,
"enableLockScreenCountdown": true,
"lockScreenCountdownDuration": 10000,
"autoStartAuth": false,
"allowPasswordWithFprintd": false,
"clockStyle": "custom",
"clockFormat": "hh\\nmm",
"passwordChars": false,
"lockScreenMonitors": [],
"lockScreenBlur": 0,
"lockScreenTint": 0,
"keybinds": {
"keyUp": [
"Up"
],
"keyDown": [
"Down"
],
"keyLeft": [
"Left"
],
"keyRight": [
"Right"
],
"keyEnter": [
"Return",
"Enter"
],
"keyEscape": [
"Esc"
],
"keyRemove": [
"Del"
]
},
"reverseScroll": false,
"smoothScrollEnabled": true
},
"ui": {
"fontDefault": "Noto Sans",
"fontFixed": "monospace",
"fontDefaultScale": 1,
"fontFixedScale": 1,
"tooltipsEnabled": true,
"scrollbarAlwaysVisible": true,
"boxBorderEnabled": false,
"panelBackgroundOpacity": 0.93,
"translucentWidgets": false,
"panelsAttachedToBar": true,
"settingsPanelMode": "attached",
"settingsPanelSideBarCardStyle": false
},
"location": {
"name": "Mercato San Severino",
"weatherEnabled": true,
"weatherShowEffects": true,
"weatherTaliaMascotAlways": false,
"useFahrenheit": false,
"use12hourFormat": false,
"showWeekNumberInCalendar": false,
"showCalendarEvents": true,
"showCalendarWeather": true,
"analogClockInCalendar": false,
"firstDayOfWeek": -1,
"hideWeatherTimezone": false,
"hideWeatherCityName": false,
"autoLocate": false
},
"calendar": {
"cards": [
{
"enabled": true,
"id": "calendar-header-card"
},
{
"enabled": true,
"id": "calendar-month-card"
},
{
"enabled": true,
"id": "weather-card"
}
]
},
"wallpaper": {
"enabled": true,
"overviewEnabled": true,
"directory": "/home/domenico/Immagini/Wallpapers",
"monitorDirectories": [],
"enableMultiMonitorDirectories": false,
"showHiddenFiles": false,
"viewMode": "single",
"setWallpaperOnAllMonitors": true,
"linkLightAndDarkWallpapers": true,
"fillMode": "crop",
"fillColor": "#000000",
"useSolidColor": false,
"solidColor": "#1a1a2e",
"automationEnabled": false,
"wallpaperChangeMode": "random",
"randomIntervalSec": 300,
"transitionDuration": 1500,
"transitionType": [
"fade",
"disc",
"stripes",
"wipe",
"pixelate",
"honeycomb"
],
"skipStartupTransition": false,
"transitionEdgeSmoothness": 0.05,
"panelPosition": "center",
"hideWallpaperFilenames": false,
"useOriginalImages": false,
"overviewBlur": 0.15,
"overviewTint": 0.6,
"useWallhaven": false,
"wallhavenQuery": "",
"wallhavenSorting": "relevance",
"wallhavenOrder": "desc",
"wallhavenCategories": "111",
"wallhavenPurity": "100",
"wallhavenRatios": "",
"wallhavenApiKey": "",
"wallhavenResolutionMode": "atleast",
"wallhavenResolutionWidth": "",
"wallhavenResolutionHeight": "",
"sortOrder": "name",
"favorites": []
},
"appLauncher": {
"enableClipboardHistory": true,
"autoPasteClipboard": false,
"enableClipPreview": true,
"clipboardWrapText": true,
"enableClipboardSmartIcons": true,
"enableClipboardChips": true,
"clipboardWatchTextCommand": "wl-paste --type text --watch cliphist store",
"clipboardWatchImageCommand": "wl-paste --type image --watch cliphist store",
"position": "center",
"pinnedApps": [],
"sortByMostUsed": true,
"terminalCommand": "kitty -e",
"customLaunchPrefixEnabled": false,
"customLaunchPrefix": "",
"viewMode": "list",
"showCategories": true,
"iconMode": "tabler",
"showIconBackground": false,
"enableSettingsSearch": true,
"enableWindowsSearch": true,
"enableSessionSearch": true,
"ignoreMouseInput": false,
"screenshotAnnotationTool": "",
"overviewLayer": false,
"density": "default"
},
"controlCenter": {
"position": "close_to_bar_button",
"diskPath": "/",
"shortcuts": {
"left": [
{
"id": "Network"
},
{
"defaultSettings": {
"connectedColor": "primary",
"disableToastNotifications": false,
"disconnectedColor": "none",
"displayMode": "onhover"
},
"id": "plugin:network-manager-vpn"
},
{
"id": "Bluetooth"
},
{
"id": "NoctaliaPerformance"
}
],
"right": [
{
"id": "Notifications"
},
{
"id": "PowerProfile"
},
{
"id": "KeepAwake"
},
{
"id": "NightLight"
}
]
},
"cards": [
{
"enabled": true,
"id": "profile-card"
},
{
"enabled": true,
"id": "shortcuts-card"
},
{
"enabled": true,
"id": "audio-card"
},
{
"enabled": false,
"id": "brightness-card"
},
{
"enabled": true,
"id": "weather-card"
},
{
"enabled": true,
"id": "media-sysmon-card"
}
]
},
"systemMonitor": {
"cpuWarningThreshold": 80,
"cpuCriticalThreshold": 90,
"tempWarningThreshold": 80,
"tempCriticalThreshold": 90,
"gpuWarningThreshold": 80,
"gpuCriticalThreshold": 90,
"memWarningThreshold": 80,
"memCriticalThreshold": 90,
"swapWarningThreshold": 80,
"swapCriticalThreshold": 90,
"diskWarningThreshold": 80,
"diskCriticalThreshold": 90,
"diskAvailWarningThreshold": 20,
"diskAvailCriticalThreshold": 10,
"batteryWarningThreshold": 20,
"batteryCriticalThreshold": 5,
"enableDgpuMonitoring": false,
"useCustomColors": false,
"warningColor": "#94e2d5",
"criticalColor": "#f38ba8",
"externalMonitor": "resources || missioncenter || jdsystemmonitor || corestats || system-monitoring-center || gnome-system-monitor || plasma-systemmonitor || mate-system-monitor || ukui-system-monitor || deepin-system-monitor || pantheon-system-monitor"
},
"noctaliaPerformance": {
"disableWallpaper": true,
"disableDesktopWidgets": true
},
"dock": {
"enabled": false,
"position": "bottom",
"displayMode": "auto_hide",
"dockType": "floating",
"backgroundOpacity": 1,
"floatingRatio": 1,
"size": 1,
"onlySameOutput": true,
"monitors": [],
"pinnedApps": [],
"colorizeIcons": false,
"showLauncherIcon": false,
"launcherPosition": "end",
"launcherUseDistroLogo": false,
"launcherIcon": "",
"launcherIconColor": "none",
"pinnedStatic": false,
"inactiveIndicators": false,
"groupApps": false,
"groupContextMenuMode": "extended",
"groupClickAction": "cycle",
"groupIndicatorStyle": "dots",
"deadOpacity": 0.6,
"animationSpeed": 1,
"sitOnFrame": false,
"showDockIndicator": false,
"indicatorThickness": 3,
"indicatorColor": "primary",
"indicatorOpacity": 0.6
},
"network": {
"bluetoothRssiPollingEnabled": false,
"bluetoothRssiPollIntervalMs": 60000,
"networkPanelView": "ethernet",
"wifiDetailsViewMode": "grid",
"bluetoothDetailsViewMode": "list",
"bluetoothHideUnnamedDevices": false,
"disableDiscoverability": false,
"bluetoothAutoConnect": true
},
"sessionMenu": {
"enableCountdown": true,
"countdownDuration": 10000,
"position": "center",
"showHeader": true,
"showKeybinds": true,
"largeButtonsStyle": true,
"largeButtonsLayout": "single-row",
"powerOptions": [
{
"action": "lock",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "1"
},
{
"action": "suspend",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "2"
},
{
"action": "hibernate",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "3"
},
{
"action": "reboot",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "4"
},
{
"action": "logout",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "5"
},
{
"action": "shutdown",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "6"
},
{
"action": "rebootToUefi",
"command": "",
"countdownEnabled": true,
"enabled": true,
"keybind": "7"
},
{
"action": "userspaceReboot",
"command": "",
"countdownEnabled": true,
"enabled": false,
"keybind": ""
}
]
},
"notifications": {
"enabled": true,
"enableMarkdown": true,
"density": "default",
"monitors": [],
"location": "top_right",
"overlayLayer": true,
"backgroundOpacity": 0.75,
"respectExpireTimeout": false,
"lowUrgencyDuration": 3,
"normalUrgencyDuration": 8,
"criticalUrgencyDuration": 15,
"clearDismissed": true,
"saveToHistory": {
"low": true,
"normal": true,
"critical": true
},
"sounds": {
"enabled": false,
"volume": 0.5,
"separateSounds": false,
"criticalSoundFile": "",
"normalSoundFile": "",
"lowSoundFile": "",
"excludedApps": "discord,firefox,chrome,chromium,edge"
},
"enableMediaToast": false,
"enableKeyboardLayoutToast": true,
"enableBatteryToast": true
},
"osd": {
"enabled": true,
"location": "top_right",
"autoHideMs": 2000,
"overlayLayer": true,
"backgroundOpacity": 1,
"enabledTypes": [
0,
1,
2
],
"monitors": []
},
"audio": {
"volumeStep": 5,
"volumeOverdrive": false,
"spectrumFrameRate": 30,
"visualizerType": "linear",
"spectrumMirrored": true,
"mprisBlacklist": [],
"preferredPlayer": "spotify",
"volumeFeedback": false,
"volumeFeedbackSoundFile": ""
},
"brightness": {
"brightnessStep": 5,
"enforceMinimum": true,
"enableDdcSupport": false,
"backlightDeviceMappings": [
{
"device": "/sys/class/backlight/intel_backlight",
"output": "eDP-1"
}
]
},
"colorSchemes": {
"useWallpaperColors": false,
"predefinedScheme": "Catppuccin",
"darkMode": true,
"schedulingMode": "off",
"manualSunrise": "06:30",
"manualSunset": "18:30",
"generationMethod": "tonal-spot",
"monitorForColors": "",
"syncGsettings": true
},
"templates": {
"activeTemplates": [
{
"enabled": true,
"id": "btop"
},
{
"enabled": true,
"id": "discord"
},
{
"enabled": true,
"id": "hyprland"
},
{
"enabled": true,
"id": "hyprtoolkit"
},
{
"enabled": true,
"id": "kitty"
},
{
"enabled": true,
"id": "starship"
},
{
"enabled": true,
"id": "code"
},
{
"enabled": true,
"id": "telegram"
},
{
"enabled": true,
"id": "qt"
},
{
"enabled": true,
"id": "steam"
}
],
"enableUserTheming": false
},
"nightLight": {
"enabled": false,
"forced": false,
"autoSchedule": true,
"nightTemp": "4000",
"dayTemp": "6500",
"manualSunrise": "06:30",
"manualSunset": "18:30"
},
"hooks": {
"enabled": false,
"wallpaperChange": "",
"darkModeChange": "",
"screenLock": "",
"screenUnlock": "",
"performanceModeEnabled": "",
"performanceModeDisabled": "",
"startup": "",
"session": "",
"colorGeneration": ""
},
"plugins": {
"autoUpdate": false,
"notifyUpdates": true
},
"idle": {
"enabled": false,
"screenOffTimeout": 600,
"lockTimeout": 660,
"suspendTimeout": 1800,
"fadeDuration": 5,
"screenOffCommand": "",
"lockCommand": "",
"suspendCommand": "",
"resumeScreenOffCommand": "",
"resumeLockCommand": "",
"resumeSuspendCommand": "",
"customCommands": "[]"
},
"desktopWidgets": {
"enabled": true,
"overviewEnabled": true,
"gridSnap": false,
"gridSnapScale": true,
"monitorWidgets": []
}
}
+53 -1
View File
@@ -1,5 +1,5 @@
local inFocusOpacity = "0.95" local inFocusOpacity = "0.95"
local notInFocusOpacity = "0.8" local notInFocusOpacity = "0.85"
@@ -21,8 +21,26 @@ hl.window_rule({
pin = 0 pin = 0
} }
}) })
-- Noctalia
hl.window_rule({
opacity = "1",
match = { class = "dev.noctalia.Noctalia" },
float = true,
size = { 1080, 920 },
})
hl.layer_rule({
name = "noctalia",
match = {
namespace = "^noctalia-(bar-.+|notification|dock|panel|attached-panel|osd|window-switcher)$",
},
no_anim = true,
ignore_alpha = 0.5,
blur = true,
blur_popups = true,
})
-- END Noctalia
hl.window_rule({ hl.window_rule({
opacity = inFocusOpacity .. " " .. notInFocusOpacity, opacity = inFocusOpacity .. " " .. notInFocusOpacity,
match = { title = "^.*().*$" } match = { title = "^.*().*$" }
@@ -88,3 +106,37 @@ hl.window_rule({
class="Chromium-browser", class="Chromium-browser",
} }
}) })
hl.window_rule({
opacity = "1.0",
match = {
class = "vlc"
}
})
-- fullscreen App
hl.window_rule({
opacity = "1.0",
fullscreen = true,
match = {
class ="blender"
}
})
hl.window_rule({
opacity = "1.0",
float = true,
match = { class = "org.kde.ksecretd|gcr-prompter"}
})
-- Floating terminal
hl.window_rule({
opacity = "0.75 0.25",
no_blur = true,
float = true,
pin = true,
match = {class = "kitty", title = "Floating"},
size = {430, 414},
move = {"(cursor_x - (window_w*0.5))", "(cursor_y-(window_h*0.5))"}
})
+24 -16
View File
@@ -1,29 +1,25 @@
local music_workspace = "music" local music_workspace = "music"
local voicechat_workspace = "voice" local voicechat_workspace = "voice"
local obs_workspace = "obs" local obs_workspace = "obs"
local social_workspace = "social"
local numkeys = {
{code = "87"}, -- 1
{code = "88"}, -- 2
{code = "89"}, -- 3
{code = "83"}, -- 4
{code = "84"}, -- 5
{code = "85"}, -- 6
{code = "79"}, -- 7
{code = "80"}, -- 8
{code = "81"}, -- 9
{code = "90"} -- 0
}
-- Keybinds that define workspaces -- Keybinds that define workspaces
hl.bind(mainMod .. " + code:"..numkeys[7].code, hl.dsp.workspace.toggle_special(music_workspace)) hl.bind(mainMod .. " + code:"..NUM_KEYS[5].code, hl.dsp.workspace.toggle_special(social_workspace))
hl.bind(mainMod .. " + code:"..numkeys[8].code, hl.dsp.workspace.toggle_special(voicechat_workspace)) hl.bind(mainMod .. " + code:"..NUM_KEYS[7].code, hl.dsp.workspace.toggle_special(music_workspace))
hl.bind(mainMod .. " + code:"..numkeys[9].code, hl.dsp.workspace.toggle_special(obs_workspace)) hl.bind(mainMod .. " + code:"..NUM_KEYS[8].code, hl.dsp.workspace.toggle_special(voicechat_workspace))
hl.bind(mainMod .. " + code:"..NUM_KEYS[9].code, hl.dsp.workspace.toggle_special(obs_workspace))
--hl.workspace_rule({workspace = "special:" .. voicechat_workspace,
-- persistent = true
--})
--hl.workspace_rule({workspace = "special:" .. social_workspace,
-- persistent = true
--})
hl.window_rule({ hl.window_rule({
name = "discord", name = "discord",
match = {class = "^(discord)$"}, match = {class = "^(discord|vesktop)$"},
workspace = "special:" .. voicechat_workspace workspace = "special:" .. voicechat_workspace
}) })
@@ -38,3 +34,15 @@ hl.window_rule({
match = {class = "com.obsproject.Studio"}, match = {class = "com.obsproject.Studio"},
workspace = "special:" .. obs_workspace workspace = "special:" .. obs_workspace
}) })
hl.window_rule({
name = "Social installed with vivaldi",
match = {title = "WhatsApp Web|X|Instagram"},
workspace = "special:" .. social_workspace
})
hl.window_rule({
name = "telegram",
match = {class = "org.telegram.desktop"},
workspace = "special:" .. social_workspace
})
+18
View File
@@ -0,0 +1,18 @@
{
"mError": "#f38ba8",
"mHover": "#94e2d5",
"mOnError": "#11111b",
"mOnHover": "#11111b",
"mOnPrimary": "#11111b",
"mOnSecondary": "#11111b",
"mOnSurface": "#cdd6f4",
"mOnSurfaceVariant": "#a3b4eb",
"mOnTertiary": "#11111b",
"mOutline": "#4c4f69",
"mPrimary": "#cba6f7",
"mSecondary": "#fab387",
"mShadow": "#11111b",
"mSurface": "#1e1e2e",
"mSurfaceVariant": "#313244",
"mTertiary": "#94e2d5"
}
@@ -0,0 +1,94 @@
{
"dark": {
"mPrimary": "#7a88cf",
"mOnPrimary": "#1f2335",
"mSecondary": "#d7729f",
"mOnSecondary": "#1f2335",
"mTertiary": "#9cd58a",
"mOnTertiary": "#1f2335",
"mError": "#f7768e",
"mOnError": "#1f2335",
"mSurface": "#1f2335",
"mOnSurface": "#a9b1d6",
"mSurfaceVariant": "#2c314a",
"mOnSurfaceVariant": "#c0caf5",
"mOutline": "#4b517a",
"mShadow": "#181b2a",
"mHover": "#9cd58a",
"mOnHover": "#1f2335",
"terminal": {
"foreground": "#a9b1d6",
"background": "#1f2335",
"selectionFg": "#a9b1d6",
"selectionBg": "#4b517a",
"cursorText": "#1f2335",
"cursor": "#a9b1d6",
"normal": {
"black": "#1f2335",
"red": "#f7768e",
"green": "#9cd58a",
"yellow": "#e6c384",
"blue": "#7a88cf",
"magenta": "#d7729f",
"cyan": "#7bb0c0",
"white": "#8289a6"
},
"bright": {
"black": "#545c7e",
"red": "#f7768e",
"green": "#9cd58a",
"yellow": "#e6c384",
"blue": "#7a88cf",
"magenta": "#d7729f",
"cyan": "#7bb0c0",
"white": "#a9b1d6"
}
}
},
"light": {
"mPrimary": "#4e70c2",
"mOnPrimary": "#f0f2f7",
"mSecondary": "#85539c",
"mOnSecondary": "#f0f2f7",
"mTertiary": "#4c663b",
"mOnTertiary": "#f0f2f7",
"mError": "#e55561",
"mOnError": "#f0f2f7",
"mSurface": "#f0f2f7",
"mOnSurface": "#4e70c2",
"mSurfaceVariant": "#dbdfea",
"mOnSurfaceVariant": "#444b6a",
"mOutline": "#c8ccd8",
"mShadow": "#a4a8b7",
"mHover": "#4c663b",
"mOnHover": "#f0f2f7",
"terminal": {
"foreground": "#4e70c2",
"background": "#f0f2f7",
"selectionFg": "#4e70c2",
"selectionBg": "#c8ccd8",
"cursorText": "#f0f2f7",
"cursor": "#4e70c2",
"normal": {
"black": "#f0f2f7",
"red": "#e55561",
"green": "#4c663b",
"yellow": "#9e8c6b",
"blue": "#4e70c2",
"magenta": "#85539c",
"cyan": "#569b9f",
"white": "#6a708a"
},
"bright": {
"black": "#939ab7",
"red": "#d26e7a",
"green": "#5e7250",
"yellow": "#9e8c6b",
"blue": "#5e7bba",
"magenta": "#85539c",
"cyan": "#569b9f",
"white": "#6a708a"
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
{
"sources": [
{
"enabled": true,
"name": "Noctalia Plugins",
"url": "https://github.com/noctalia-dev/noctalia-plugins"
}
],
"states": {
"clipper": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
},
"fancy-audiovisualizer": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
},
"network-manager-vpn": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
},
"polkit-agent": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
},
"wallcards": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
},
"workspace-overview": {
"enabled": true,
"sourceUrl": "https://github.com/noctalia-dev/noctalia-plugins"
}
},
"version": 2
}
+16
View File
@@ -0,0 +1,16 @@
# Backup files
*.backup
*.bak
*.tmp
# Runtime files
pinned.json
settings.json
# Build artifacts
*.qmlc
# Development scripts
i18n/all_languages.sh
# User notecards - personal data
notecards/*.json
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

+75
View File
@@ -0,0 +1,75 @@
import QtQuick
import Quickshell
import qs.Commons
import qs.Services.UI
import qs.Widgets
NIconButton {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
icon: "clipboard-data"
tooltipText: pluginApi?.tr("bar.tooltip")
tooltipDirection: BarService.getTooltipDirection(screen?.name)
baseSize: Style.getCapsuleHeightForScreen(screen?.name)
applyUiScale: false
customRadius: Style.radiusL
colorBg: Style.capsuleColor
colorFg: Color.mOnSurface
colorBgHover: Color.mHover
colorFgHover: Color.mOnHover
colorBorder: "transparent"
colorBorderHover: "transparent"
border.color: Style.capsuleBorderColor
border.width: Style.capsuleBorderWidth
NPopupContextMenu {
id: contextMenu
model: [
{
"label": pluginApi?.tr("context.toggle"),
"action": "toggle-clipper",
"icon": "clipboard"
},
{
"label": pluginApi?.tr("context.settings"),
"action": "open-settings",
"icon": "settings"
},
]
onTriggered: action => {
contextMenu.close();
PanelService.closeContextMenu(screen);
if (action === "toggle-clipper") {
if (pluginApi?.togglePanel) {
pluginApi.togglePanel(screen);
}
} else if (action === "open-settings") {
if (pluginApi?.manifest) {
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
}
}
onClicked: {
if (pluginApi?.openPanel) {
pluginApi.openPanel(screen, this);
}
}
onRightClicked: {
PanelService.showContextMenu(contextMenu, root, screen);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,274 @@
# Clipper Plugin - Changelog
## Version 2.0.0 (2026-02-05)
### 🎯 Major Changes
**New Feature: Add Selection to NoteCard**
- New IPC command: `addSelectionToNoteCard`
- Captures selected text and presents context menu to add to existing notes or create new note
- Text is automatically formatted as bullet points with "- " prefix
- Integrated with Hyprland keybind: Super+V, X (chord)
**Translation System Overhaul**
- Migrated from `I18n.tr()` to `pluginApi?.tr()` across all files
- Added comprehensive `i18n/en.json` with 28 toast message keys
- All user-facing strings now use translation system with fallbacks
- Translation key structure: `component.key` (e.g., `toast.note-created`)
**Architecture Improvements**
- Fixed IPC handlers to use `pluginApi.withCurrentScreen()` instead of direct `Quickshell.screens[0]` access
- Added proper Component.onDestruction cleanup for 6 data structures (pinnedItems, noteCards, items, firstSeenById, imageCache, imageCacheOrder)
- Memory leak prevention with proper process termination
- Unified translation syntax: `pluginApi?.tr("key") || "fallback"`
### 📁 New Files
1. **NoteCardSelector.qml** (173 lines)
- Fullscreen overlay for note selection menu
- Synchronously loads notecards from files
- Shows "Create New Note" + list of existing notes
- Mouse-position-aware context menu
- ESC to cancel, click outside to close
2. **NoteCard.qml** (~300 lines)
- Updated visual style to match ClipboardCard
- Icon + title left-aligned (not centered)
- Transparent buttons without background
- Increased font sizes (13-14px)
- Auto-height expansion based on content
- Drag handle on icon only (24px width)
- All corners rounded on main card
- Only top corners rounded on header
3. **NoteCardsPanel.qml** (updated)
- Background click-to-close functionality
- Controls moved to top-left corner
- Note counter and add button repositioned
- Starting Y position increased to 80px to avoid control overlap
### 🔧 Files Modified
**Main.qml** (+250 lines, massive refactoring)
*New IPC Handlers:*
- `addSelectionToNoteCard()` - Main entry point for feature
- Uses same pattern as `addSelectionToTodo()` for consistency
*New Functions:*
- `getSelectionAndShowNoteSelector()` - Triggers selection capture
- `showNoteCardSelector(text)` - Displays note selection menu
- `handleNoteCardSelected(noteId, noteTitle)` - Handles existing note selection
- `handleCreateNewNoteFromSelection()` - Creates new note with bullet text
- `appendTextToNoteCard(noteId, text)` - Adds bullet point to existing note
*New Process:*
- `getSelectionForNoteSelectorProcess` - Captures primary selection via wl-paste
- StdioCollector: `noteSelectionStdout`
- Calls `showNoteCardSelector()` on successful capture
*New Variants:*
- `NoteCardSelector` instances for each screen
- Registered as `noteCardSelector` property
- Connected to signal handlers for note selection/creation
*New Properties:*
- `noteCardSelector: null` - Reference to selector instance
- `pendingNoteCardText: ""` - Stores captured selection
*Translation Updates:*
- All 28 Toast messages now use `pluginApi?.tr()`
- String interpolation for dynamic values ({max}, {fileName}, {count})
- Consistent error handling with translated messages
*Bug Fixes:*
- Fixed `createNoteCard()` to call `saveNoteCard()` after creation
- Removed duplicate `saveNoteCard(newNote)` calls from wrong functions
- Fixed Component.onDestruction syntax error (duplicate `}`)
**i18n/en.json** (+30 keys)
*New Toast Section:*
```json
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"note-created": "Note created",
"note-not-found": "Note not found",
"text-added-to-note": "Text added to note",
"could-not-open-note-selector": "Could not open note selector",
// ... 22 more keys
}
```
**NoteCard.qml** (visual redesign)
- Header layout changed from centered to left-aligned
- Icon (note) + title in RowLayout
- Drag handle isolated to icon Item (24px width)
- Separate MouseArea for title focus
- Transparent button styling: `colorBg: "transparent"`
- Font changes: removed bold, increased size to 14px
- Radius system:
- Main card: `radius: Style.radiusM` (all corners)
- Header: `topLeftRadius/topRightRadius: Style.radiusM`, bottom: 0
- Auto-height expansion via ScrollView content measurement
- Size changed: 300x180 → 350x280px
- Title input wider, better usability
**NoteCardsPanel.qml**
- Background MouseArea with `z: -1` for click-to-close
- Controls repositioned to `anchors.top` + `anchors.left`
- Note counter + add button in top-left corner
- 16px margins from edges
**ClipboardCard.qml**
- Applied same radius pattern as NoteCard
- Main card: all corners rounded
- Header: top corners only
**BarWidget.qml**
- Migrated `I18n.tr()``pluginApi?.tr()`
- Keys: `bar.tooltip`, `context.toggle`, `context.settings`
**Settings.qml**
- Already had full translation coverage
- Verified all labels use `pluginApi?.tr()`
### 🎨 UI/UX Changes
**NoteCard Visual Updates:**
- More modern, cleaner look matching ClipboardCard
- Better readability with larger fonts
- Improved drag interaction (icon-only handle)
- Responsive to global radius settings (Style.radiusM)
- Auto-expands height based on content
**NoteCardsPanel Improvements:**
- Better spatial organization (controls top-left)
- No accidental close when clicking on notes
- Clear visual hierarchy
**Context Menu for Note Selection:**
- Appears at cursor position
- Smooth 150ms delay for compositor
- First option always "Create New Note"
- Separator before existing notes
- Note icons for visual distinction
### ⚙️ Technical Improvements
**Memory Management:**
- Cleanup of 6 data structures in Component.onDestruction
- Process termination for `getSelectionForNoteSelectorProcess`
- Proper array clearing: `items = []`, `imageCache = {}`
**Code Quality:**
- Consistent translation syntax across codebase
- No hardcoded user-facing strings
- Proper null-safe operators: `pluginApi?.tr()`
- Immutable array updates maintained
**IPC Architecture:**
- Correct use of `pluginApi.withCurrentScreen()`
- No direct `Quickshell.screens` access in IPC handlers
- Consistent pattern with existing `addSelectionToTodo`
### 🐛 Bug Fixes
1. **Syntax Errors:**
- Fixed duplicate `}` in Component.onDestruction
- Removed extra closing braces in function declarations
- Fixed missing `} catch(e) {` block in loadNoteCardsProc
2. **Runtime Errors:**
- Fixed `ReferenceError: newNote is not defined` by removing invalid `saveNoteCard(newNote)` calls from functions that don't have access to `newNote`
- Only `createNoteCard()` calls `saveNoteCard(newNote)` now
3. **Functional Issues:**
- Fixed note selector not showing existing notes (timing issue with async loading)
- Fixed "Create New Note" not working (signal connection verified)
- Fixed notes not being saved to files (added `saveNoteCard()` call)
### 📝 Usage Examples
**IPC Commands:**
```bash
# Add selected text to note via keybind
qs -c noctalia-shell ipc call plugin:clipper addSelectionToNoteCard
# Or with dev repo
qs -c ~/dev/repo/noctalia ipc call plugin:clipper addSelectionToNoteCard
```
**Hyprland Keybind (chord):**
```bash
# In ~/.config/hypr/keybind.conf
binds = SUPER_L, V&C, exec, qs -c ~/dev/repo/noctalia ipc call plugin:clipper addSelectionToTodo
binds = SUPER_L, V&X, exec, qs -c ~/dev/repo/noctalia ipc call plugin:clipper addSelectionToNoteCard
```
**User Flow:**
1. Select text anywhere
2. Press Super+V (opens chord mode)
3. Press X (triggers addSelectionToNoteCard)
4. Context menu appears at cursor
5. Click "Create New Note" or select existing note
6. Text is added as bullet point: `- selected text`
### 📊 Statistics
**Lines Changed:**
- Main.qml: +250 lines
- NoteCard.qml: ~300 lines (redesigned)
- NoteCardSelector.qml: +173 lines (new)
- NoteCardsPanel.qml: +50 lines
- i18n/en.json: +30 keys
- Total: ~800 lines added/modified
**Files:**
- New: 1 file (NoteCardSelector.qml)
- Modified: 7 files
- Total: 8 files changed
**Translation Coverage:**
- 28 new toast message keys
- 100% coverage for user-facing strings
- All components use pluginApi?.tr()
### 🔄 Breaking Changes
**Translation System:**
- Old: `I18n.tr("clipboard.key", "Fallback")`
- New: `pluginApi?.tr("component.key") || "Fallback"`
- All plugins must update if they were using I18n
**IPC Handlers:**
- Now use `pluginApi.withCurrentScreen()` instead of `Quickshell.screens[0]`
- More robust multi-screen support
### 🚀 Next Steps (Future Enhancements)
1. **i18n Completion:**
- Translate en.json to all other language files (de, es, fr, etc.)
- Maintain identical structure across all languages
2. **Feature Enhancements:**
- Resizable note cards
- Rich text formatting
- Note categories/tags
- Search functionality
3. **Performance:**
- Lazy loading for large note collections
- Virtual scrolling if >20 notes
4. **Integration:**
- Share notes between devices
- Sync with cloud services
---
**Version:** 2.0.0
**Date:** 2026-02-05
**Author:** Development Team
**Reviewed by:** Code Review (QML-code-reviewer.md)
@@ -0,0 +1,73 @@
# Changelog v2.1.0
## Release Date
2026-02-08
## New Features
### Show Close Button
- Added optional X button in top-right corner of panel header
- Toggle available in Settings > General
- Provides alternative way to close the panel
- Button styled with error color on hover for clear visual feedback
## Bug Fixes
### Clipboard Selection Fix
- Fixed critical bug where only the last copied item could be selected from history
- Fixed issue where clipboard items couldn't be copied back to system clipboard
- Changed from two-process approach to direct shell pipe: `cliphist decode ID | wl-copy`
- Removed non-existent `stdout.data` API usage
### Note Persistence Fix
- Fixed bug where notes didn't persist after reopening panel
- Added missing `saveNoteCard()` call in `updateNoteCard()` function
- Notes now properly save to disk when modified
## Improvements
### Settings UI Enhancement
- Reorganized settings into tabbed interface using NTabs
- **General Tab**: Integrations, Features (PinCards/NoteCards toggles), Show Close Button
- **Appearance Tab**: Card color customization
- Improved settings organization and discoverability
- Added feature toggles with item counters showing current usage
### Translation System Synchronization
- Synchronized all 16 language files with consistent structure
- Removed unused translation keys
- All translation files now have identical key structure
- Maintained existing translations while adding new keys
### Code Quality
- Removed experimental "Close on Background Click" feature (not implementable in current Noctalia architecture)
- Cleaned up debug logging
- Removed temporary backup files
- Improved code organization
## Technical Details
### Fixed Functions
- `copyToClipboard()` in Main.qml (line 742-755)
- `updateNoteCard()` in Main.qml (added saveNoteCard call)
### Modified Components
- Settings.qml: Restructured with NTabBar/NTabView
- Panel.qml: Added optional close button with reactive visibility
- i18n/*.json: All 16 language files synchronized
### Removed Features
- "Close on Background Click" toggle and related code
- Panel behavior section in settings
- Related translation keys and properties
## Breaking Changes
None - all changes are backward compatible
## Known Issues
None
## Upgrade Notes
- Plugin will automatically migrate settings
- No user action required
- Existing pinned items and notecards will be preserved
@@ -0,0 +1,70 @@
# Changelog v2.2.3
## Release Date
**2026-03-16**
## New Features
#### Control Center Shortcut
- Added Control Center Widget shortcut to Control Center
## Bug Fixes
#### Clear All Notes Fix
- Fixed the bug where you click Clear All Notes and reopen the plugin panel but the notes are still there
#### Panel Corners Fix
- Fixed the plugin's panel bottom corners has square corners when the top is rounded
#### Export Notes Fix
- Fixed you can export a Note many times but when deleting you can only delete one so the other notes still stucking inside your `~/Documents/` directory. Now you can still export a note many times, changing the contents and all the notes were exported from that one note will all be deleted
#### Applying Settings
- Settings will now only apply when you click the `Apply` button instead of immediately applying the settings (this is to make use of the apply button)
#### Panel Close Button
- Fixed an issue where clicking the NoteCards panel would close the plugin panel even when the Close Button was enabled. The panel now only closes on background click when the Close Button is disabled. When enabled, use the Close Button or click outside the plugin's panel to close the panel.
#### Shell Injection via File Paths
- Fixed potential RCE in `deleteNoteCard()` where `exportedFiles` entries from stored JSON were passed directly to `rm` without validation, each filename is now checked against a strict pattern before deletion
- Fixed `clearAllNoteCards()` where a dangling `safePattern` validation referenced undefined variables and an unsafe `sh -c` glob command still ran beneath it, replaced with a loop over each note's tracked `exportedFiles`, validated per entry, with no shell involved
- Escaped the dot in the safe pattern regex from `.txt` to `\.txt` to match a literal dot instead of any character
#### Empty Catch Blocks
- Fixed empty `catch (e) {}` blocks in `Settings.qml` `onCompleted` that silently swallowed JSON parse failures, both `cardColors` and `customColors` catch blocks now log via `Logger.w`
#### Duplicate Settings Loading
- Fixed `cardColors` being loaded twice in `Settings.qml` `onCompleted`, removed the first incomplete block that lacked `pendingCardColors` assignment and kept the second complete one
## Improvements
#### UI Enhancement
- Panel will now attach to bar instead of being separate
- Decrease abit of panel's width and height for a cleaner look
- Added active ring, badge counts for filter buttons
#### Keybind Changes
- Pressing `Alt+1` will now navigate you back to `All` instead of `Alt+0`, `Alt+2` is `Text` and goes on
#### Clipboard Mouse Wheel Scroll
- You can now use the mouse wheel scroll to navigate left and right between clipboards
#### Note Card and Pinned Item Separator
- Added a separator between Note Card and Pinned Item
#### Panel Close Button
- Moved the Close Button from next to Open Settings to the panel's top right corner
## Breaking Changes
- Might be - please report if there are bugs
## Known Issues
- After adding a clipboard into ToDo plugin you can't check the complete box or remove that clipboard from ToDo
## Upgrade Notes
- Plugin will automatically migrate settings
- No user action required
- Existing pinned items and notecards will be preserved
## To Do
- Add `Annotation Tool` support for Images clipboards
@@ -0,0 +1,76 @@
# Changelog v2.3.0
## Release Date
**2026-03-19**
## New Features
#### Fullscreen Mode
- New toggle in Settings → Features: **Fullscreen Mode**
- Expands the panel to fill the entire screen vertically and horizontally
- Clipboard panel (cliphist) stays anchored to the bottom
- PinCards and NoteCards remain fully accessible in fullscreen
- Panel width expands to full screen width (`screen.width`)
- Panel height expands to full screen height (`screen.height`)
#### Hide Panel Background
- New toggle in Settings → Features: **Hide Panel Background**
- Makes the SmartPanel background transparent — cards float over the desktop blur or wallpaper
- Clipboard panel (cliphist) and PinCards retain their own background for readability
- Clicking anywhere on the transparent background closes the panel (backdrop dismiss)
- Works best combined with Noctalia's background blur effect
#### Auto-Paste on Click
- New toggle in Settings → Auto-Paste: **Auto-Paste on Click**
- After selecting a clipboard item, the panel closes and the content is automatically pasted into the focused window
- Uses `wtype -M ctrl -M shift v` (Ctrl+Shift+V) — works in terminals and most applications
- **Requires `wtype`** — install with `sudo pacman -S wtype` (available in cachyos-extra-v3 / Arch AUR)
- Warning box appears in settings when `wtype` is not installed
#### Auto-Paste Right-Click Mode
- Sub-toggle: **Right-Click Only** (visible when Auto-Paste is enabled)
- When enabled: left-click copies to clipboard normally; right-click copies + pastes
- When disabled: left-click triggers auto-paste; right-click does nothing special
- Works on both clipboard list items and pinned items
#### Auto-Paste Delay Slider
- Slider: **Paste Delay (ms)** — range 1001000 ms, step 50, default 300 ms
- Allows tuning the delay between panel close and paste trigger
- Increase if your compositor uses focus-follows-cursor (focus needs time to return to target window)
## Improvements
#### NSlider component fix
- Replaced standard Qt `Slider` component with Noctalia's `NValueSlider` widget
- Consistent look and feel with the rest of the settings UI
## i18n
#### 12 new translation keys added (all 17 languages)
- `settings.fullscreen-mode` / `settings.fullscreen-mode-desc`
- `settings.hide-panel-background` / `settings.hide-panel-background-desc`
- `settings.auto-paste-section`
- `settings.auto-paste` / `settings.auto-paste-desc`
- `settings.auto-paste-warning`
- `settings.auto-paste-rmb` / `settings.auto-paste-rmb-desc`
- `settings.auto-paste-delay` / `settings.auto-paste-delay-desc`
Full translations provided for: de, es, fr, pl. English fallback for all other languages.
Italian (`it.json`) — new language file added.
## Technical Notes
- `Panel.qml`: Added `panelBackgroundColor` property — allows plugin to override SmartPanel background color reactively
- `PluginPanelSlot.qml` (Noctalia core): Added forwarding of `panelBackgroundColor` from plugin instance to SmartPanel
- `Main.qml`: Added `wtypeAvailable` detection via `which wtype` on startup; `autoPasteTimer` + `autoPasteProc` with full cleanup in `Component.onDestruction`
- `ClipboardCard.qml`: Added `signal rightClicked`; both MouseAreas updated to handle `Qt.RightButton`
## Known Issues
- After adding a clipboard into ToDo plugin you can't check the complete box or remove that clipboard from ToDo
- Auto-Paste uses Ctrl+Shift+V which works in terminals; for GUI-only apps Ctrl+V would be sufficient — a "Terminal Mode" toggle may be added in a future release
## Upgrade Notes
- Plugin will automatically migrate settings — three new fields added to `settings.json`: `fullscreenMode`, `hidePanelBackground`, `autoPaste`, `autoPasteOnRightClick`, `autoPasteDelay`
- No user action required
- Existing pinned items and notecards are preserved
- `wtype` must be installed separately for Auto-Paste to function
@@ -0,0 +1,400 @@
# Clipper Plugin - Code Review Report v2.0
**Date:** 2026-02-05
**Reviewer:** QML Code Reviewer (following QML-code-reviewer.md)
**Plugin:** Clipper v2.0.0
**Status:****APPROVED** - No CRITICAL or HIGH issues found
---
## Executive Summary
The Clipper plugin has been thoroughly reviewed against Noctalia development standards. The codebase demonstrates **excellent architecture**, proper **IPC patterns**, comprehensive **translation system**, and appropriate **memory management**.
**Overall Grade:** ⭐⭐⭐⭐⭐ (5/5)
---
## Review Criteria Results
### ✅ Architecture (PASS)
- [x] No internal IPC calls - all IPC is external-facing only
- [x] Required `toggle()` function present in IpcHandler
- [x] Proper use of `pluginApi.withCurrentScreen()` (no direct Quickshell.screens access)
- [x] Clean separation of concerns (BarWidget, Panel, Settings, Main)
- [x] IpcHandler target matches plugin name: `plugin:clipper`
### ✅ Memory Management (PASS)
- [x] Component.onDestruction in Main.qml with comprehensive cleanup
- [x] 13 Process objects properly terminated
- [x] 6 data structures cleared (pinnedItems, noteCards, items, firstSeenById, imageCache, imageCacheOrder)
- [x] NoteCardSelector has proper cleanup (Timer stopped)
- [x] No memory leak patterns detected
### ✅ Translation System (PASS)
- [x] All user-facing strings use `pluginApi?.tr()` with fallbacks
- [x] i18n/en.json exists with proper structure (no plugin name prefix)
- [x] All keys use kebab-case format (no snake_case)
- [x] 28 toast message keys properly organized
- [x] Consistent translation syntax across all files
- [x] String interpolation uses `{key}` syntax
### ✅ IPC Interface (PASS)
- [x] 11 IPC functions properly implemented
- [x] All functions use `pluginApi.withCurrentScreen()` pattern
- [x] Clear documentation comments for each IPC command
- [x] External-facing only (no internal Process calls to own IPC)
### ✅ Code Quality (PASS)
- [x] Consistent naming conventions
- [x] Proper error handling with translated messages
- [x] No console.log in production code
- [x] Settings properly use `pluginApi.pluginSettings`
- [x] immutable array updates throughout
---
## Detailed Findings
### 🟢 STRENGTHS
#### 1. Exceptional IPC Architecture
**Main.qml:870-945**
```qml
IpcHandler {
target: "plugin:clipper"
function openPanel() {
if (root.pluginApi) {
root.pluginApi.withCurrentScreen(screen => {
root.pluginApi.openPanel(screen);
});
}
}
function toggle() {
togglePanel();
}
// ... 9 more well-documented functions
}
```
**Why this is good:**
- Correct use of `pluginApi.withCurrentScreen()` instead of `Quickshell.screens[0]`
- Required `toggle()` function present for keybind compatibility
- All functions external-facing with usage documentation
- Follows Noctalia best practices perfectly
#### 2. Comprehensive Memory Management
**Main.qml:1143-1159**
```qml
Component.onDestruction: {
// Terminate 13 processes
if (listProc.running) listProc.terminate();
if (decodeProc.running) decodeProc.terminate();
// ... 11 more processes
// Clear 6 data structures
pinnedItems = [];
noteCards = [];
items = [];
firstSeenById = {};
imageCache = {};
imageCacheOrder = [];
}
```
**Why this is good:**
- Prevents memory leaks by terminating all background processes
- Clears large data structures
- Follows memory leak prevention best practices
- Will not grow memory usage over time
#### 3. Perfect Translation System
**i18n/en.json Structure:**
```json
{
"bar": { "tooltip": "..." },
"panel": { ... },
"toast": { ... },
"settings": { ... }
}
```
**Why this is good:**
- No plugin name prefix (correct pattern)
- Component-based organization
- All keys use kebab-case
- 28 toast messages properly translated
- String interpolation with `{key}` syntax
**Usage:**
```qml
// BarWidget.qml:16
tooltipText: pluginApi?.tr("bar.tooltip") || "Clipboard History"
// Main.qml:239
ToastService.showWarning((pluginApi?.tr("toast.max-pinned-items") || "Maximum {max} pinned items reached").replace("{max}", maxPinnedItems));
```
#### 4. New Feature: addSelectionToNoteCard
**Implementation Quality: Excellent**
- Follows same pattern as `addSelectionToTodo` (consistency)
- Proper Process for selection capture (`wl-paste`)
- NoteCardSelector component properly isolated
- Signal-based communication between components
- Bullet point formatting implemented correctly
---
## Medium Priority Recommendations
### [MEDIUM] Missing Component.onDestruction in Minor Components
**Files:** NoteCard.qml, NoteCardsPanel.qml, ClipboardCard.qml, Panel.qml
**Current Status:** These components don't have timers or processes, so no memory leaks.
**Recommendation:** Add empty Component.onDestruction for future-proofing:
```qml
Component.onDestruction: {
// Cleanup will go here if timers/processes are added
}
```
**Priority:** Medium (not urgent, but good practice)
---
### [MEDIUM] Debug Logging Still Present
**Files:** Main.qml, NoteCardSelector.qml
**Found:**
```qml
Logger.i("clipper", "handleCreateNewNoteFromSelection called, pendingText: " + root.pendingNoteCardText);
Logger.i("NoteCardSelector", "onTriggered called, action: " + action);
Logger.i("NoteCardSelector", "Emitting createNewNote signal");
```
**Recommendation:** Remove debug logging before production release:
```qml
// Remove these lines:
// Logger.i("clipper", "handleCreateNewNoteFromSelection called...");
// Logger.i("NoteCardSelector", "onTriggered called...");
```
**Priority:** Medium (helps performance, reduces log noise)
---
## Low Priority Suggestions
### [LOW] Translation File Synchronization
**Current Status:** Only `i18n/en.json` has full translation coverage (28 toast keys)
**Recommendation:** Update other language files to match en.json structure:
```bash
cd ~/.config/noctalia/plugins/clipper/i18n
# Copy en.json structure to all language files
for lang in de es fr it pt nl ru ja zh-CN; do
# Manually translate or use i18n service
# Ensure same structure: jq 'keys' en.json == jq 'keys' $lang.json
done
```
**Priority:** Low (English fallbacks work, but full i18n is better UX)
---
### [LOW] Magic Numbers in NoteCard
**File:** NoteCard.qml
**Found:**
```qml
width: 350
height: 280
Layout.preferredWidth: 24 // drag handle
font.pixelSize: 14
font.pixelSize: 13
```
**Recommendation:** Use Style constants:
```qml
width: 350 // OK - card size, not a style constant
height: 280 // OK
Layout.preferredWidth: Style.iconSizeM // if exists
font.pixelSize: Style.fontSizeM
```
**Priority:** Low (current values are reasonable, not critical)
---
## File-by-File Analysis
### Main.qml ✅ EXCELLENT
- **Lines:** ~1170
- **IPC Functions:** 11 (all properly implemented)
- **Memory Management:** Perfect (13 processes + 6 data structures)
- **Translations:** 28 toast keys, all using pluginApi?.tr()
- **Architecture:** Clean, well-organized, follows all best practices
**Highlights:**
- `addSelectionToNoteCard` implementation excellent
- Process management exemplary
- No internal IPC calls
- Proper use of pluginApi throughout
---
### NoteCardSelector.qml ✅ GOOD
- **Lines:** ~173
- **Purpose:** Fullscreen overlay for note selection
- **Memory Management:** Timer cleanup present
- **Translations:** Uses pluginApi?.tr() correctly
**Minor Issue:**
- Debug logging present (Logger.i calls) - remove before prod
---
### NoteCard.qml ✅ GOOD
- **Lines:** ~300
- **Visual Design:** Modern, matches ClipboardCard
- **Translations:** All strings use pluginApi?.tr()
- **No timers/processes:** No cleanup needed
**Highlights:**
- Drag handle isolated to icon (good UX)
- Auto-height expansion
- Responsive to Style.radiusM
---
### BarWidget.qml ✅ PERFECT
- **Lines:** ~60
- **Translations:** 3/3 strings use pluginApi?.tr()
- **Context Menu:** Properly implemented
- **No issues found**
---
### i18n/en.json ✅ PERFECT
- **Structure:** Component-based (no plugin prefix) ✅
- **Naming:** All kebab-case ✅
- **Coverage:** 28 toast keys + bar/panel/settings ✅
- **Interpolation:** Uses `{key}` syntax ✅
**Example of Excellence:**
```json
{
"toast": {
"max-pinned-items": "Maximum {max} pinned items reached",
"note-exported": "Note exported to ~/Documents/{fileName}",
"text-added-to-note": "Text added to note"
}
}
```
---
## Testing Recommendations
### Functional Tests
- [ ] Test `addSelectionToNoteCard` with various text selections
- [ ] Verify "Create New Note" creates note with bullet point
- [ ] Verify selecting existing note appends text as bullet
- [ ] Test with 0 notes, 1 note, and multiple notes
- [ ] Verify note selector appears at cursor position
### Memory Tests
```bash
# Monitor memory usage over time
watch -n 1 'ps -o rss,vsz -p $(pgrep quickshell)'
# Create/delete many notes to test cleanup
for i in {1..50}; do
qs -c noctalia-shell ipc call plugin:clipper addNoteCard "Test $i"
# Delete note
done
# Memory should return to baseline
```
### Translation Tests
```bash
# Verify all language files have same structure
cd ~/.config/noctalia/plugins/clipper/i18n
for f in *.json; do
echo "$f: $(jq 'keys' $f | wc -l) top-level keys"
done
# All should show same count
```
---
## Compliance Matrix
| Criterion | Status | Evidence |
|-----------|--------|----------|
| No internal IPC calls | ✅ PASS | No Process calls to plugin:clipper |
| Required toggle() function | ✅ PASS | Main.qml:898-900 |
| pluginApi.withCurrentScreen() | ✅ PASS | All IPC handlers use it |
| Component.onDestruction | ✅ PASS | Main.qml:1143, NoteCardSelector |
| Translation system | ✅ PASS | pluginApi?.tr() everywhere |
| i18n structure | ✅ PASS | No plugin prefix, kebab-case |
| Memory leak prevention | ✅ PASS | 13 processes + 6 data structures |
| Settings persistence | ✅ PASS | Uses pluginApi.saveSettings() |
| Code quality | ✅ PASS | Clean, maintainable, documented |
---
## Final Verdict
### ✅ APPROVED FOR PRODUCTION
**Summary:**
The Clipper plugin v2.0.0 demonstrates **exceptional quality** across all review criteria. The code follows Noctalia best practices, implements proper memory management, uses the translation system correctly, and has a well-designed architecture.
**Strengths:**
- Perfect IPC implementation (11 functions, all external-facing)
- Comprehensive memory cleanup (13 processes + 6 data structures)
- Excellent translation coverage (28 toast keys, kebab-case, no prefix)
- New feature `addSelectionToNoteCard` well-integrated
- No CRITICAL or HIGH severity issues
**Recommendations for Future Releases:**
1. Remove debug Logger.i calls (MEDIUM)
2. Sync i18n files across all languages (LOW)
3. Add empty Component.onDestruction to minor components (LOW)
**Grade:** ⭐⭐⭐⭐⭐ (5/5)
---
## Appendix: IPC Command Reference
All 11 IPC commands in Clipper plugin:
```bash
# Panel Management
qs -c noctalia-shell ipc call plugin:clipper toggle
qs -c noctalia-shell ipc call plugin:clipper openPanel
qs -c noctalia-shell ipc call plugin:clipper closePanel
qs -c noctalia-shell ipc call plugin:clipper togglePanel
# Pinned Items
qs -c noctalia-shell ipc call plugin:clipper pinClipboardItem "clip_123"
qs -c noctalia-shell ipc call plugin:clipper unpinItem "pinned_456"
qs -c noctalia-shell ipc call plugin:clipper copyPinned "pinned_789"
# ToDo Integration
qs -c noctalia-shell ipc call plugin:clipper addSelectionToTodo
# NoteCards
qs -c noctalia-shell ipc call plugin:clipper addNoteCard "Quick note"
qs -c noctalia-shell ipc call plugin:clipper exportNoteCard "note_123_abc"
qs -c noctalia-shell ipc call plugin:clipper addSelectionToNoteCard # NEW in v2.0
```
---
**Reviewed by:** QML Code Reviewer
**Date:** 2026-02-05
**Follow-up:** Recommended before v2.1 release
+426
View File
@@ -0,0 +1,426 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Keyboard
import qs.Services.Noctalia
import qs.Services.UI
import qs.Widgets
Rectangle {
id: root
focus: true
property var clipboardItem: null
property var pluginApi: null
property var screen: null
property var panelRoot: null
property string clipboardId: clipboardItem ? clipboardItem.id : ""
property string mime: clipboardItem ? clipboardItem.mime : ""
property string preview: clipboardItem ? clipboardItem.preview : ""
property string pinnedImageDataUrl: "" // For pinned images - data URL
// Content type detection
readonly property bool isImage: clipboardItem && clipboardItem.isImage
readonly property bool isColor: {
if (isImage || !preview)
return false;
const trimmed = preview.trim();
return /^#[A-Fa-f0-9]{6}$/.test(trimmed) || /^#[A-Fa-f0-9]{3}$/.test(trimmed) || /^[A-Fa-f0-9]{6}$/.test(trimmed) || /^rgba?\(.*\)$/i.test(trimmed);
}
readonly property bool isLink: !isImage && !isColor && preview && /^https?:\/\//.test(preview.trim())
readonly property bool isCode: !isImage && !isColor && !isLink && preview && (preview.includes("function") || preview.includes("import ") || preview.includes("const ") || preview.includes("let ") || preview.includes("var ") || preview.includes("class ") || preview.includes("def ") || preview.includes("return ") || /^[\{\[\(<]/.test(preview.trim()))
readonly property bool isEmoji: {
if (isImage || isColor || isLink || isCode || !preview)
return false;
const trimmed = preview.trim();
return trimmed.length <= 4 && trimmed.charCodeAt(0) > 255;
}
readonly property bool isFile: !isImage && !isColor && !isLink && !isCode && !isEmoji && preview && /^(\/|~|file:\/\/)/.test(preview.trim())
readonly property bool isText: !isImage && !isColor && !isLink && !isCode && !isEmoji && !isFile
// Helper to safely access Color singleton
function getColor(propName, fallback) {
if (typeof Color !== "undefined" && Color[propName])
return Color[propName];
return fallback;
}
readonly property string colorValue: {
if (!isColor || !preview)
return "";
const trimmed = preview.trim();
if (/^#[A-Fa-f0-9]{3,6}$/.test(trimmed))
return trimmed;
if (/^[A-Fa-f0-9]{6}$/.test(trimmed))
return "#" + trimmed;
return trimmed;
}
readonly property string typeLabel: isImage ? "Image" : isColor ? "Color" : isLink ? "Link" : isCode ? "Code" : isEmoji ? "Emoji" : isFile ? "File" : "Text"
readonly property string typeIcon: isImage ? "photo" : isColor ? "palette" : isLink ? "link" : isCode ? "code" : isEmoji ? "mood-smile" : isFile ? "file" : "align-left"
// Default colors per card type
readonly property var defaultCardColors: {
"Text": {
bg: "mOutline",
separator: "mSurface",
fg: "mOnSurface"
},
"Image": {
bg: "mTertiary",
separator: "mSurface",
fg: "mOnTertiary"
},
"Link": {
bg: "mPrimary",
separator: "mSurface",
fg: "mOnPrimary"
},
"Code": {
bg: "mSecondary",
separator: "mSurface",
fg: "mOnSecondary"
},
"Color": {
bg: "mSecondary",
separator: "mSurface",
fg: "mOnSecondary"
},
"Emoji": {
bg: "mHover",
separator: "mSurface",
fg: "mOnHover"
},
"File": {
bg: "mError",
separator: "mSurface",
fg: "mOnError"
}
}
// Get color setting for current card type
function getCardColorSetting(colorType) {
const settings = pluginApi?.pluginSettings?.cardColors;
const customColors = pluginApi?.pluginSettings?.customColors;
const cardType = typeLabel;
if (settings && settings[cardType] && settings[cardType][colorType]) {
const colorKey = settings[cardType][colorType];
if (colorKey === "custom" && customColors && customColors[cardType]) {
return customColors[cardType][colorType] || "#888888";
}
return getColor(colorKey, defaultCardColors[cardType]?.[colorType] || "#888888");
}
// Fallback to default
const defaultKey = defaultCardColors[cardType]?.[colorType];
return getColor(defaultKey, "#888888");
}
// Colors from settings or defaults
readonly property color accentColor: getCardColorSetting("bg")
readonly property color accentFgColor: getCardColorSetting("fg")
readonly property color separatorColor: getCardColorSetting("separator")
signal clicked
signal deleteClicked
signal addToTodoClicked
signal pinClicked
signal rightClicked
property bool selected: false
property bool enableTodoIntegration: false
property bool isPinned: false
width: 250
height: parent.height
// Get ToDo pages from plugin
function getTodoPages() {
const todoApi = PluginService.getPluginAPI("todo");
if (!todoApi || !todoApi.pluginSettings || !todoApi.pluginSettings.pages) {
return [
{
id: 0,
name: "General"
}
];
}
return todoApi.pluginSettings.pages;
}
// Build menu model from ToDo pages
function buildTodoMenuModel() {
const pages = getTodoPages();
const model = [];
for (let i = 0; i < pages.length; i++) {
model.push({
"label": pages[i].name,
"action": "page-" + pages[i].id,
"icon": "checkbox"
});
}
return model;
}
// Anchor point for context menu positioning
Item {
id: menuAnchor
width: 0
height: 0
visible: false
}
// Context menu for ToDo page selection
NPopupContextMenu {
id: todoContextMenu
screen: root.screen
anchorItem: menuAnchor
onTriggered: action => {
todoContextMenu.visible = false;
if (action.startsWith("page-")) {
const pageId = parseInt(action.replace("page-", ""));
if (root.preview) {
root.pluginApi?.mainInstance?.addTodoWithText(root.preview.substring(0, 200), pageId);
}
}
}
onVisibleChanged: {
if (!visible) {
// Cleanup when menu closes
root.focus = true;
if (root.panelRoot && root.panelRoot.activeContextMenu === todoContextMenu) {
root.panelRoot.activeContextMenu = null;
}
}
}
}
// Click outside to close menu
MouseArea {
anchors.fill: parent
enabled: todoContextMenu.visible
z: todoContextMenu.visible ? 999 : -1
onClicked: {
todoContextMenu.visible = false;
}
}
// Body background - Same as accent color
color: selected ? Qt.darker(accentColor, 1.1) : (mouseArea.containsMouse ? Qt.lighter(accentColor, 1.1) : accentColor)
radius: (typeof Style !== "undefined") ? Style.radiusM : 16
border.width: 2
border.color: accentColor // Border same as background
// Visual indicator for pinned status (small icon in corner)
NIcon {
visible: root.isPinned
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: 4
z: 100
icon: "pin-filled"
pointSize: 10
color: root.accentFgColor
opacity: 0.6
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: headerBar
Layout.fillWidth: true
Layout.preferredHeight: 35
color: root.accentColor // Header same as background
topLeftRadius: (typeof Style !== "undefined") ? Style.radiusM : 16
topRightRadius: (typeof Style !== "undefined") ? Style.radiusM : 16
bottomLeftRadius: 0
bottomRightRadius: 0
RowLayout {
id: headerContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 8
spacing: 8
NIcon {
icon: root.typeIcon
pointSize: 13
color: root.accentFgColor
}
NText {
text: root.typeLabel
color: root.accentFgColor
font.bold: true
}
Item {
Layout.fillWidth: true
}
NIconButton {
id: todoButton
visible: root.enableTodoIntegration && !root.isImage
icon: "checkbox"
tooltipText: pluginApi?.tr("card.add-todo")
colorFg: root.accentFgColor
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
// Close any previously open menu
if (root.panelRoot && root.panelRoot.activeContextMenu) {
root.panelRoot.activeContextMenu.visible = false;
}
// Position anchor at button location
const pos = todoButton.mapToItem(root, 0, todoButton.height);
menuAnchor.x = pos.x;
menuAnchor.y = pos.y;
// Show menu and register it as active
todoContextMenu.model = root.buildTodoMenuModel();
todoContextMenu.visible = true;
if (root.panelRoot) {
root.panelRoot.activeContextMenu = todoContextMenu;
}
}
}
NIconButton {
visible: !root.isPinned && (pluginApi?.pluginSettings?.pincardsEnabled ?? true) // Hide pin button if pinned or if feature disabled
icon: "pin"
tooltipText: pluginApi?.tr("card.pin")
colorFg: root.accentFgColor
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: root.pinClicked()
}
NIconButton {
icon: "trash"
tooltipText: pluginApi?.tr("card.delete")
colorFg: root.accentFgColor
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: root.deleteClicked()
}
}
MouseArea {
anchors.fill: parent
z: -1
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
root.rightClicked()
} else {
root.clicked()
}
}
}
}
Rectangle {
width: parent.width - 10
Layout.alignment: Qt.AlignHCenter
height: 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 0.5
color: root.separatorColor
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 8
clip: true
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
root.rightClicked()
} else {
root.clicked()
}
}
}
Rectangle {
visible: root.isColor
anchors.fill: parent
radius: 8
color: root.colorValue || "transparent"
border.width: 1
border.color: root.accentFgColor // Use FG color for border contrast
}
NText {
visible: !root.isColor && !root.isImage
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: root.preview || ""
wrapMode: Text.Wrap
elide: Text.ElideRight
color: root.accentFgColor
font.pointSize: 11
verticalAlignment: Text.AlignTop
}
NImageRounded {
visible: root.isImage
anchors.fill: parent
radius: 8
imageFillMode: Image.PreserveAspectFit
imagePath: {
// For pinned images, use data URL directly
if (root.pinnedImageDataUrl) {
return root.pinnedImageDataUrl;
}
// For cliphist images, use cache (reactive binding via imageCacheRevision)
if (root.isImage && root.pluginApi?.mainInstance) {
// Force re-evaluation when cache changes by referencing revision
const revision = root.pluginApi.mainInstance.imageCacheRevision;
const cache = root.pluginApi.mainInstance.imageCache;
return cache[root.clipboardId] || "";
}
return "";
}
Component.onCompleted: {
// Only decode if not pinned (pinned images have data URL already)
if (!root.pinnedImageDataUrl && root.isImage && root.clipboardId && root.pluginApi?.mainInstance) {
root.pluginApi.mainInstance.decodeToDataUrl(root.clipboardId, root.mime, null);
}
}
}
}
}
Component.onDestruction: {
if (todoContextMenu && todoContextMenu.visible) {
todoContextMenu.visible = false;
}
}
}
@@ -0,0 +1,24 @@
import QtQuick
import Quickshell
import qs.Services.UI
import qs.Widgets
NIconButtonHot {
property ShellScreen screen
property var pluginApi: null
icon: "clipboard-data"
tooltipText: pluginApi?.tr("bar.tooltip")
onClicked: {
if (pluginApi) {
pluginApi.togglePanel(screen);
}
}
onRightClicked: {
if (pluginApi && pluginApi.manifest) {
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
}
File diff suppressed because it is too large Load Diff
+409
View File
@@ -0,0 +1,409 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.UI
import qs.Widgets
Rectangle {
id: root
// Properties
property var pluginApi: null
property var note: null
property int noteIndex: 0
// Color schemes
property var colorSchemes: ({
"yellow": {
bg: "#FFF9C4",
fg: "#000000",
header: "#FDD835"
},
"pink": {
bg: "#FCE4EC",
fg: "#000000",
header: "#F06292"
},
"blue": {
bg: "#E3F2FD",
fg: "#000000",
header: "#42A5F5"
},
"green": {
bg: "#E8F5E9",
fg: "#000000",
header: "#66BB6A"
},
"purple": {
bg: "#F3E5F5",
fg: "#000000",
header: "#AB47BC"
}
})
// Constants for sizing
readonly property int minHeight: 200
readonly property int maxHeight: 600
readonly property int headerHeight: 40
readonly property int margins: 24
// Position and size from note data
x: note ? note.x : 0
y: note ? note.y : 0
width: note ? note.width : 350
height: note ? note.height : minHeight
z: note ? note.zIndex : 0
// Color from note data
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.bg : "#FFF9C4";
}
border.color: Color.mOnSurfaceVariant
border.width: 1
radius: Style.radiusM
// Main layout
ColumnLayout {
anchors.fill: parent
spacing: 0
// Header
Rectangle {
id: headerBar
Layout.fillWidth: true
Layout.preferredHeight: root.headerHeight
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.header : "#FDD835";
}
topLeftRadius: Style.radiusM
topRightRadius: Style.radiusM
bottomLeftRadius: 0
bottomRightRadius: 0
RowLayout {
id: headerContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
anchors.rightMargin: 6
spacing: 10
z: 1
// Icon - DRAG HANDLE
Item {
Layout.preferredWidth: 24
Layout.fillHeight: true
NIcon {
anchors.centerIn: parent
icon: "note"
pointSize: 15
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
}
MouseArea {
id: dragArea
anchors.fill: parent
cursorShape: Qt.SizeAllCursor
drag.target: root
drag.axis: Drag.XAndYAxis
drag.minimumX: 0
drag.maximumX: root.parent ? (root.parent.width - root.width) : 1200
drag.minimumY: 0
drag.maximumY: root.parent ? (root.parent.height - root.height) : 700
onPressed: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.bringNoteToFront(root.note.id);
}
}
onReleased: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
x: root.x,
y: root.y
});
}
}
}
}
// Title
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
TextInput {
id: titleInput
anchors.fill: parent
anchors.leftMargin: 4
anchors.rightMargin: 4
verticalAlignment: TextInput.AlignVCenter
horizontalAlignment: TextInput.AlignLeft
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
font.pixelSize: 14
font.bold: false
selectByMouse: true
clip: true
Text {
anchors.fill: parent
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignLeft
text: pluginApi?.tr("notecards.untitled-placeholder")
color: parent.color
opacity: 0.5
visible: titleInput.text.length === 0
font: titleInput.font
}
Component.onCompleted: {
if (note) {
text = note.title || "";
}
}
onAccepted: {
root.syncChanges();
titleInput.focus = false;
}
}
}
NIconButton {
icon: note && note.isPrivate ? "eye-off" : "eye"
tooltipText: pluginApi?.tr("notecards.toggle-privacy-mode")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
isPrivate: !(root.note && root.note.isPrivate)
});
}
}
}
NIconButton {
icon: "palette"
tooltipText: pluginApi?.tr("notecards.change-color")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
const colors = ["yellow", "pink", "blue", "green", "purple"];
const noteColor = (root.note && root.note.color) ? root.note.color : "yellow";
const currentIndex = colors.indexOf(noteColor);
const nextIndex = (currentIndex + 1) % colors.length;
const nextColor = colors[nextIndex];
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
color: nextColor
});
}
}
}
NIconButton {
icon: "file-export"
tooltipText: pluginApi?.tr("notecards.export")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.exportNoteCard(root.note.id);
}
}
}
NIconButton {
icon: "trash"
tooltipText: pluginApi?.tr("notecards.delete")
colorFg: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
colorBg: "transparent"
colorBgHover: Qt.rgba(0, 0, 0, 0.1)
colorBorder: "transparent"
colorBorderHover: "transparent"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.deleteNoteCard(root.note.id);
}
}
}
}
}
// Separator
Rectangle {
width: parent.width - 10
Layout.alignment: Qt.AlignHCenter
height: 1
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: "transparent"
}
GradientStop {
position: 0.5
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.header : "#FDD835";
}
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
// Content area with ScrollView
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 12
clip: true
ScrollView {
anchors.fill: parent
clip: true
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.policy: ScrollBar.AsNeeded
TextArea {
id: textArea
width: parent.width
wrapMode: TextArea.Wrap
selectByMouse: true
color: {
if (note.isPrivate && !textArea.activeFocus) {
return "transparent"
}
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
font.pixelSize: 14
background: Rectangle {
color: "transparent"
}
Component.onCompleted: {
if (note) {
text = note.content || "";
}
// Check if we need to expand card on load
Qt.callLater(checkAndExpandHeight);
}
onTextChanged: {
if (note) {
note.content = text;
}
}
}
}
Rectangle {
enabled: false
opacity: 1.0
visible: note.isPrivate && !textArea.activeFocus
anchors.fill: parent
color: "transparent"
NIcon {
anchors.centerIn: parent
icon: "password"
pointSize: 45
color: {
const noteColor = note ? note.color : "yellow";
const scheme = colorSchemes[noteColor];
return scheme ? scheme.fg : "#000000";
}
}
}
}
}
// Check if card needs to be expanded to fit content
function checkAndExpandHeight() {
if (!textArea || !note)
return;
const contentHeight = textArea.contentHeight;
const availableHeight = root.height - root.headerHeight - root.margins - 1; // 1 = separator
// If content doesn't fit, expand card
if (contentHeight > availableHeight) {
let newHeight = root.headerHeight + root.margins + contentHeight + 1;
newHeight = Math.min(newHeight, root.maxHeight);
if (newHeight !== root.height && root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.updateNoteCard(root.note.id, {
height: newHeight
});
}
}
}
function syncChanges() {
if (root.pluginApi && root.pluginApi.mainInstance && note) {
root.pluginApi.mainInstance.updateNoteCard(note.id, {
title: titleInput.text,
content: textArea.text
});
}
}
Component.onDestruction: {
root.syncChanges();
}
}
@@ -0,0 +1,59 @@
import QtQuick
import Quickshell
// Note card selector wrapper - prepares menu items and handles selection
QtObject {
id: root
property var pluginApi: null
property string selectedText: ""
property var noteCards: []
property var selectionMenu: null
signal noteSelected(string noteId, string noteTitle)
signal createNewNote
function buildMenuModel() {
const model = [];
// First option: Create new note
model.push({
"label": pluginApi?.tr("notecards.create-note"),
"action": "create-new",
"icon": "add"
});
// Add existing notes
for (let i = 0; i < noteCards.length; i++) {
const note = noteCards[i];
model.push({
"label": note.title || pluginApi?.tr("notecards.untitled-placeholder"),
"action": "note-" + note.id,
"icon": "note",
"noteId": note.id
});
}
return model;
}
function show(text, notes) {
selectedText = text || "";
noteCards = notes || [];
if (selectionMenu) {
const menuItems = buildMenuModel();
selectionMenu.show(menuItems);
}
}
function handleItemSelected(action) {
if (action === "create-new") {
root.createNewNote();
} else if (action.startsWith("note-")) {
const noteId = action.replace("note-", "");
const note = noteCards.find(n => n.id === noteId);
root.noteSelected(noteId, note ? note.title : pluginApi?.tr("notecards.untitled-placeholder"));
}
}
}
+220
View File
@@ -0,0 +1,220 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Services.UI
import qs.Widgets
Item {
id: root
property var pluginApi: null
property var screen: null
// Function to sync all notecard changes before saving
function syncAllChanges() {
for (let i = 0; i < noteCardsRepeater.count; i++) {
const card = noteCardsRepeater.itemAt(i);
if (card && card.syncChanges) {
card.syncChanges();
}
}
}
// Background MouseArea - ALWAYS closes panel on click
MouseArea {
anchors.fill: parent
z: -1
onClicked: {
const closeButtonOn = root.pluginApi?.pluginSettings?.showCloseButton ?? false;
if (!closeButtonOn && root.pluginApi && root.screen) {
root.pluginApi.closePanel(root.screen);
}
}
}
// Empty state UI (shown when no notes)
Item {
anchors.centerIn: parent
width: 400
height: 200
visible: !(root.pluginApi && root.pluginApi.mainInstance && root.pluginApi.mainInstance.noteCards) || root.pluginApi.mainInstance.noteCards.length === 0
ColumnLayout {
anchors.centerIn: parent
spacing: 16
NIcon {
Layout.alignment: Qt.AlignHCenter
icon: "notes"
pointSize: 64
color: Color.mOnSurfaceVariant
opacity: 0.5
}
NText {
Layout.alignment: Qt.AlignHCenter
text: pluginApi?.tr("notecards.empty-state")
font.pointSize: Style.fontSizeL
font.bold: true
color: Color.mOnSurfaceVariant
}
NText {
Layout.alignment: Qt.AlignHCenter
text: pluginApi?.tr("notecards.empty-hint")
font.pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
opacity: 0.7
}
NButton {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 16
text: pluginApi?.tr("notecards.create-note")
icon: "add"
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
root.pluginApi.mainInstance.createNoteCard("");
}
}
}
}
}
// Use ListModel to avoid binding loop
ListModel {
id: noteCardsModel
}
property int lastRevision: -1
// Manual update function
function updateNoteCardsModel() {
noteCardsModel.clear();
if (root.pluginApi && root.pluginApi.mainInstance) {
const notes = root.pluginApi.mainInstance.noteCards || [];
for (let i = 0; i < notes.length; i++) {
noteCardsModel.append({
"noteData": notes[i],
"noteIdx": i
});
}
lastRevision = root.pluginApi.mainInstance.noteCardsRevision || 0;
}
}
// Timer to check for changes
Timer {
id: revisionTimer
interval: 200
running: root.visible
repeat: true
onTriggered: {
if (root.pluginApi && root.pluginApi.mainInstance) {
const currentRevision = root.pluginApi.mainInstance.noteCardsRevision || 0;
if (currentRevision !== root.lastRevision) {
root.updateNoteCardsModel();
}
}
}
}
Component.onCompleted: {
updateNoteCardsModel();
}
// Repeater for note cards
Repeater {
id: noteCardsRepeater
model: noteCardsModel
NoteCard {
pluginApi: root.pluginApi
note: noteData
noteIndex: noteIdx
z: 1 // Above background MouseArea
}
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.margins: 16
width: contentRow.width + 16
height: 40
color: Color.mSurfaceVariant
border.color: Color.mOnSurfaceVariant
border.width: 1
radius: Style.radiusM
visible: (root.pluginApi && root.pluginApi.mainInstance && root.pluginApi.mainInstance.noteCards) ? root.pluginApi.mainInstance.noteCards.length > 0 : false
z: 2 // Above everything
RowLayout {
id: contentRow
anchors.centerIn: parent
spacing: 8
// Add note button
NIconButton {
width: 28
height: 28
icon: "add"
tooltipText: pluginApi?.tr("notecards.create-note")
colorBg: Color.mPrimary
colorFg: Color.mOnPrimary
colorBgHover: Qt.lighter(Color.mPrimary, 1.2)
colorFgHover: Color.mOnPrimary
onClicked: {
if (root.pluginApi && root.pluginApi.mainInstance) {
const count = root.pluginApi.mainInstance.noteCards ? root.pluginApi.mainInstance.noteCards.length : 0;
const max = root.pluginApi.mainInstance.maxNoteCards || 20;
if (count >= max) {
ToastService.showWarning(pluginApi?.tr("toast.max-notes").replace("{max}", max));
} else {
root.pluginApi.mainInstance.createNoteCard("");
}
}
}
}
// Vertical separator
Rectangle {
width: 1
height: 24
color: Color.mOnSurfaceVariant
opacity: 0.3
}
// Note icon
NIcon {
icon: "note"
pointSize: 16
color: Color.mOnSurfaceVariant
}
// Count text
NText {
text: {
const count = (root.pluginApi && root.pluginApi.mainInstance && root.pluginApi.mainInstance.noteCards) ? root.pluginApi.mainInstance.noteCards.length : 0;
const max = (root.pluginApi && root.pluginApi.mainInstance) ? (root.pluginApi.mainInstance.maxNoteCards || 20) : 20;
return count + " / " + max;
}
font.pointSize: Style.fontSizeM
font.bold: true
color: {
const count = (root.pluginApi && root.pluginApi.mainInstance && root.pluginApi.mainInstance.noteCards) ? root.pluginApi.mainInstance.noteCards.length : 0;
const max = (root.pluginApi && root.pluginApi.mainInstance) ? (root.pluginApi.mainInstance.maxNoteCards || 20) : 20;
return count >= max ? Color.mError : Color.mOnSurfaceVariant;
}
}
}
}
Component.onDestruction: {
revisionTimer.stop();
noteCardsModel.clear();
}
}
File diff suppressed because it is too large Load Diff
+321
View File
@@ -0,0 +1,321 @@
# Clipper - Advanced Clipboard Manager for Noctalia Shell
**Version 2.3.0** - A powerful clipboard history manager with persistent pinned items, NoteCards sticky notes, seamless ToDo integration and auto-paste.
![Clipboard Preview](Assets/clipboard-preview.png)
## ✨ Features
### ⚙️ Control Center Shortcut
- **Control Center Shortcut**: Access Clipper in Control Center shortcut instead to save widget spaces in your bar
### 📋 Clipboard Management
- **Unlimited History**: Access your entire clipboard history powered by `cliphist`
- **Smart Filtering**: Filter by type (text, images, colors, links, code, emoji, files)
- **Quick Search**: Real-time search through clipboard items
- **Persistent Pinned Items**: Pin important items that survive across sessions
- **Rich Preview**: Image previews, color swatches, and formatted text
### 📝 NoteCards (Sticky Notes)
Quick capture and organize your thoughts with persistent sticky notes:
![NoteCards Demo](Assets/notecard-preview.gif)
- **Individual JSON Storage**: Each notecard stored separately for data safety
- **Editable Titles**: Click title to edit, press Enter to save
- **Color Coding**: 8 preset colors for visual organization
- **Auto-saving Content**: Content saves when panel closes
- **Export to TXT**: Export notecards to `~/Documents/`
- **Drag to Reorder**: Organize cards by dragging (visual feedback)
- **Add Selection**: Capture selected text directly to notecard via keybind
### ✅ ToDo Integration
Seamless integration with Noctalia ToDo plugin:
![ToDo Integration Demo](Assets/todo-preview.gif)
- **Smart Context Menu**: Right-click clipboard items to add to specific ToDo pages
- **Selection to ToDo**: Capture selected text directly to ToDo via keybind
- **Multi-Page Support**: Choose target ToDo page from visual menu
- **Auto-Copy**: Selected text automatically copied to clipboard
### 🎨 Visual Customization
- **Per-Type Color Schemes**: Customize colors for each card type separately
- Text cards, Image cards, Color cards, Link cards, etc.
- **Three Color Properties**: Background, Separator, Foreground (text/icons)
- **Live Preview**: See changes instantly in settings panel
- **Reset to Defaults**: One-click restore to default theme
- ### ⚡ Auto-Paste
- **Toggle in Settings → Auto-Paste**: After selecting a clipboard item, content is automatically pasted into the focused window
- **Right-Click Only mode**: Left-click copies normally; right-click copies and pastes
- **Paste Delay slider**: Tune the delay (1001000 ms) for compositor focus timing
- Requires `wtype` (`sudo pacman -S wtype`); settings show a warning when not installed
## 🎬 Video Demonstrations
### NoteCards in Action
Creating notecards, editing titles and content, color coding, and exporting:
![NoteCards Demo](Assets/notecard-preview.gif)
### ToDo Integration
Adding clipboard items to ToDo, selection to ToDo workflow, and multi-page management:
![ToDo Integration Demo](Assets/todo-preview.gif)
## 🚀 Installation
### From Noctalia Plugin Manager (Recommended)
1. Open Noctalia Settings → Plugins
2. Search for "Clipper"
3. Click Install
4. Reload Noctalia Shell
### Manual Installation
```bash
# Clone repository
cd ~/.config/noctalia/plugins/
git clone https://github.com/blackbartblues/noctalia-clipper clipper
# Reload Noctalia
qs -c noctalia-shell reload
```
## ⌨️ Keybind Setup
### Hyprland Configuration
Add to `~/.config/hypr/keybind.conf`:
```conf
# Clipper - Clipboard Manager
bindr = SUPER, V, exec, qs -c noctalia-shell ipc call plugin:clipper toggle
# Selection to ToDo (chord: Super+V, then C)
binds = SUPER_L, V&C, exec, qs -c noctalia-shell ipc call plugin:clipper addSelectionToTodo
# Selection to NoteCard (chord: Super+V, then X)
binds = SUPER_L, V&X, exec, qs -c noctalia-shell ipc call plugin:clipper addSelectionToNoteCard
```
**Chord Keybinds Explained:**
- Press `Super+V` (don't release Super)
- While holding Super, press `C` → adds selection to ToDo
- While holding Super, press `X` → adds selection to NoteCard
## 🎮 Usage
### Basic Operations
**Toggle Panel:**
```bash
qs -c noctalia-shell ipc call plugin:clipper toggle
```
**Pin/Unpin Item:**
- Click pin icon on clipboard card
- Or use context menu (right-click)
**Add to ToDo:**
- Right-click clipboard item → "Add to ToDo"
- Select target ToDo page from menu
### NoteCards
**Create NoteCard:**
- Click "+ Create Note" button in NoteCards panel
- Or via IPC:
```bash
qs -c noctalia-shell ipc call plugin:clipper addNoteCard "Quick note"
```
**Edit NoteCard:**
- Click title to edit, press Enter to save
- Type in content area (auto-saves on panel close)
**Change Color:**
- Right-click notecard → "Change Color"
- Select from 8 preset colors
**Export NoteCard:**
- Right-click notecard → "Export to .txt"
- Saved to `~/Documents/note_TIMESTAMP.txt`
**Add Selection to NoteCard:**
1. Select text in any application
2. Press keybind (e.g., `Super+V, X`)
3. Choose existing notecard or create new one
4. Text added as bullet point
### Advanced IPC Commands
```bash
# Panel Management
qs -c noctalia-shell ipc call plugin:clipper openPanel
qs -c noctalia-shell ipc call plugin:clipper closePanel
qs -c noctalia-shell ipc call plugin:clipper togglePanel
# Pinned Items
qs -c noctalia-shell ipc call plugin:clipper pinClipboardItem "123456"
qs -c noctalia-shell ipc call plugin:clipper unpinItem "pinned-123"
qs -c noctalia-shell ipc call plugin:clipper copyPinned "pinned-123"
# NoteCards
qs -c noctalia-shell ipc call plugin:clipper addNoteCard "Quick note"
qs -c noctalia-shell ipc call plugin:clipper exportNoteCard "note_abc123"
qs -c noctalia-shell ipc call plugin:clipper addSelectionToNoteCard
# ToDo Integration
qs -c noctalia-shell ipc call plugin:clipper addSelectionToTodo
```
## ⚙️ Settings
### General
- **ToDo Plugin Integration**: Enable/disable ToDo integration (auto-detects if plugin installed)
### NoteCards
- **Enable NoteCards**: Toggle NoteCards panel visibility
- **Default Note Color**: Choose default color for new notecards
- **Current Notes Count**: Shows number of stored notecards
- **Clear All Notes**: One-click removal of all notecards
### Appearance
Customize colors for each card type:
- **Card Type Selector**: Choose which type to customize (Text, Image, Color, Link, Code, Emoji, File)
- **Live Preview**: See changes in real-time
- **Background Color**: Card background
- **Separator Color**: Line between header and content
- **Foreground Color**: Text, icons, and content color
- **Reset to Defaults**: Restore default color scheme
## 📁 Data Storage
```
~/.config/noctalia/plugins/clipper/
├── settings.json # Plugin settings and color customization
├── pinned.json # Persistent pinned items
└── notecards/
├── note_abc123.json # Individual notecard files
├── note_def456.json
└── ...
```
## 🔧 Technical Details
### Dependencies
- **cliphist**: Clipboard history backend (required)
- **wl-clipboard**: Wayland clipboard utilities (required)
- **Noctalia ToDo Plugin**: For ToDo integration (optional)
Install dependencies:
```bash
# Arch Linux
sudo pacman -S cliphist wl-clipboard
# Or use your distribution's package manager
```
### Architecture
- **Main.qml**: Core logic, IPC handlers, data management
- **Panel.qml**: Main clipboard history panel UI
- **NoteCardsPanel.qml**: Sticky notes panel UI
- **TodoPageSelector.qml**: ToDo page selection logic
- **NoteCardSelector.qml**: Notecard selection logic
- **SelectionContextMenu.qml**: Universal context menu for selections
- **ClipboardCard.qml**: Individual clipboard item card
- **NoteCard.qml**: Individual notecard component
- **Settings.qml**: Settings panel UI
- **BarWidget.qml**: Bar widget button
### Translation System
- **16 Languages Supported**: en, de, es, fr, hu, it, ja, ko-KR, nl, pl, pt, ru, sv, tr, uk-UA, zh-CN, zh-TW
- **100% Coverage**: All user-facing strings translated
- **Synchronized Structure**: All translation files have identical keys
### Memory Management
- **Automatic Cleanup**: All components properly destroyed on exit
- **Process Termination**: Background processes stopped on destruction
- **Timer Management**: All timers stopped when not needed
- **No Memory Leaks**: Tested with 500+ operations
## 🐛 Troubleshooting
### Clipboard history not working
```bash
# Ensure cliphist is running
pkill -9 wl-paste; wl-paste --watch cliphist store &
```
### Pinned items not persisting
- Check `~/.config/noctalia/plugins/clipper/pinned.json` exists
- Verify file permissions (should be readable/writable by user)
### NoteCards not saving
- Check `~/.config/noctalia/plugins/clipper/notecards/` directory exists
- Verify write permissions
### ToDo integration not working
1. Check if ToDo plugin is installed and enabled
2. Verify settings: Enable "ToDo Plugin Integration"
3. Check `~/.config/noctalia/plugins.json` contains ToDo plugin
### Context menu not appearing
- Ensure screen is correctly detected
- Check logs: `journalctl --user -u plasma-qs@noctalia-shell.service -f`
## 📝 Changelog
See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
### v2.2.0 (2026-03-11) - Current Release
**Bug Fixes:**
- 🐞 Clear All Notes doesn't clear notes when reopen plugin panel fix
- 🐞 Square Panel Corners at the bottom corners fix
- 🐞 Export a Note multiple times and not deleting it fix
- 🐞 Wait until user press Apply button to apply the settings
- 🐞 clicking the NoteCards panel would close the plugin panel even when the Close Button was enabled. The panel now only closes on background click when the Close Button is disabled.
**Improvements:**
- 🎨 Panel will now attach to bar instead of being separate
- 🎨 Decrease panel Width and Height for a cleaner look
- 🎨 Added a separator between Note Card and Pinned Itemnhanced visual customization
- 🎨 Move Panel Close Button to the top right corner instead of being next to Open Plugin Settings
- 🎨 Added active ring, badge counts for filter buttons
- 🔧 Remaps keybindings using Alt + 1 to 8 instead of Alt + 0 to 7
- 🔧 Mouse Wheel Scroll left and right clipboards support
**Technical:**
- 📦 Zero console.log statements in production
- 🧹 No internal IPC calls (only external API)
- 🔒 Secure command execution (no injection vulnerabilities)
- ✅ Tested: 500+ operations, no memory growth
## 🤝 Contributing
Contributions welcome! Please:
1. Follow QML code style guidelines
2. Use `pluginApi?.tr()` for all user-facing strings
3. Test memory management (Component.onDestruction)
4. Ensure all translation files are synchronized
5. No console.log in production code
## 📄 License
MIT License - See LICENSE file for details
## 👤 Author
**blackbartblues**
- GitHub: [@blackbartblues](https://github.com/blackbartblues)
## 🙏 Acknowledgments
- Noctalia Shell team for the amazing desktop environment
- cliphist for robust clipboard history
- Community contributors and testers
---
**Made with ❤️ for Noctalia Shell**
@@ -0,0 +1,151 @@
# Clipper v2.0.0 Release Notes
**Release Date**: 2026-02-05
## 🎉 Major Features
### 📝 NoteCards (Sticky Notes)
Brand new sticky notes system for quick note-taking:
- Individual JSON storage for each notecard
- Editable titles and auto-saving content
- 8 color presets for organization
- Export to TXT files
- Add selected text directly to notecards via keybind
- Drag to reorder (visual feedback)
**Demo**: [Notecard Preview.mp4](Assets/Notecard%20Preview.mp4)
### ✅ Enhanced ToDo Integration
Completely revamped ToDo integration:
- Selection-to-ToDo with visual page selector
- Context menu for adding clipboard items to specific pages
- Fixed pageId type handling (int instead of string)
- Proper active selector routing
**Demo**: [ToDo Preview.mp4](Assets/ToDo%20Preview.mp4)
### 🌍 Complete Translation Coverage
- 16 languages fully supported
- 30+ toast messages translated
- All UI strings use `pluginApi?.tr()`
- Synchronized structure across all language files
## 🔧 Bug Fixes
### Critical Fixes
- ✅ Fixed `addSelectionToTodo` not adding items to correct page
- ✅ Fixed pageId type (string → int) causing ToDo filtering issues
- ✅ Fixed `handleTodoPageSelected` syntax error (missing closing brace)
- ✅ Fixed selector routing with `activeSelector` property
### High Priority Fixes
- ✅ Fixed timer memory leak in NoteCardsPanel.qml
- ✅ Fixed all hardcoded strings (now use translations)
- ✅ Fixed `appendTextToNoteCard` mutation (now immutable)
### Medium Priority Fixes
- ✅ Added Component.onDestruction cleanup to ClipboardCard.qml
- ✅ Removed all console.log debug statements
- ✅ Removed debug stack traces from Noctalia core
## 🎨 Improvements
### Architecture
- Immutable data patterns throughout codebase
- Proper memory cleanup (timers, processes, connections)
- Active selector routing for context menus
- No internal IPC calls (only external API)
### User Experience
- Universal SelectionContextMenu component
- Smooth notecard interactions
- Visual feedback for all operations
- Consistent color scheme customization
### Performance
- Zero memory leaks (tested with 500+ operations)
- Efficient ListModel-based rendering
- Proper garbage collection
## 📦 Technical Details
### Files Changed
- Main.qml (1176 lines) - Core logic and IPC handlers
- TodoPageSelector.qml - Fixed pageId type
- SelectionContextMenu.qml - Universal context menu
- Panel.qml, NoteCardsPanel.qml - UI improvements
- Settings.qml - Enhanced configuration
- i18n/*.json - All 16 files synchronized
### New Dependencies
None - still uses cliphist and wl-clipboard
### Breaking Changes
None - fully backward compatible with v1.x settings
## 🚀 Upgrade Instructions
### From v1.x
1. **Backup your data** (optional, but recommended):
```bash
cp -r ~/.config/noctalia/plugins/clipper/pinned.json ~/clipper-backup.json
```
2. **Update plugin**:
- Via Plugin Manager: Update button in Noctalia Settings
- Via Git:
```bash
cd ~/.config/noctalia/plugins/clipper
git pull
```
3. **Reload Noctalia**:
```bash
qs -c noctalia-shell reload
```
4. **No migration needed** - all settings and pinned items preserved!
### Fresh Install
See [README.md](README.md) for installation instructions.
## 🎬 Media
### Screenshots
![Clipboard Preview](Assets/clipboard-preview.png)
### Videos
- [Notecard Preview.mp4](Assets/Notecard%20Preview.mp4)
- [ToDo Preview.mp4](Assets/ToDo%20Preview.mp4)
## 📝 Full Changelog
See [CHANGELOG.md](CHANGELOG.md) for complete version history.
## 🙏 Acknowledgments
Special thanks to:
- Code reviewers who identified all issues
- Memory leak testers
- Translation contributors
- Noctalia Shell team
## 🐛 Known Issues
None currently! 🎉
If you find any bugs, please report at:
https://github.com/blackbartblues/noctalia-clipper/issues
## 💡 What's Next?
Planned for v2.1.0:
- Notecard categories/folders
- Search within notecards
- Rich text formatting
- Notecard templates
- Cloud sync (optional)
---
**Enjoy Clipper v2.0.0!** 🚀
@@ -0,0 +1,120 @@
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Wayland
import qs.Commons
import qs.Services.UI
import qs.Widgets
// Universal selection context menu that appears at cursor position
PanelWindow {
id: root
required property ShellScreen screen
property var pluginApi: null
property var menuItems: []
signal itemSelected(string action)
signal cancelled
anchors.top: true
anchors.left: true
anchors.right: true
anchors.bottom: true
visible: false
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
WlrLayershell.namespace: "noctalia-selection-menu-" + (screen?.name || "unknown")
WlrLayershell.exclusionMode: ExclusionMode.Ignore
property int cursorX: 0
property int cursorY: 0
function show(items) {
menuItems = items || [];
visible = true;
getCursorPositionProcess.running = true;
}
function close() {
visible = false;
contextMenu.visible = false;
}
// Get cursor position from hyprctl
Process {
id: getCursorPositionProcess
command: ["hyprctl", "cursorpos", "-j"]
running: false
stdout: StdioCollector {
id: cursorStdout
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
try {
const data = JSON.parse(cursorStdout.text);
root.cursorX = data.x || 0;
root.cursorY = data.y || 0;
// Position menu at cursor
anchorPoint.x = root.cursorX;
anchorPoint.y = root.cursorY;
contextMenu.model = root.menuItems;
contextMenu.anchorItem = anchorPoint;
contextMenu.visible = true;
} catch (e) {
ToastService.showError(pluginApi?.tr("toast.failed-to-get-cursor-position"));
root.close();
}
} else {
root.close();
}
}
}
// Context menu
NPopupContextMenu {
id: contextMenu
visible: false
screen: root.screen
minWidth: 200
onTriggered: (action, item) => {
root.itemSelected(action);
root.close();
}
}
// Anchor point for menu positioning
Item {
id: anchorPoint
width: 1
height: 1
x: 0
y: 0
}
// Fullscreen mouse area to capture outside clicks
MouseArea {
id: mouseCapture
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
root.cancelled();
root.close();
}
}
Component.onDestruction: {
if (getCursorPositionProcess.running) {
getCursorPositionProcess.terminate();
}
close();
}
}
+974
View File
@@ -0,0 +1,974 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
spacing: 0
property var pluginApi: null
// Live preview + revert-on-cancel pattern (approved deviation from AGENTS.md edit-copy).
// User-approved 2026-04-20. Settings changes apply visually in real time to Panel/BarWidget
// but are only persisted to disk when the shell calls saveSettings() (Apply button).
// Closing without Apply restores the snapshot taken on open.
property var _snapshot: null
property bool _applied: false
function _applyPreview(key, value) {
if (!pluginApi) return;
var patch = {};
patch[key] = value;
pluginApi.pluginSettings = Object.assign({}, pluginApi.pluginSettings, patch);
}
property bool valueTodoIntegration: pluginApi?.pluginSettings?.enableTodoIntegration ?? false
property bool valuePincardsEnabled: pluginApi?.pluginSettings?.pincardsEnabled ?? true
property bool valueNotecardsEnabled: pluginApi?.pluginSettings?.notecardsEnabled ?? true
property bool valueShowCloseButton: pluginApi?.pluginSettings?.showCloseButton ?? false
property bool valueFullscreenMode: pluginApi?.pluginSettings?.fullscreenMode ?? false
property bool valueHidePanelBackground: pluginApi?.pluginSettings?.hidePanelBackground ?? false
property bool valueAutoPaste: pluginApi?.pluginSettings?.autoPaste ?? false
property bool valueAutoPasteOnRightClick: pluginApi?.pluginSettings?.autoPasteOnRightClick ?? false
property int valueAutoPasteDelay: pluginApi?.pluginSettings?.autoPasteDelay ?? 300
property int valuePanelWidth: pluginApi?.pluginSettings?.panelWidth ?? 1450
property int valuePanelHeight: pluginApi?.pluginSettings?.panelHeight ?? 0
property var pendingCardColors: JSON.parse(JSON.stringify(defaultCardColors))
property var pendingCustomColors: {
"Text": {
bg: "#555555",
separator: "#000000",
fg: "#e9e4f0"
},
"Image": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"Link": {
bg: "#c7a1d8",
separator: "#000000",
fg: "#1a151f"
},
"Code": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Color": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Emoji": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"File": {
bg: "#e9899d",
separator: "#000000",
fg: "#1e1418"
}
}
// ToDo integration
property bool todoPluginAvailable: false
property bool enableTodoIntegration: pluginApi?.pluginSettings?.enableTodoIntegration ?? false
// Available card types
readonly property var cardTypes: [
{
key: "Text",
name: "Text"
},
{
key: "Image",
name: "Image"
},
{
key: "Link",
name: "Link"
},
{
key: "Code",
name: "Code"
},
{
key: "Color",
name: "Color"
},
{
key: "Emoji",
name: "Emoji"
},
{
key: "File",
name: "File"
}
]
// Available colors from Color scheme
readonly property var colorOptions: [
{
key: "mPrimary",
name: "Primary"
},
{
key: "mOnPrimary",
name: "On Primary"
},
{
key: "mSecondary",
name: "Secondary"
},
{
key: "mOnSecondary",
name: "On Secondary"
},
{
key: "mTertiary",
name: "Tertiary"
},
{
key: "mOnTertiary",
name: "On Tertiary"
},
{
key: "mSurface",
name: "Surface"
},
{
key: "mOnSurface",
name: "On Surface"
},
{
key: "mSurfaceVariant",
name: "Surface Variant"
},
{
key: "mOnSurfaceVariant",
name: "On Surface Variant"
},
{
key: "mOutline",
name: "Outline"
},
{
key: "mError",
name: "Error"
},
{
key: "mOnError",
name: "On Error"
},
{
key: "mHover",
name: "Hover"
},
{
key: "mOnHover",
name: "On Hover"
},
{
key: "custom",
name: "Custom..."
}
]
// Currently selected card type for editing
property string selectedCardType: "Text"
// Default colors per card type
readonly property var defaultCardColors: {
"Text": {
bg: "mOutline",
separator: "mSurface",
fg: "mOnSurface"
},
"Image": {
bg: "mTertiary",
separator: "mSurface",
fg: "mOnTertiary"
},
"Link": {
bg: "mPrimary",
separator: "mSurface",
fg: "mOnPrimary"
},
"Code": {
bg: "mSecondary",
separator: "mSurface",
fg: "mOnSecondary"
},
"Color": {
bg: "mSecondary",
separator: "mSurface",
fg: "mOnSecondary"
},
"Emoji": {
bg: "mHover",
separator: "mSurface",
fg: "mOnHover"
},
"File": {
bg: "mError",
separator: "mSurface",
fg: "mOnError"
}
}
// Current card colors (loaded from settings or defaults)
property var cardColors: JSON.parse(JSON.stringify(defaultCardColors))
// Custom color values (when "custom" is selected)
property var customColors: {
"Text": {
bg: "#555555",
separator: "#000000",
fg: "#e9e4f0"
},
"Image": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"Link": {
bg: "#c7a1d8",
separator: "#000000",
fg: "#1a151f"
},
"Code": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Color": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Emoji": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"File": {
bg: "#e9899d",
separator: "#000000",
fg: "#1e1418"
}
}
// Home directory for path resolution
readonly property string homeDir: Quickshell.env("HOME") || ""
// Check if ToDo plugin is installed and enabled
FileView {
id: pluginsConfigFile
path: root.homeDir + "/.config/noctalia/plugins.json"
printErrors: false
watchChanges: true
onLoaded: {
try {
const content = text();
if (content && content.length > 0) {
const config = JSON.parse(content);
root.todoPluginAvailable = config?.states?.todo?.enabled === true;
}
} catch (e) {
root.todoPluginAvailable = false;
}
}
}
Component.onCompleted: {
// Snapshot settings on open for revert-on-cancel
if (pluginApi) {
_snapshot = JSON.parse(JSON.stringify(pluginApi.pluginSettings));
}
// Load saved settings
if (pluginApi?.pluginSettings?.enableTodoIntegration !== undefined) {
enableTodoIntegration = pluginApi.pluginSettings.enableTodoIntegration;
}
if (pluginApi?.pluginSettings?.cardColors) {
try {
const loaded = JSON.parse(JSON.stringify(pluginApi.pluginSettings.cardColors));
cardColors = loaded;
pendingCardColors = JSON.parse(JSON.stringify(loaded));
} catch (e) {
Logger.w("Clipper", "Failed to load card colors: " + e);
}
}
if (pluginApi?.pluginSettings?.customColors) {
try {
const loaded = JSON.parse(JSON.stringify(pluginApi.pluginSettings.customColors));
customColors = loaded;
pendingCustomColors = JSON.parse(JSON.stringify(loaded));
} catch (e) {
Logger.w("Clipper", "Failed to load custom colors: " + e);
}
}
}
Component.onDestruction: {
if (!_applied && pluginApi && _snapshot) {
pluginApi.pluginSettings = Object.assign({}, _snapshot);
}
}
// Helper to get actual color value
function getColorValue(colorKey, cardType, colorType) {
if (colorKey === "custom") {
return customColors[cardType]?.[colorType] || "#888888";
}
if (typeof Color !== "undefined" && Color[colorKey]) {
return Color[colorKey];
}
return "#888888";
}
// Get current colors for preview
function getPreviewBg() {
return getColorValue(cardColors[selectedCardType]?.bg || "mOutline", selectedCardType, "bg");
}
function getPreviewSeparator() {
return getColorValue(cardColors[selectedCardType]?.separator || "mSurface", selectedCardType, "separator");
}
function getPreviewFg() {
return getColorValue(cardColors[selectedCardType]?.fg || "mOnSurface", selectedCardType, "fg");
}
// Tab bar
NTabBar {
id: tabBar
Layout.fillWidth: true
Layout.bottomMargin: Style.marginM
distributeEvenly: true
currentIndex: tabView.currentIndex
NTabButton {
text: pluginApi?.tr("settings.tab-general")
tabIndex: 0
checked: tabBar.currentIndex === 0
}
NTabButton {
text: pluginApi?.tr("settings.tab-appearance")
tabIndex: 1
checked: tabBar.currentIndex === 1
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.marginS
}
// Tab view
NTabView {
id: tabView
currentIndex: tabBar.currentIndex
// TAB 1: GENERAL
ColumnLayout {
spacing: Style.marginL
// ===== INTEGRATIONS SECTION =====
NText {
text: pluginApi?.tr("settings.integrations")
font.bold: true
font.pointSize: Style.fontSizeL
}
// ToDo Integration Toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.todo-integration")
description: root.todoPluginAvailable ? pluginApi?.tr("settings.todo-description") : pluginApi?.tr("settings.todo-disabled")
enabled: root.todoPluginAvailable
checked: root.valueTodoIntegration
onToggled: checked => {
root.valueTodoIntegration = checked;
root._applyPreview("enableTodoIntegration", checked);
}
}
NDivider {
Layout.fillWidth: true
}
// ===== FEATURES SECTION =====
NText {
text: pluginApi?.tr("settings.features")
font.bold: true
font.pointSize: Style.fontSizeL
}
// Fullscreen Mode Toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.fullscreen-mode")
description: pluginApi?.tr("settings.fullscreen-mode-desc")
checked: root.valueFullscreenMode
onToggled: checked => {
root.valueFullscreenMode = checked;
root._applyPreview("fullscreenMode", checked);
}
}
// Panel Width (hidden when fullscreen)
NSpinBox {
Layout.fillWidth: true
visible: !root.valueFullscreenMode
label: pluginApi?.tr("settings.panel-width")
description: pluginApi?.tr("settings.panel-width-desc")
value: root.valuePanelWidth
from: 400
to: 3840
stepSize: 50
onValueChanged: {
root.valuePanelWidth = value;
root._applyPreview("panelWidth", value);
}
}
// Panel Height (hidden when fullscreen)
NSpinBox {
Layout.fillWidth: true
visible: !root.valueFullscreenMode
label: pluginApi?.tr("settings.panel-height")
description: pluginApi?.tr("settings.panel-height-desc")
value: root.valuePanelHeight
from: 0
to: 2160
stepSize: 50
onValueChanged: {
root.valuePanelHeight = value;
root._applyPreview("panelHeight", value);
}
}
NDivider {
Layout.fillWidth: true
}
// PinCards Enable Toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.pincards-enabled")
description: pluginApi?.tr("settings.pincards-desc")
checked: root.valuePincardsEnabled
onToggled: checked => {
root.valuePincardsEnabled = checked;
root._applyPreview("pincardsEnabled", checked);
}
}
// Pinned items count display
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
visible: pluginApi?.pluginSettings?.pincardsEnabled ?? true
NText {
text: pluginApi?.tr("settings.pincards-items-count")
font.bold: true
}
Item {
Layout.fillWidth: true
}
NText {
text: {
const count = pluginApi?.mainInstance?.pinnedItems?.length || 0;
const max = pluginApi?.mainInstance?.maxPinnedItems || 20;
return count + " / " + max;
}
color: {
const count = pluginApi?.mainInstance?.pinnedItems?.length || 0;
const max = pluginApi?.mainInstance?.maxPinnedItems || 20;
return count >= max ? Color.mError : Color.mOnSurface;
}
}
}
// Clear all pinned items button
NButton {
Layout.alignment: Qt.AlignRight
text: pluginApi?.tr("settings.clear-all-pinned")
icon: "trash"
visible: pluginApi?.pluginSettings?.pincardsEnabled ?? true
enabled: (pluginApi?.mainInstance?.pinnedItems?.length || 0) > 0
onClicked: {
if (pluginApi?.mainInstance) {
// Clear all pinned items
pluginApi.mainInstance.pinnedItems = [];
pluginApi.mainInstance.savePinnedFile();
pluginApi.mainInstance.pinnedRevision++;
ToastService.showNotice(pluginApi?.tr("toast.pinned-cleared"));
}
}
}
// NoteCards Enable Toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.notecards-enabled")
description: pluginApi?.tr("settings.notecards-desc")
checked: root.valueNotecardsEnabled
onToggled: checked => {
root.valueNotecardsEnabled = checked;
root._applyPreview("notecardsEnabled", checked);
}
}
// Notes count display
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
visible: pluginApi?.pluginSettings?.notecardsEnabled ?? true
NText {
text: pluginApi?.tr("settings.notecards-notes-count")
font.bold: true
}
Item {
Layout.fillWidth: true
}
NText {
text: {
const count = pluginApi?.mainInstance?.noteCards?.length || 0;
const max = pluginApi?.mainInstance?.maxNoteCards || 20;
return count + " / " + max;
}
color: {
const count = pluginApi?.mainInstance?.noteCards?.length || 0;
const max = pluginApi?.mainInstance?.maxNoteCards || 20;
return count >= max ? Color.mError : Color.mOnSurface;
}
}
}
// Clear all notes button
NButton {
Layout.alignment: Qt.AlignRight
text: pluginApi?.tr("settings.clear-all-notes")
icon: "trash"
visible: pluginApi?.pluginSettings?.notecardsEnabled ?? true
enabled: (pluginApi?.mainInstance?.noteCards?.length || 0) > 0
onClicked: {
if (pluginApi?.mainInstance) {
pluginApi.mainInstance.clearAllNoteCards();
}
}
}
NDivider {
Layout.fillWidth: true
}
// Hide Panel Background Toggle (hidden when Notecards enabled — no effect with notecards)
NToggle {
Layout.fillWidth: true
visible: !root.valueNotecardsEnabled
label: pluginApi?.tr("settings.hide-panel-background")
description: pluginApi?.tr("settings.hide-panel-background-desc")
checked: root.valueHidePanelBackground
onToggled: checked => {
root.valueHidePanelBackground = checked;
root._applyPreview("hidePanelBackground", checked);
}
}
NDivider {
Layout.fillWidth: true
visible: !root.valueNotecardsEnabled
}
// ===== AUTO-PASTE SECTION =====
NText {
text: pluginApi?.tr("settings.auto-paste-section")
font.bold: true
font.pointSize: Style.fontSizeL
}
// Auto-Paste Toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.auto-paste")
description: pluginApi?.tr("settings.auto-paste-desc")
checked: root.valueAutoPaste
onToggled: checked => {
root.valueAutoPaste = checked;
root._applyPreview("autoPaste", checked);
}
}
// Warning: wtype not installed (visible only when autoPaste=true and wtype unavailable)
Rectangle {
visible: root.valueAutoPaste && !(pluginApi?.mainInstance?.wtypeAvailable ?? false)
Layout.fillWidth: true
Layout.preferredHeight: warningText.implicitHeight + Style.marginM * 2
color: (typeof Color !== "undefined") ? Qt.rgba(Color.mError.r, Color.mError.g, Color.mError.b, 0.15) : "#33CC0000"
radius: Style.radiusS
border.width: 1
border.color: (typeof Color !== "undefined") ? Color.mError : "#CC0000"
NText {
id: warningText
anchors.fill: parent
anchors.margins: Style.marginM
text: pluginApi?.tr("settings.auto-paste-warning")
wrapMode: Text.Wrap
color: (typeof Color !== "undefined") ? Color.mError : "#CC0000"
font.pointSize: Style.fontSizeS
}
}
// RMB Only Toggle (visible only when autoPaste=true)
NToggle {
visible: root.valueAutoPaste
Layout.fillWidth: true
label: pluginApi?.tr("settings.auto-paste-rmb")
description: pluginApi?.tr("settings.auto-paste-rmb-desc")
checked: root.valueAutoPasteOnRightClick
onToggled: checked => {
root.valueAutoPasteOnRightClick = checked;
root._applyPreview("autoPasteOnRightClick", checked);
}
}
// Paste Delay Row (visible only when autoPaste=true)
ColumnLayout {
visible: root.valueAutoPaste
Layout.fillWidth: true
spacing: Style.marginS
NValueSlider {
Layout.fillWidth: true
label: pluginApi?.tr("settings.auto-paste-delay")
description: pluginApi?.tr("settings.auto-paste-delay-desc")
from: 100
to: 1000
stepSize: 50
value: root.valueAutoPasteDelay
text: root.valueAutoPasteDelay + " ms"
onMoved: value => {
root.valueAutoPasteDelay = Math.round(value);
root._applyPreview("autoPasteDelay", Math.round(value));
}
}
}
NDivider {
Layout.fillWidth: true
}
// Show close button toggle
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.show-close-button")
description: pluginApi?.tr("settings.show-close-button-desc")
checked: root.valueShowCloseButton
onToggled: checked => {
root.valueShowCloseButton = checked;
root._applyPreview("showCloseButton", checked);
}
}
} // End General Tab
// TAB 2: APPEARANCE
ColumnLayout {
spacing: Style.marginL
// ===== APPEARANCE SECTION =====
NText {
text: pluginApi?.tr("settings.appearance")
font.bold: true
font.pointSize: Style.fontSizeL
}
// Card type selector
NComboBox {
Layout.fillWidth: true
label: pluginApi?.tr("settings.card-type")
description: pluginApi?.tr("settings.card-type-desc")
model: root.cardTypes
currentKey: root.selectedCardType
onSelected: key => root.selectedCardType = key
}
// Live Preview
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 280
color: (typeof Color !== "undefined") ? Color.mSurfaceVariant : "#333333"
radius: Style.radiusM
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
NText {
text: pluginApi?.tr("settings.preview")
font.bold: true
color: Color.mOnSurface
}
// Preview card
Rectangle {
Layout.preferredWidth: 250
Layout.preferredHeight: 220
Layout.alignment: Qt.AlignHCenter
color: root.getPreviewBg()
radius: Style.radiusM
border.width: 2
border.color: root.getPreviewBg()
ColumnLayout {
anchors.fill: parent
spacing: 0
// Header
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 32
color: root.getPreviewBg()
radius: Style.radiusM
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: parent.radius
color: parent.color
}
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 8
NIcon {
icon: root.selectedCardType === "Image" ? "photo" : root.selectedCardType === "Link" ? "link" : root.selectedCardType === "Code" ? "code" : root.selectedCardType === "Color" ? "palette" : root.selectedCardType === "Emoji" ? "mood-smile" : root.selectedCardType === "File" ? "file" : "align-left"
pointSize: 12
color: root.getPreviewFg()
}
NText {
text: root.selectedCardType
font.bold: true
color: root.getPreviewFg()
}
Item {
Layout.fillWidth: true
}
NIcon {
icon: "trash"
pointSize: 12
color: root.getPreviewFg()
}
}
}
// Separator
Rectangle {
Layout.preferredWidth: parent.width - 10
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 1
color: root.getPreviewSeparator()
}
// Content area
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 8
NText {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
text: pluginApi?.tr("settings.sample-content")
wrapMode: Text.Wrap
color: root.getPreviewFg()
verticalAlignment: Text.AlignTop
}
}
}
}
}
}
// Color settings
NComboBox {
Layout.fillWidth: true
label: pluginApi?.tr("settings.bg-color")
description: pluginApi?.tr("settings.bg-color-desc")
model: root.colorOptions
currentKey: root.cardColors[root.selectedCardType]?.bg || "mOutline"
onSelected: key => {
if (!root.pendingCardColors[root.selectedCardType])
root.pendingCardColors[root.selectedCardType] = {};
root.pendingCardColors[root.selectedCardType].bg = key;
root.cardColors = JSON.parse(JSON.stringify(root.pendingCardColors));
}
}
NColorPicker {
visible: root.cardColors[root.selectedCardType]?.bg === "custom"
Layout.preferredWidth: Style.sliderWidth
Layout.preferredHeight: Style.baseWidgetSize
selectedColor: root.customColors[root.selectedCardType]?.bg || "#888888"
onColorSelected: color => {
if (!root.pendingCustomColors[root.selectedCardType])
root.pendingCustomColors[root.selectedCardType] = {};
root.pendingCustomColors[root.selectedCardType].bg = color.toString();
root.customColors = JSON.parse(JSON.stringify(root.pendingCustomColors));
}
}
NComboBox {
Layout.fillWidth: true
label: pluginApi?.tr("settings.separator-color")
description: pluginApi?.tr("settings.separator-color-desc")
model: root.colorOptions
currentKey: root.cardColors[root.selectedCardType]?.separator || "mSurface"
onSelected: key => {
if (!root.pendingCardColors[root.selectedCardType])
root.pendingCardColors[root.selectedCardType] = {};
root.pendingCardColors[root.selectedCardType].separator = key;
root.cardColors = JSON.parse(JSON.stringify(root.pendingCardColors));
}
}
NColorPicker {
visible: root.cardColors[root.selectedCardType]?.separator === "custom"
Layout.preferredWidth: Style.sliderWidth
Layout.preferredHeight: Style.baseWidgetSize
selectedColor: root.customColors[root.selectedCardType]?.separator || "#000000"
onColorSelected: color => {
if (!root.pendingCustomColors[root.selectedCardType])
root.pendingCustomColors[root.selectedCardType] = {};
root.pendingCustomColors[root.selectedCardType].separator = color.toString();
root.customColors = JSON.parse(JSON.stringify(root.pendingCustomColors));
}
}
NComboBox {
Layout.fillWidth: true
label: pluginApi?.tr("settings.fg-color")
description: pluginApi?.tr("settings.fg-color-desc")
model: root.colorOptions
currentKey: root.cardColors[root.selectedCardType]?.fg || "mOnSurface"
onSelected: key => {
if (!root.pendingCardColors[root.selectedCardType])
root.pendingCardColors[root.selectedCardType] = {};
root.pendingCardColors[root.selectedCardType].fg = key;
root.cardColors = JSON.parse(JSON.stringify(root.pendingCardColors));
}
}
NColorPicker {
visible: root.cardColors[root.selectedCardType]?.fg === "custom"
Layout.preferredWidth: Style.sliderWidth
Layout.preferredHeight: Style.baseWidgetSize
selectedColor: root.customColors[root.selectedCardType]?.fg || "#e9e4f0"
onColorSelected: color => {
if (!root.pendingCustomColors[root.selectedCardType])
root.pendingCustomColors[root.selectedCardType] = {};
root.pendingCustomColors[root.selectedCardType].fg = color.toString();
root.customColors = JSON.parse(JSON.stringify(root.pendingCustomColors));
}
}
// Reset button
NButton {
Layout.alignment: Qt.AlignRight
text: pluginApi?.tr("settings.reset-defaults")
icon: "refresh"
onClicked: {
const defaults = JSON.parse(JSON.stringify(root.defaultCardColors));
const defaultCustom = {
"Text": {
bg: "#555555",
separator: "#000000",
fg: "#e9e4f0"
},
"Image": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"Link": {
bg: "#c7a1d8",
separator: "#000000",
fg: "#1a151f"
},
"Code": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Color": {
bg: "#a984c4",
separator: "#000000",
fg: "#f3edf7"
},
"Emoji": {
bg: "#e0b7c9",
separator: "#000000",
fg: "#20161f"
},
"File": {
bg: "#e9899d",
separator: "#000000",
fg: "#1e1418"
}
};
root.pendingCardColors = defaults;
root.pendingCustomColors = defaultCustom;
root.cardColors = JSON.parse(JSON.stringify(defaults));
root.customColors = JSON.parse(JSON.stringify(defaultCustom));
}
}
} // End Appearance Tab
} // End NTabView
function saveSettings() {
if (!pluginApi)
return;
// Belt-and-suspenders: guarantee final state is correct even if a _applyPreview was missed.
pluginApi.pluginSettings.enableTodoIntegration = root.valueTodoIntegration;
pluginApi.pluginSettings.pincardsEnabled = root.valuePincardsEnabled;
pluginApi.pluginSettings.notecardsEnabled = root.valueNotecardsEnabled;
pluginApi.pluginSettings.showCloseButton = root.valueShowCloseButton;
pluginApi.pluginSettings.fullscreenMode = root.valueFullscreenMode;
pluginApi.pluginSettings.hidePanelBackground = root.valueHidePanelBackground;
pluginApi.pluginSettings.autoPaste = root.valueAutoPaste;
pluginApi.pluginSettings.autoPasteOnRightClick = root.valueAutoPasteOnRightClick;
pluginApi.pluginSettings.autoPasteDelay = root.valueAutoPasteDelay;
pluginApi.pluginSettings.panelWidth = root.valuePanelWidth;
pluginApi.pluginSettings.panelHeight = root.valuePanelHeight;
pluginApi.pluginSettings.cardColors = JSON.parse(JSON.stringify(root.pendingCardColors));
pluginApi.pluginSettings.customColors = JSON.parse(JSON.stringify(root.pendingCustomColors));
if (pluginApi.mainInstance) {
pluginApi.mainInstance.showCloseButton = root.valueShowCloseButton;
}
_applied = true;
pluginApi.saveSettings();
}
}
@@ -0,0 +1,49 @@
import QtQuick
import Quickshell
// Todo page selector wrapper - prepares menu items and handles selection
QtObject {
id: root
property var pluginApi: null
property string selectedText: ""
property var todoPages: []
property var selectionMenu: null
signal pageSelected(int pageId, string pageName)
function buildMenuModel() {
const model = [];
// Add existing todo pages only
for (let i = 0; i < todoPages.length; i++) {
const page = todoPages[i];
model.push({
"label": page.name || pluginApi?.tr("notecards.untitled-placeholder"),
"action": "page-" + page.id,
"icon": "list",
"pageId": page.id
});
}
return model;
}
function show(text, pages) {
selectedText = text || "";
todoPages = pages || [];
if (selectionMenu) {
const menuItems = buildMenuModel();
selectionMenu.show(menuItems);
} else {}
}
function handleItemSelected(action) {
if (action.startsWith("page-")) {
const pageId = parseInt(action.replace("page-", ""));
const page = todoPages.find(p => p.id === pageId);
root.pageSelected(pageId, page ? page.name : pluginApi?.tr("notecards.untitled-placeholder"));
}
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Zwischenablage-Verlauf"
},
"context": {
"toggle": "Clipper umschalten",
"settings": "Einstellungen öffnen"
},
"panel": {
"title": "Zwischenablage-Verlauf",
"pinned-title": "Angeheftete Elemente",
"no-pinned": "Keine angehefteten Elemente",
"settings": "Einstellungen",
"close": "Close",
"search-placeholder": "Suchen...",
"filter-all": "Alle",
"filter-text": "Text",
"filter-images": "Bilder",
"filter-colors": "Farben",
"filter-links": "Links",
"filter-code": "Code",
"filter-emoji": "Emoji",
"filter-files": "Dateien",
"clear-all": "Alles löschen",
"no-matches": "Keine übereinstimmenden Elemente",
"empty": "Zwischenablage ist leer"
},
"card": {
"add-todo": "Zur Aufgabenliste hinzufügen",
"pin": "Anheften",
"delete": "Löschen"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrationen",
"todo-integration": "ToDo-Plugin-Integration",
"todo-description": "Zwischenablage-Elemente direkt zu Ihrer Aufgabenliste hinzufügen",
"todo-disabled": "ToDo-Plugin ist nicht installiert oder deaktiviert",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Vollbildmodus",
"fullscreen-mode-desc": "Das Clipboard-Panel auf den gesamten Bildschirm erweitern",
"hide-panel-background": "Panelhintergrund ausblenden",
"hide-panel-background-desc": "Hintergrund der Panelcontainer entfernen. Hinweis: Pin Cards und Note Cards funktionieren am besten mit aktiviertem Hintergrund.",
"auto-paste-section": "Automatisches Einfügen",
"auto-paste": "Beim Klick automatisch einfügen",
"auto-paste-desc": "Clipboard-Inhalt nach der Auswahl automatisch in das fokussierte Fenster einfügen",
"auto-paste-warning": "wtype wird für das automatische Einfügen benötigt. Installieren mit: sudo pacman -S wtype",
"auto-paste-rmb": "Nur Rechtsklick",
"auto-paste-rmb-desc": "Rechtsklick zum automatischen Einfügen; Linksklick kopiert nur in die Zwischenablage",
"auto-paste-delay": "Einfügeverzögerung",
"auto-paste-delay-desc": "Verzögerung vor dem Einfügen (ms). Erhöhen wenn Fokus-folgt-Maus in Ihrem Compositor aktiv ist.",
"appearance": "Kartendarstellung",
"card-type": "Kartentyp",
"card-type-desc": "Wählen Sie den Kartentyp zum Anpassen",
"preview": "Vorschau",
"sample-content": "Beispielinhalt...",
"bg-color": "Hintergrundfarbe",
"bg-color-desc": "Hintergrundfarbe der Karte",
"separator-color": "Trennlinienfarbe",
"separator-color-desc": "Linie zwischen Kopfzeile und Inhalt",
"fg-color": "Vordergrundfarbe",
"fg-color-desc": "Farbe für Titel, Symbole und Textinhalt",
"reset-defaults": "Auf Standardwerte zurücksetzen",
"panel-width": "Panelbreite",
"panel-width-desc": "Breite des Panels in Pixeln (nur wenn der Vollbildmodus deaktiviert ist)",
"panel-height": "Panelhöhe",
"panel-height-desc": "Höhe des Panels in Pixeln. Auf 0 setzen für automatische Höhe (nur wenn der Vollbildmodus deaktiviert ist)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Clipboard History"
},
"context": {
"toggle": "Toggle Clipper",
"settings": "Open Settings"
},
"panel": {
"title": "Clipboard History",
"pinned-title": "Pinned Items",
"no-pinned": "No pinned items",
"settings": "Settings",
"close": "Close",
"search-placeholder": "Search...",
"filter-all": "All",
"filter-text": "Text",
"filter-images": "Images",
"filter-colors": "Colors",
"filter-links": "Links",
"filter-code": "Code",
"filter-emoji": "Emoji",
"filter-files": "Files",
"clear-all": "Clear All",
"no-matches": "No matching items",
"empty": "Clipboard is empty"
},
"card": {
"add-todo": "Add to ToDo",
"pin": "Pin",
"delete": "Delete"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrations",
"todo-integration": "ToDo Plugin Integration",
"todo-description": "Add clipboard items directly to your ToDo list",
"todo-disabled": "ToDo plugin is not installed or disabled",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes and sticky notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Card Appearance",
"card-type": "Card Type",
"card-type-desc": "Select card type to customize",
"preview": "Preview",
"sample-content": "Sample content preview...",
"bg-color": "Background Color",
"bg-color-desc": "Card background color",
"separator-color": "Separator Color",
"separator-color-desc": "Line between header and content",
"fg-color": "Foreground Color",
"fg-color-desc": "Title, icons and content text color",
"reset-defaults": "Reset to Defaults",
"panel-width": "Panel Width",
"panel-width-desc": "Width of the panel in pixels (only when fullscreen mode is disabled)",
"panel-height": "Panel Height",
"panel-height-desc": "Height of the panel in pixels. Set to 0 for automatic height (only when fullscreen mode is disabled)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Historial del portapapeles"
},
"context": {
"toggle": "Alternar Clipper",
"settings": "Abrir configuración"
},
"panel": {
"title": "Historial del portapapeles",
"pinned-title": "Elementos fijados",
"no-pinned": "Sin elementos fijados",
"settings": "Configuración",
"close": "Close",
"search-placeholder": "Buscar...",
"filter-all": "Todos",
"filter-text": "Texto",
"filter-images": "Imágenes",
"filter-colors": "Colores",
"filter-links": "Enlaces",
"filter-code": "Código",
"filter-emoji": "Emoji",
"filter-files": "Archivos",
"clear-all": "Borrar todo",
"no-matches": "Sin coincidencias",
"empty": "El portapapeles está vacío"
},
"card": {
"add-todo": "Añadir a tareas",
"pin": "Fijar",
"delete": "Eliminar"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integraciones",
"todo-integration": "Integración con ToDo",
"todo-description": "Añadir elementos del portapapeles directamente a tu lista de tareas",
"todo-disabled": "El plugin ToDo no está instalado o está deshabilitado",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Modo pantalla completa",
"fullscreen-mode-desc": "Expandir el panel al tamaño completo de la pantalla",
"hide-panel-background": "Ocultar fondo del panel",
"hide-panel-background-desc": "Eliminar el fondo de los contenedores del panel. Nota: Las tarjetas ancladas y las notas funcionan mejor con el fondo activado.",
"auto-paste-section": "Pegado automático",
"auto-paste": "Pegar automáticamente al hacer clic",
"auto-paste-desc": "Pegar automáticamente el elemento seleccionado en la ventana activa",
"auto-paste-warning": "wtype es necesario para el pegado automático. Instalar con: sudo pacman -S wtype",
"auto-paste-rmb": "Solo clic derecho",
"auto-paste-rmb-desc": "Clic derecho para pegar automáticamente; clic izquierdo solo copia al portapapeles",
"auto-paste-delay": "Retraso de pegado",
"auto-paste-delay-desc": "Retraso antes de pegar (ms). Aumentar si focus-follows-cursor está activado en tu compositor.",
"appearance": "Apariencia de tarjetas",
"card-type": "Tipo de tarjeta",
"card-type-desc": "Selecciona el tipo de tarjeta para personalizar",
"preview": "Vista previa",
"sample-content": "Contenido de ejemplo...",
"bg-color": "Color de fondo",
"bg-color-desc": "Color de fondo de la tarjeta",
"separator-color": "Color del separador",
"separator-color-desc": "Línea entre el encabezado y el contenido",
"fg-color": "Color del texto",
"fg-color-desc": "Color del título, iconos y contenido de texto",
"reset-defaults": "Restaurar valores predeterminados",
"panel-width": "Ancho del panel",
"panel-width-desc": "Ancho del panel en píxeles (solo cuando el modo pantalla completa está desactivado)",
"panel-height": "Alto del panel",
"panel-height-desc": "Alto del panel en píxeles. Establecer en 0 para altura automática (solo cuando el modo pantalla completa está desactivado)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Historique du presse-papiers"
},
"context": {
"toggle": "Basculer Clipper",
"settings": "Ouvrir les paramètres"
},
"panel": {
"title": "Historique du presse-papiers",
"pinned-title": "Éléments épinglés",
"no-pinned": "Aucun élément épinglé",
"settings": "Paramètres",
"close": "Close",
"search-placeholder": "Rechercher...",
"filter-all": "Tous",
"filter-text": "Texte",
"filter-images": "Images",
"filter-colors": "Couleurs",
"filter-links": "Liens",
"filter-code": "Code",
"filter-emoji": "Emoji",
"filter-files": "Fichiers",
"clear-all": "Tout effacer",
"no-matches": "Aucun élément correspondant",
"empty": "Le presse-papiers est vide"
},
"card": {
"add-todo": "Ajouter aux tâches",
"pin": "Épingler",
"delete": "Supprimer"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Intégrations",
"todo-integration": "Intégration du plugin ToDo",
"todo-description": "Ajouter des éléments du presse-papiers directement à votre liste de tâches",
"todo-disabled": "Le plugin ToDo n'est pas installé ou est désactivé",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Mode plein écran",
"fullscreen-mode-desc": "Étendre le panneau au plein écran",
"hide-panel-background": "Masquer l'arrière-plan",
"hide-panel-background-desc": "Supprimer l'arrière-plan des conteneurs du panneau. Note: Les cartes épinglées et les notes fonctionnent mieux avec l'arrière-plan activé.",
"auto-paste-section": "Collage automatique",
"auto-paste": "Coller automatiquement au clic",
"auto-paste-desc": "Coller automatiquement l'élément sélectionné dans la fenêtre active",
"auto-paste-warning": "wtype est requis pour le collage automatique. Installer avec: sudo pacman -S wtype",
"auto-paste-rmb": "Clic droit uniquement",
"auto-paste-rmb-desc": "Clic droit pour coller automatiquement; clic gauche copie dans le presse-papiers",
"auto-paste-delay": "Délai de collage",
"auto-paste-delay-desc": "Délai avant le collage (ms). Augmenter si focus-suit-curseur est activé dans votre compositeur.",
"appearance": "Apparence des cartes",
"card-type": "Type de carte",
"card-type-desc": "Sélectionner le type de carte à personnaliser",
"preview": "Aperçu",
"sample-content": "Aperçu du contenu...",
"bg-color": "Couleur d'arrière-plan",
"bg-color-desc": "Couleur d'arrière-plan de la carte",
"separator-color": "Couleur du séparateur",
"separator-color-desc": "Ligne entre l'en-tête et le contenu",
"fg-color": "Couleur de premier plan",
"fg-color-desc": "Couleur du titre, des icônes et du texte",
"reset-defaults": "Réinitialiser les valeurs par défaut",
"panel-width": "Largeur du panneau",
"panel-width-desc": "Largeur du panneau en pixels (uniquement quand le mode plein écran est désactivé)",
"panel-height": "Hauteur du panneau",
"panel-height-desc": "Hauteur du panneau en pixels. Mettre à 0 pour hauteur automatique (uniquement quand le mode plein écran est désactivé)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Vágólap előzmények"
},
"context": {
"toggle": "Clipper váltása",
"settings": "Beállítások megnyitása"
},
"panel": {
"title": "Vágólap előzmények",
"pinned-title": "Rögzített elemek",
"no-pinned": "Nincs rögzített elem",
"settings": "Beállítások",
"close": "Close",
"search-placeholder": "Keresés...",
"filter-all": "Minden",
"filter-text": "Szöveg",
"filter-images": "Képek",
"filter-colors": "Színek",
"filter-links": "Hivatkozások",
"filter-code": "Kód",
"filter-emoji": "Emoji",
"filter-files": "Fájlok",
"clear-all": "Összes törlése",
"no-matches": "Nincs egyező elem",
"empty": "A vágólap üres"
},
"card": {
"add-todo": "Hozzáadás a feladatokhoz",
"pin": "Rögzítés",
"delete": "Törlés"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrációk",
"todo-integration": "ToDo beépülő integráció",
"todo-description": "Vágólap elemek hozzáadása közvetlenül a feladatlistához",
"todo-disabled": "A ToDo beépülő nincs telepítve vagy le van tiltva",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Kártya megjelenése",
"card-type": "Kártya típusa",
"card-type-desc": "Válassza ki a testreszabandó kártya típust",
"preview": "Előnézet",
"sample-content": "Minta tartalom előnézete...",
"bg-color": "Háttérszín",
"bg-color-desc": "Kártya háttérszíne",
"separator-color": "Elválasztó színe",
"separator-color-desc": "Vonal a fejléc és a tartalom között",
"fg-color": "Előtérszín",
"fg-color-desc": "Cím, ikonok és szöveges tartalom színe",
"reset-defaults": "Alapértelmezések visszaállítása",
"panel-width": "Panel szélessége",
"panel-width-desc": "A panel szélessége képpontokban (csak ha a teljes képernyős mód ki van kapcsolva)",
"panel-height": "Panel magassága",
"panel-height-desc": "A panel magassága képpontokban. 0 értéknél automatikus magasság (csak ha a teljes képernyős mód ki van kapcsolva)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Cronologia appunti"
},
"context": {
"toggle": "Attiva/disattiva Clipper",
"settings": "Apri impostazioni"
},
"panel": {
"title": "Cronologia appunti",
"pinned-title": "Elementi bloccati",
"no-pinned": "Nessun elemento bloccato",
"settings": "Impostazioni",
"close": "Chiudi",
"search-placeholder": "Cerca...",
"filter-all": "Tutti",
"filter-text": "Testo",
"filter-images": "Immagini",
"filter-colors": "Colori",
"filter-links": "Link",
"filter-code": "Codice",
"filter-emoji": "Emoji",
"filter-files": "File",
"clear-all": "Cancella tutto",
"no-matches": "Nessun elemento corrispondente",
"empty": "Gli appunti sono vuoti"
},
"card": {
"add-todo": "Aggiungi a ToDo",
"pin": "Blocca",
"delete": "Elimina"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrazioni",
"todo-integration": "Integrazione plugin ToDo",
"todo-description": "Aggiungi elementi dagli appunti direttamente alla lista ToDo",
"todo-disabled": "Plugin ToDo non installato o disabilitato",
"features": "Funzionalità",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes and sticky notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Modalità schermo intero",
"fullscreen-mode-desc": "Espandi il pannello degli appunti a tutto schermo",
"hide-panel-background": "Nascondi sfondo pannello",
"hide-panel-background-desc": "Rimuovi lo sfondo dai contenitori del pannello. Nota: Le card bloccate e le note funzionano meglio con lo sfondo attivo.",
"auto-paste-section": "Incolla automaticamente",
"auto-paste": "Incolla automaticamente al clic",
"auto-paste-desc": "Incolla automaticamente l'elemento selezionato nella finestra attiva",
"auto-paste-warning": "wtype è necessario per l'incolla automatico. Installa con: sudo pacman -S wtype",
"auto-paste-rmb": "Solo clic destro",
"auto-paste-rmb-desc": "Clic destro per incollare automaticamente; clic sinistro copia solo negli appunti",
"auto-paste-delay": "Ritardo incolla",
"auto-paste-delay-desc": "Ritardo prima di incollare (ms). Aumenta se il focus-segue-cursore è attivo nel tuo compositore.",
"appearance": "Aspetto card",
"card-type": "Tipo card",
"card-type-desc": "Seleziona il tipo di card da personalizzare",
"preview": "Anteprima",
"sample-content": "Anteprima contenuto di esempio...",
"bg-color": "Colore sfondo",
"bg-color-desc": "Colore di sfondo della card",
"separator-color": "Colore separatore",
"separator-color-desc": "Linea tra intestazione e contenuto",
"fg-color": "Colore primo piano",
"fg-color-desc": "Colore per titolo, icone e testo",
"reset-defaults": "Ripristina impostazioni predefinite",
"panel-width": "Larghezza del pannello",
"panel-width-desc": "Larghezza del pannello in pixel (solo quando la modalità schermo intero è disattivata)",
"panel-height": "Altezza del pannello",
"panel-height-desc": "Altezza del pannello in pixel. Imposta 0 per altezza automatica (solo quando la modalità schermo intero è disattivata)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "クリップボード履歴"
},
"context": {
"toggle": "Clipperを切り替え",
"settings": "設定を開く"
},
"panel": {
"title": "クリップボード履歴",
"pinned-title": "ピン留めされたアイテム",
"no-pinned": "ピン留めされたアイテムはありません",
"settings": "設定",
"close": "Close",
"search-placeholder": "検索...",
"filter-all": "すべて",
"filter-text": "テキスト",
"filter-images": "画像",
"filter-colors": "色",
"filter-links": "リンク",
"filter-code": "コード",
"filter-emoji": "絵文字",
"filter-files": "ファイル",
"clear-all": "すべてクリア",
"no-matches": "一致するアイテムがありません",
"empty": "クリップボードは空です"
},
"card": {
"add-todo": "ToDoに追加",
"pin": "ピン留め",
"delete": "削除"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "統合",
"todo-integration": "ToDoプラグイン統合",
"todo-description": "クリップボードアイテムをToDoリストに直接追加",
"todo-disabled": "ToDoプラグインがインストールされていないか無効です",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "カードの外観",
"card-type": "カードタイプ",
"card-type-desc": "カスタマイズするカードタイプを選択",
"preview": "プレビュー",
"sample-content": "サンプルコンテンツのプレビュー...",
"bg-color": "背景色",
"bg-color-desc": "カードの背景色",
"separator-color": "区切り線の色",
"separator-color-desc": "ヘッダーとコンテンツ間の線",
"fg-color": "前景色",
"fg-color-desc": "タイトル、アイコン、テキストコンテンツの色",
"reset-defaults": "デフォルトにリセット",
"panel-width": "パネルの幅",
"panel-width-desc": "パネルの幅をピクセル単位で指定(全画面モードが無効の場合のみ)",
"panel-height": "パネルの高さ",
"panel-height-desc": "パネルの高さをピクセル単位で指定。0 で自動高さ(全画面モードが無効の場合のみ)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "클립보드 기록"
},
"context": {
"toggle": "Clipper 전환",
"settings": "설정 열기"
},
"panel": {
"title": "클립보드 기록",
"pinned-title": "고정된 항목",
"no-pinned": "고정된 항목이 없습니다",
"settings": "설정",
"close": "Close",
"search-placeholder": "검색...",
"filter-all": "전체",
"filter-text": "텍스트",
"filter-images": "이미지",
"filter-colors": "색상",
"filter-links": "링크",
"filter-code": "코드",
"filter-emoji": "이모지",
"filter-files": "파일",
"clear-all": "모두 지우기",
"no-matches": "일치하는 항목이 없습니다",
"empty": "클립보드가 비어 있습니다"
},
"card": {
"add-todo": "할 일에 추가",
"pin": "고정",
"delete": "삭제"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "통합",
"todo-integration": "ToDo 플러그인 통합",
"todo-description": "클립보드 항목을 할 일 목록에 직접 추가",
"todo-disabled": "ToDo 플러그인이 설치되지 않았거나 비활성화됨",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "카드 모양",
"card-type": "카드 유형",
"card-type-desc": "사용자 지정할 카드 유형 선택",
"preview": "미리보기",
"sample-content": "샘플 콘텐츠 미리보기...",
"bg-color": "배경색",
"bg-color-desc": "카드 배경색",
"separator-color": "구분선 색상",
"separator-color-desc": "헤더와 콘텐츠 사이의 선",
"fg-color": "전경색",
"fg-color-desc": "제목, 아이콘 및 텍스트 콘텐츠 색상",
"reset-defaults": "기본값으로 재설정",
"panel-width": "패널 너비",
"panel-width-desc": "패널의 너비(픽셀 단위, 전체 화면 모드가 비활성화된 경우에만 적용)",
"panel-height": "패널 높이",
"panel-height-desc": "패널의 높이(픽셀 단위). 0으로 설정하면 자동 높이(전체 화면 모드가 비활성화된 경우에만 적용)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Klembordgeschiedenis"
},
"context": {
"toggle": "Clipper schakelen",
"settings": "Instellingen openen"
},
"panel": {
"title": "Klembordgeschiedenis",
"pinned-title": "Vastgemaakte items",
"no-pinned": "Geen vastgemaakte items",
"settings": "Instellingen",
"close": "Close",
"search-placeholder": "Zoeken...",
"filter-all": "Alle",
"filter-text": "Tekst",
"filter-images": "Afbeeldingen",
"filter-colors": "Kleuren",
"filter-links": "Links",
"filter-code": "Code",
"filter-emoji": "Emoji",
"filter-files": "Bestanden",
"clear-all": "Alles wissen",
"no-matches": "Geen overeenkomende items",
"empty": "Klembord is leeg"
},
"card": {
"add-todo": "Toevoegen aan takenlijst",
"pin": "Vastmaken",
"delete": "Verwijderen"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integraties",
"todo-integration": "ToDo-plugin-integratie",
"todo-description": "Klembord-items rechtstreeks toevoegen aan uw takenlijst",
"todo-disabled": "ToDo-plugin is niet geïnstalleerd of uitgeschakeld",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Kaartweergave",
"card-type": "Kaarttype",
"card-type-desc": "Selecteer kaarttype om aan te passen",
"preview": "Voorbeeld",
"sample-content": "Voorbeeldinhoud...",
"bg-color": "Achtergrondkleur",
"bg-color-desc": "Achtergrondkleur van kaart",
"separator-color": "Scheidingslijnkleur",
"separator-color-desc": "Lijn tussen koptekst en inhoud",
"fg-color": "Voorgrondkleur",
"fg-color-desc": "Kleur van titel, pictogrammen en tekstinhoud",
"reset-defaults": "Standaardinstellingen herstellen",
"panel-width": "Panelbreedte",
"panel-width-desc": "Breedte van het paneel in pixels (alleen wanneer de volledig-schermmodus is uitgeschakeld)",
"panel-height": "Panelhoogte",
"panel-height-desc": "Hoogte van het paneel in pixels. Stel in op 0 voor automatische hoogte (alleen wanneer de volledig-schermmodus is uitgeschakeld)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Historia schowka"
},
"context": {
"toggle": "Przełącz Clipper",
"settings": "Otwórz ustawienia"
},
"panel": {
"title": "Historia schowka",
"pinned-title": "Przypięte elementy",
"no-pinned": "Brak przypiętych elementów",
"settings": "Ustawienia",
"close": "Close",
"search-placeholder": "Szukaj...",
"filter-all": "Wszystkie",
"filter-text": "Tekst",
"filter-images": "Obrazy",
"filter-colors": "Kolory",
"filter-links": "Linki",
"filter-code": "Kod",
"filter-emoji": "Emoji",
"filter-files": "Pliki",
"clear-all": "Wyczyść wszystko",
"no-matches": "Brak pasujących elementów",
"empty": "Schowek jest pusty"
},
"card": {
"add-todo": "Dodaj do zadań",
"pin": "Przypnij",
"delete": "Usuń"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integracje",
"todo-integration": "Integracja z ToDo",
"todo-description": "Dodawaj elementy ze schowka bezpośrednio do listy zadań",
"todo-disabled": "Plugin ToDo nie jest zainstalowany lub jest wyłączony",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Tryb pełnoekranowy",
"fullscreen-mode-desc": "Rozwiń panel na cały ekran",
"hide-panel-background": "Ukryj tło panelu",
"hide-panel-background-desc": "Usuwa tło kontenerów panelu. Uwaga: Przypięte karty i notatki działają lepiej z włączonym tłem.",
"auto-paste-section": "Automatyczne wklejanie",
"auto-paste": "Wklej automatycznie po kliknięciu",
"auto-paste-desc": "Automatycznie wkleja zaznaczony element do aktywnego okna",
"auto-paste-warning": "wtype jest wymagany do automatycznego wklejania. Zainstaluj: sudo pacman -S wtype",
"auto-paste-rmb": "Tylko prawy przycisk myszy",
"auto-paste-rmb-desc": "Prawy klik = wklej automatycznie; lewy klik = tylko kopiuj do schowka",
"auto-paste-delay": "Opóźnienie wklejania",
"auto-paste-delay-desc": "Opóźnienie przed wklejeniem (ms). Zwiększ jeśli włączony jest focus-follows-cursor w kompozytorze.",
"appearance": "Wygląd kart",
"card-type": "Typ karty",
"card-type-desc": "Wybierz typ karty do personalizacji",
"preview": "Podgląd",
"sample-content": "Przykładowa treść...",
"bg-color": "Kolor tła",
"bg-color-desc": "Kolor tła karty",
"separator-color": "Kolor separatora",
"separator-color-desc": "Linia między nagłówkiem a treścią",
"fg-color": "Kolor tekstu",
"fg-color-desc": "Kolor tytułu, ikon i treści tekstowej",
"reset-defaults": "Przywróć domyślne",
"panel-width": "Szerokość panelu",
"panel-width-desc": "Szerokość panelu w pikselach (tylko gdy tryb pełnoekranowy jest wyłączony)",
"panel-height": "Wysokość panelu",
"panel-height-desc": "Wysokość panelu w pikselach. Ustaw 0 dla automatycznej wysokości (tylko gdy tryb pełnoekranowy jest wyłączony)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Histórico da área de transferência"
},
"context": {
"toggle": "Alternar Clipper",
"settings": "Abrir configurações"
},
"panel": {
"title": "Histórico da área de transferência",
"pinned-title": "Itens fixados",
"no-pinned": "Nenhum item fixado",
"settings": "Configurações",
"close": "Close",
"search-placeholder": "Pesquisar...",
"filter-all": "Todos",
"filter-text": "Texto",
"filter-images": "Imagens",
"filter-colors": "Cores",
"filter-links": "Links",
"filter-code": "Código",
"filter-emoji": "Emoji",
"filter-files": "Arquivos",
"clear-all": "Limpar tudo",
"no-matches": "Nenhum item correspondente",
"empty": "A área de transferência está vazia"
},
"card": {
"add-todo": "Adicionar às tarefas",
"pin": "Fixar",
"delete": "Excluir"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrações",
"todo-integration": "Integração do plugin ToDo",
"todo-description": "Adicionar itens da área de transferência diretamente à sua lista de tarefas",
"todo-disabled": "O plugin ToDo não está instalado ou está desativado",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Aparência do cartão",
"card-type": "Tipo de cartão",
"card-type-desc": "Selecione o tipo de cartão para personalizar",
"preview": "Visualização",
"sample-content": "Visualização de conteúdo de exemplo...",
"bg-color": "Cor de fundo",
"bg-color-desc": "Cor de fundo do cartão",
"separator-color": "Cor do separador",
"separator-color-desc": "Linha entre o cabeçalho e o conteúdo",
"fg-color": "Cor do primeiro plano",
"fg-color-desc": "Cor do título, ícones e conteúdo de texto",
"reset-defaults": "Redefinir para padrões",
"panel-width": "Largura do painel",
"panel-width-desc": "Largura do painel em pixels (apenas quando o modo ecrã completo está desativado)",
"panel-height": "Altura do painel",
"panel-height-desc": "Altura do painel em pixels. Defina 0 para altura automática (apenas quando o modo ecrã completo está desativado)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "История буфера обмена"
},
"context": {
"toggle": "Переключить Clipper",
"settings": "Открыть настройки"
},
"panel": {
"title": "История буфера обмена",
"pinned-title": "Закрепленные элементы",
"no-pinned": "Нет закрепленных элементов",
"settings": "Настройки",
"close": "Close",
"search-placeholder": "Поиск...",
"filter-all": "Все",
"filter-text": "Текст",
"filter-images": "Изображения",
"filter-colors": "Цвета",
"filter-links": "Ссылки",
"filter-code": "Код",
"filter-emoji": "Эмодзи",
"filter-files": "Файлы",
"clear-all": "Очистить все",
"no-matches": "Нет совпадающих элементов",
"empty": "Буфер обмена пуст"
},
"card": {
"add-todo": "Добавить в задачи",
"pin": "Закрепить",
"delete": "Удалить"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Интеграции",
"todo-integration": "Интеграция с плагином ToDo",
"todo-description": "Добавлять элементы буфера обмена прямо в список задач",
"todo-disabled": "Плагин ToDo не установлен или отключен",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Внешний вид карточек",
"card-type": "Тип карточки",
"card-type-desc": "Выберите тип карточки для настройки",
"preview": "Предпросмотр",
"sample-content": "Предпросмотр образца содержимого...",
"bg-color": "Цвет фона",
"bg-color-desc": "Цвет фона карточки",
"separator-color": "Цвет разделителя",
"separator-color-desc": "Линия между заголовком и содержимым",
"fg-color": "Цвет переднего плана",
"fg-color-desc": "Цвет заголовка, значков и текстового содержимого",
"reset-defaults": "Сбросить к значениям по умолчанию",
"panel-width": "Ширина панели",
"panel-width-desc": "Ширина панели в пикселях (только когда полноэкранный режим отключён)",
"panel-height": "Высота панели",
"panel-height-desc": "Высота панели в пикселях. Установите 0 для автоматической высоты (только когда полноэкранный режим отключён)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Urklippshistorik"
},
"context": {
"toggle": "Växla Clipper",
"settings": "Öppna inställningar"
},
"panel": {
"title": "Urklippshistorik",
"pinned-title": "Fästa objekt",
"no-pinned": "Inga fästa objekt",
"settings": "Inställningar",
"close": "Close",
"search-placeholder": "Sök...",
"filter-all": "Alla",
"filter-text": "Text",
"filter-images": "Bilder",
"filter-colors": "Färger",
"filter-links": "Länkar",
"filter-code": "Kod",
"filter-emoji": "Emoji",
"filter-files": "Filer",
"clear-all": "Rensa allt",
"no-matches": "Inga matchande objekt",
"empty": "Urklippet är tomt"
},
"card": {
"add-todo": "Lägg till i uppgifter",
"pin": "Fäst",
"delete": "Ta bort"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Integrationer",
"todo-integration": "ToDo-pluginintegration",
"todo-description": "Lägg till urklippsobjekt direkt till din uppgiftslista",
"todo-disabled": "ToDo-plugin är inte installerat eller inaktiverat",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Kortutseende",
"card-type": "Korttyp",
"card-type-desc": "Välj korttyp att anpassa",
"preview": "Förhandsgranskning",
"sample-content": "Förhandsgranskning av exempelinnehåll...",
"bg-color": "Bakgrundsfärg",
"bg-color-desc": "Kortets bakgrundsfärg",
"separator-color": "Separatorfärg",
"separator-color-desc": "Linje mellan rubrik och innehåll",
"fg-color": "Förgrundsfärg",
"fg-color-desc": "Färg på titel, ikoner och textinnehåll",
"reset-defaults": "Återställ till standard",
"panel-width": "Panelbredd",
"panel-width-desc": "Panelens bredd i pixlar (bara när fullskärmsläget är inaktiverat)",
"panel-height": "Panelhöjd",
"panel-height-desc": "Panelens höjd i pixlar. Ange 0 för automatisk höjd (bara när fullskärmsläget är inaktiverat)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Pano geçmişi"
},
"context": {
"toggle": "Clipper'ı değiştir",
"settings": "Ayarları aç"
},
"panel": {
"title": "Pano geçmişi",
"pinned-title": "Sabitlenmiş öğeler",
"no-pinned": "Sabitlenmiş öğe yok",
"settings": "Ayarlar",
"close": "Close",
"search-placeholder": "Ara...",
"filter-all": "Tümü",
"filter-text": "Metin",
"filter-images": "Resimler",
"filter-colors": "Renkler",
"filter-links": "Bağlantılar",
"filter-code": "Kod",
"filter-emoji": "Emoji",
"filter-files": "Dosyalar",
"clear-all": "Tümünü temizle",
"no-matches": "Eşleşen öğe yok",
"empty": "Pano boş"
},
"card": {
"add-todo": "Yapılacaklara ekle",
"pin": "Sabitle",
"delete": "Sil"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Entegrasyonlar",
"todo-integration": "ToDo eklentisi entegrasyonu",
"todo-description": "Pano öğelerini doğrudan yapılacaklar listenize ekleyin",
"todo-disabled": "ToDo eklentisi yüklü değil veya devre dışı",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Kart görünümü",
"card-type": "Kart türü",
"card-type-desc": "Özelleştirilecek kart türünü seçin",
"preview": "Önizleme",
"sample-content": "Örnek içerik önizlemesi...",
"bg-color": "Arka plan rengi",
"bg-color-desc": "Kart arka plan rengi",
"separator-color": "Ayırıcı rengi",
"separator-color-desc": "Başlık ve içerik arasındaki çizgi",
"fg-color": "Ön plan rengi",
"fg-color-desc": "Başlık, simgeler ve metin içeriği rengi",
"reset-defaults": "Varsayılanlara sıfırla",
"panel-width": "Panel genişliği",
"panel-width-desc": "Panelin piksel cinsinden genişliği (yalnızca tam ekran modu devre dışıyken)",
"panel-height": "Panel yüksekliği",
"panel-height-desc": "Panelin piksel cinsinden yüksekliği. Otomatik yükseklik için 0 girin (yalnızca tam ekran modu devre dışıyken)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "Історія буфера обміну"
},
"context": {
"toggle": "Перемкнути Clipper",
"settings": "Відкрити налаштування"
},
"panel": {
"title": "Історія буфера обміну",
"pinned-title": "Закріплені елементи",
"no-pinned": "Немає закріплених елементів",
"settings": "Налаштування",
"close": "Close",
"search-placeholder": "Пошук...",
"filter-all": "Усі",
"filter-text": "Текст",
"filter-images": "Зображення",
"filter-colors": "Кольори",
"filter-links": "Посилання",
"filter-code": "Код",
"filter-emoji": "Емодзі",
"filter-files": "Файли",
"clear-all": "Очистити все",
"no-matches": "Немає відповідних елементів",
"empty": "Буфер обміну порожній"
},
"card": {
"add-todo": "Додати до завдань",
"pin": "Закріпити",
"delete": "Видалити"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "Інтеграції",
"todo-integration": "Інтеграція з плагіном ToDo",
"todo-description": "Додавати елементи буфера обміну безпосередньо до списку завдань",
"todo-disabled": "Плагін ToDo не встановлено або вимкнено",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "Зовнішній вигляд карток",
"card-type": "Тип картки",
"card-type-desc": "Виберіть тип картки для налаштування",
"preview": "Попередній перегляд",
"sample-content": "Попередній перегляд зразка вмісту...",
"bg-color": "Колір фону",
"bg-color-desc": "Колір фону картки",
"separator-color": "Колір роздільника",
"separator-color-desc": "Лінія між заголовком і вмістом",
"fg-color": "Колір переднього плану",
"fg-color-desc": "Колір заголовка, значків і текстового вмісту",
"reset-defaults": "Скинути до типових значень",
"panel-width": "Ширина панелі",
"panel-width-desc": "Ширина панелі в пікселях (тільки коли повноекранний режим вимкнено)",
"panel-height": "Висота панелі",
"panel-height-desc": "Висота панелі в пікселях. Встановіть 0 для автоматичної висоти (тільки коли повноекранний режим вимкнено)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "剪贴板历史"
},
"context": {
"toggle": "切换 Clipper",
"settings": "打开设置"
},
"panel": {
"title": "剪贴板历史",
"pinned-title": "已固定项目",
"no-pinned": "无已固定项目",
"settings": "设置",
"close": "Close",
"search-placeholder": "搜索...",
"filter-all": "全部",
"filter-text": "文本",
"filter-images": "图片",
"filter-colors": "颜色",
"filter-links": "链接",
"filter-code": "代码",
"filter-emoji": "表情符号",
"filter-files": "文件",
"clear-all": "全部清除",
"no-matches": "无匹配项目",
"empty": "剪贴板为空"
},
"card": {
"add-todo": "添加到待办事项",
"pin": "固定",
"delete": "删除"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "集成",
"todo-integration": "ToDo 插件集成",
"todo-description": "将剪贴板项目直接添加到待办事项列表",
"todo-disabled": "ToDo 插件未安装或已禁用",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "卡片外观",
"card-type": "卡片类型",
"card-type-desc": "选择要自定义的卡片类型",
"preview": "预览",
"sample-content": "示例内容预览...",
"bg-color": "背景颜色",
"bg-color-desc": "卡片背景颜色",
"separator-color": "分隔线颜色",
"separator-color-desc": "标题和内容之间的线条",
"fg-color": "前景颜色",
"fg-color-desc": "标题、图标和文本内容颜色",
"reset-defaults": "重置为默认值",
"panel-width": "面板宽度",
"panel-width-desc": "面板的像素宽度(仅在全屏模式关闭时有效)",
"panel-height": "面板高度",
"panel-height-desc": "面板的像素高度。设为 0 则自动高度(仅在全屏模式关闭时有效)"
}
}
+122
View File
@@ -0,0 +1,122 @@
{
"bar": {
"tooltip": "剪貼簿歷史記錄"
},
"context": {
"toggle": "切換 Clipper",
"settings": "開啟設定"
},
"panel": {
"title": "剪貼簿歷史記錄",
"pinned-title": "已釘選項目",
"no-pinned": "無已釘選項目",
"settings": "設定",
"close": "Close",
"search-placeholder": "搜尋...",
"filter-all": "全部",
"filter-text": "文字",
"filter-images": "圖片",
"filter-colors": "顏色",
"filter-links": "連結",
"filter-code": "程式碼",
"filter-emoji": "表情符號",
"filter-files": "檔案",
"clear-all": "全部清除",
"no-matches": "無符合項目",
"empty": "剪貼簿為空"
},
"card": {
"add-todo": "加入待辦事項",
"pin": "釘選",
"delete": "刪除"
},
"notecards": {
"empty-state": "No notes yet",
"empty-hint": "Click the button below to create your first note",
"create-note": "Create Note",
"change-color": "Change Color",
"export": "Export to .txt",
"delete": "Delete Note",
"untitled-placeholder": "Untitled"
},
"toast": {
"invalid-clipboard-item": "Invalid clipboard item",
"max-pinned-items": "Maximum {max} pinned items reached",
"item-not-found": "Item not found in clipboard",
"failed-to-pin": "Failed to pin item",
"failed-to-pin-image": "Failed to pin image",
"image-too-large": "Image too large to pin (max 5MB)",
"text-too-large": "Text too large to pin (max 1MB)",
"item-pinned": "Item pinned",
"item-unpinned": "Item unpinned",
"max-notes": "Maximum {max} notes reached",
"note-created": "Note created",
"note-deleted": "Note deleted",
"note-not-found": "Note not found",
"note-exported": "Note exported to ~/Documents/{fileName}",
"copied-to-clipboard": "Copied to clipboard",
"failed-to-copy-image": "Failed to copy image",
"failed-to-copy-text": "Failed to copy text",
"no-text-selected": "No text selected",
"failed-to-get-selection": "Failed to get selection",
"no-text-to-add": "No text to add",
"todo-not-available": "ToDo plugin not available",
"added-to-todo": "Added to ToDo",
"failed-to-copy": "Failed to copy to clipboard",
"todo-disabled": "ToDo integration is disabled",
"could-not-open-todo": "Could not open ToDo selector",
"could-not-open-note-selector": "Could not open note selector",
"text-added-to-note": "Text added to note",
"todo-page-created": "New ToDo page created",
"failed-to-get-cursor-position": "Failed to get cursor position",
"notes-cleared": "All notes cleared",
"pinned-cleared": "All pinned items cleared"
},
"settings": {
"tab-general": "General",
"tab-appearance": "Appearance",
"integrations": "整合",
"todo-integration": "ToDo 外掛程式整合",
"todo-description": "將剪貼簿項目直接加入待辦事項清單",
"todo-disabled": "ToDo 外掛程式未安裝或已停用",
"features": "Features",
"pincards-enabled": "Enable Pin Cards",
"pincards-desc": "Show pinned items panel and allow pinning clipboard items",
"pincards-items-count": "Pinned Items",
"clear-all-pinned": "Clear All Pinned Items",
"notecards-enabled": "Enable NoteCards",
"notecards-desc": "Show notecards panel for quick notes",
"notecards-notes-count": "Current Notes",
"clear-all-notes": "Clear All Notes",
"show-close-button": "Show Close Button",
"show-close-button-desc": "Display an X button at the top-right corner to close the panel",
"fullscreen-mode": "Fullscreen Mode",
"fullscreen-mode-desc": "Expand the clipboard panel to fill the entire screen",
"hide-panel-background": "Hide Panel Background",
"hide-panel-background-desc": "Remove background from panel containers. Note: Pin Cards and Note Cards work best with background enabled.",
"auto-paste-section": "Auto-Paste",
"auto-paste": "Auto-Paste on Click",
"auto-paste-desc": "Automatically paste clipboard item into focused window after selecting",
"auto-paste-warning": "wtype is required for auto-paste. Install with: sudo pacman -S wtype",
"auto-paste-rmb": "Right-Click Only",
"auto-paste-rmb-desc": "Use right-click for auto-paste; left-click copies to clipboard without pasting",
"auto-paste-delay": "Paste Delay",
"auto-paste-delay-desc": "Delay before pasting (ms). Increase if focus follows cursor is enabled in your compositor.",
"appearance": "卡片外觀",
"card-type": "卡片類型",
"card-type-desc": "選擇要自訂的卡片類型",
"preview": "預覽",
"sample-content": "範例內容預覽...",
"bg-color": "背景顏色",
"bg-color-desc": "卡片背景顏色",
"separator-color": "分隔線顏色",
"separator-color-desc": "標題與內容之間的線條",
"fg-color": "前景顏色",
"fg-color-desc": "標題、圖示和文字內容顏色",
"reset-defaults": "重設為預設值",
"panel-width": "面板寬度",
"panel-width-desc": "面板的像素寬度(僅在全螢幕模式關閉時有效)",
"panel-height": "面板高度",
"panel-height-desc": "面板的像素高度。設為 0 則自動高度(僅在全螢幕模式關閉時有效)"
}
}
+46
View File
@@ -0,0 +1,46 @@
{
"id": "clipper",
"name": "Clipper",
"version": "2.4.4",
"minNoctaliaVersion": "4.1.2",
"author": "blackbartblues",
"contributors": [
"rscipher001",
"neyfua"
],
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Advanced clipboard manager with history, search, keyboard navigation, ToDo integration, pinned items, notecards/sticky notes, and selection-to-notecard feature. Fully translated with comprehensive i18n support.",
"tags": [
"Utility",
"Bar",
"Panel"
],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"panel": "Panel.qml",
"settings": "Settings.qml",
"controlCenterWidget": "ControlCenterWidget.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"enableTodoIntegration": false,
"pincardsEnabled": true,
"notecardsEnabled": true,
"showCloseButton": true,
"fullscreenMode": false,
"hidePanelBackground": false,
"autoPaste": false,
"autoPasteOnRightClick": false,
"autoPasteDelay": 300,
"panelWidth": 1450,
"panelHeight": 0,
"cardColors": {},
"customColors": {}
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

@@ -0,0 +1,150 @@
import QtQuick
import Quickshell
import qs.Commons
import qs.Modules.DesktopWidgets
import qs.Services.Media
DraggableDesktopWidget {
id: root
property var pluginApi: null
implicitWidth: Math.round(300 * widgetScale)
implicitHeight: Math.round(300 * widgetScale)
showBackground: false
// Scaled dimensions
readonly property int scaledRadiusL: Math.round(Style.radiusL * widgetScale)
// Settings from plugin
readonly property real sensitivity: widgetData?.sensitivity ?? pluginApi?.pluginSettings?.sensitivity ?? pluginApi?.manifest?.metadata?.defaultSettings?.sensitivity
readonly property real rotationSpeed: widgetData?.rotationSpeed ?? pluginApi?.pluginSettings?.rotationSpeed ?? pluginApi?.manifest?.metadata?.defaultSettings?.rotationSpeed
readonly property real barWidth: widgetData?.barWidth ?? pluginApi?.pluginSettings?.barWidth ?? pluginApi?.manifest?.metadata?.defaultSettings?.barWidth
readonly property real ringOpacity: widgetData?.ringOpacity ?? pluginApi?.pluginSettings?.ringOpacity ?? pluginApi?.manifest?.metadata?.defaultSettings?.ringOpacity
readonly property real bloomIntensity: widgetData?.bloomIntensity ?? pluginApi.pluginSettings?.bloomIntensity ?? pluginApi?.manifest?.metadata?.defaultSettings?.bloomIntensity
readonly property int visualizationMode: widgetData?.visualizationMode ?? pluginApi?.pluginSettings?.visualizationMode ?? pluginApi?.manifest?.metadata?.defaultSettings?.visualizationMode ?? 3
readonly property real waveThickness: widgetData?.waveThickness ?? pluginApi?.pluginSettings?.waveThickness ?? pluginApi?.manifest?.metadata?.defaultSettings?.waveThickness ?? 1.0
readonly property real innerDiameter: widgetData?.innerDiameter ?? pluginApi?.pluginSettings?.innerDiameter ?? pluginApi?.manifest?.metadata?.defaultSettings?.innerDiameter ?? 0.7
readonly property bool fadeWhenIdle: widgetData?.fadeWhenIdle ?? pluginApi?.pluginSettings?.fadeWhenIdle ?? false
readonly property bool useCustomColors: widgetData?.useCustomColors ?? pluginApi?.pluginSettings?.useCustomColors ?? false
readonly property color customPrimaryColor: widgetData?.customPrimaryColor ?? pluginApi?.pluginSettings?.customPrimaryColor ?? "#6750A4"
readonly property color customSecondaryColor: widgetData?.customSecondaryColor ?? pluginApi?.pluginSettings?.customSecondaryColor ?? "#625B71"
// Animation time for shader (0 to 3600, 1 hour cycle)
property real shaderTime: 0
NumberAnimation on shaderTime {
loops: Animation.Infinite
from: 0
to: 3600
duration: 3600000
running: !SpectrumService.isIdle
}
// Hidden canvas that encodes audio data as a 32x1 texture
Canvas {
id: audioCanvas
width: 32
height: 1
visible: false
onPaint: {
var ctx = getContext("2d");
var values = SpectrumService.values;
if (!values || values.length === 0) {
ctx.fillStyle = "black";
ctx.fillRect(0, 0, 32, 1);
return;
}
for (var i = 0; i < 32; i++) {
var v = values[i] || 0;
// Encode amplitude in grayscale (R=G=B=amplitude)
var c = Math.floor(v * 255);
ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
ctx.fillRect(i, 0, 1, 1);
}
}
}
// Trigger canvas repaint when audio data changes
Connections {
target: SpectrumService
function onValuesChanged() {
if (!SpectrumService.isIdle) {
audioCanvas.requestPaint();
}
}
}
// Unique instance ID for SpectrumService registration
// This prevents the old widget's destruction from unregistering the new widget
readonly property string spectrumInstanceId: "plugin:fancy-audiovisualizer:" + Date.now() + Math.random()
// Register with SpectrumService when pluginApi becomes available
onPluginApiChanged: {
if (pluginApi) {
SpectrumService.registerComponent(spectrumInstanceId);
audioCanvas.requestPaint();
}
}
Component.onDestruction: {
SpectrumService.unregisterComponent(spectrumInstanceId);
}
// Audio texture source (outside ShaderEffect to avoid 'source' property warning)
ShaderEffectSource {
id: audioTextureSource
sourceItem: audioCanvas
live: true
hideSource: true
}
// The shader effect visualization
ShaderEffect {
id: visualizer
anchors.fill: parent
visible: pluginApi !== null
opacity: (root.fadeWhenIdle && SpectrumService.isIdle) ? 0 : 1
Behavior on opacity {
NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
}
// Audio texture - named 'source' to match ShaderEffectSource's property and avoid warning
property var source: audioTextureSource
// Uniforms passed to shader
property real time: root.shaderTime
property real itemWidth: visualizer.width
property real itemHeight: visualizer.height
property color primaryColor: root.useCustomColors ? root.customPrimaryColor : Color.mPrimary
property color secondaryColor: root.useCustomColors ? root.customSecondaryColor : Color.mSecondary
property real sensitivity: root.sensitivity
property real rotationSpeed: root.rotationSpeed
property real barWidth: root.barWidth
property real ringOpacity: root.ringOpacity
property real cornerRadius: scaledRadiusL
property real bloomIntensity: root.bloomIntensity
property real visualizationMode: root.visualizationMode
property real waveThickness: root.waveThickness
property real innerDiameter: root.innerDiameter
fragmentShader: pluginApi ? Qt.resolvedUrl(pluginApi.pluginDir + "/shaders/visualizer.frag.qsb") : ""
}
// Fallback when shader not loaded
Rectangle {
anchors.fill: parent
color: Color.mSurface
radius: scaledRadiusL
visible: !visualizer.visible || visualizer.fragmentShader === ""
Text {
anchors.centerIn: parent
text: "Loading..."
color: Color.mOnSurface
font.pointSize: Math.round(Style.fontSizeM * widgetScale)
}
}
}
@@ -0,0 +1,244 @@
import QtQuick
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property var pluginApi: null
property var widgetSettings: null
property var screen: null
spacing: Style.marginM
// Local state for editing
property real valueSensitivity: widgetSettings?.data?.sensitivity ?? pluginApi?.pluginSettings?.sensitivity ?? 1.5
property real valueRotationSpeed: widgetSettings?.data?.rotationSpeed ?? pluginApi?.pluginSettings?.rotationSpeed ?? 0.5
property real valueBarWidth: widgetSettings?.data?.barWidth ?? pluginApi?.pluginSettings?.barWidth ?? 0.6
property real valueRingOpacity: widgetSettings?.data?.ringOpacity ?? pluginApi?.pluginSettings?.ringOpacity ?? 0.8
property real valueBloomIntensity: widgetSettings?.data?.bloomIntensity ?? pluginApi?.pluginSettings?.bloomIntensity ?? 0.5
property int valueVisualizationMode: widgetSettings?.data?.visualizationMode ?? pluginApi?.pluginSettings?.visualizationMode ?? 3
property real valueWaveThickness: widgetSettings?.data?.waveThickness ?? pluginApi?.pluginSettings?.waveThickness ?? 1.0
property real valueInnerDiameter: widgetSettings?.data?.innerDiameter ?? pluginApi?.pluginSettings?.innerDiameter ?? 0.7
property bool valueFadeWhenIdle: widgetSettings?.data?.fadeWhenIdle ?? pluginApi?.pluginSettings?.fadeWhenIdle ?? true
property bool valueUseCustomColors: widgetSettings?.data?.useCustomColors ?? pluginApi?.pluginSettings?.useCustomColors ?? false
property color valueCustomPrimaryColor: widgetSettings?.data?.customPrimaryColor ?? pluginApi?.pluginSettings?.customPrimaryColor ?? "#6750A4"
property color valueCustomSecondaryColor: widgetSettings?.data?.customSecondaryColor ?? pluginApi?.pluginSettings?.customSecondaryColor ?? "#625B71"
// Mode helpers
readonly property bool modeHasBars: valueVisualizationMode === 0 || valueVisualizationMode === 3 || valueVisualizationMode === 5
readonly property bool modeHasWave: valueVisualizationMode === 1 || valueVisualizationMode === 4 || valueVisualizationMode === 5
readonly property bool modeHasRings: valueVisualizationMode >= 2
NHeader {
label: pluginApi?.tr("settings.title") ?? "Visualizer Settings"
description: pluginApi?.tr("settings.description") ?? "Configure the audio visualizer appearance"
}
// Visualization mode selector
NComboBox {
Layout.fillWidth: true
label: pluginApi?.tr("settings.visualizationMode") ?? "Visualization Mode"
description: pluginApi?.tr("settings.visualizationMode-description") ?? "Choose visualization style"
model: [
{"key": "0", "name": pluginApi?.tr("settings.mode.bars") ?? "Bars"},
{"key": "1", "name": pluginApi?.tr("settings.mode.wave") ?? "Wave"},
{"key": "2", "name": pluginApi?.tr("settings.mode.rings") ?? "Rings"},
{"key": "3", "name": pluginApi?.tr("settings.mode.barsRings") ?? "Bars + Rings"},
{"key": "4", "name": pluginApi?.tr("settings.mode.waveRings") ?? "Wave + Rings"},
{"key": "5", "name": pluginApi?.tr("settings.mode.all") ?? "All"}
]
currentKey: String(root.valueVisualizationMode)
onSelected: key => {
root.valueVisualizationMode = parseInt(key);
root.saveSettings();
}
}
// Wave thickness slider (shown when mode includes wave)
NValueSlider {
Layout.fillWidth: true
visible: root.modeHasWave
label: pluginApi?.tr("settings.waveThickness") ?? "Wave Thickness"
value: root.valueWaveThickness
from: 0.3
to: 2.0
stepSize: 0.1
onMoved: value => {
root.valueWaveThickness = value;
root.saveSettings();
}
}
// Sensitivity slider
NValueSlider {
Layout.fillWidth: true
label: pluginApi?.tr("settings.sensitivity") ?? "Sensitivity"
value: root.valueSensitivity
from: 0.5
to: 3.0
stepSize: 0.1
onMoved: value => {
root.valueSensitivity = value;
root.saveSettings();
}
}
// Rotation speed slider
NValueSlider {
Layout.fillWidth: true
label: pluginApi?.tr("settings.rotationSpeed") ?? "Rotation Speed"
value: root.valueRotationSpeed
from: 0.0
to: 2.0
stepSize: 0.1
onMoved: value => {
root.valueRotationSpeed = value;
root.saveSettings();
}
}
// Bar width slider (shown when mode includes bars)
NValueSlider {
Layout.fillWidth: true
visible: root.modeHasBars
label: pluginApi?.tr("settings.barWidth") ?? "Bar Width"
value: root.valueBarWidth
from: 0.2
to: 1.0
stepSize: 0.1
onMoved: value => {
root.valueBarWidth = value;
root.saveSettings();
}
}
// Ring opacity slider (shown when mode includes rings)
NValueSlider {
Layout.fillWidth: true
visible: root.modeHasRings
label: pluginApi?.tr("settings.ringOpacity") ?? "Ring Opacity"
value: root.valueRingOpacity
from: 0.0
to: 1.0
stepSize: 0.1
onMoved: value => {
root.valueRingOpacity = value;
root.saveSettings();
}
}
// Base diameter slider
NValueSlider {
Layout.fillWidth: true
label: pluginApi?.tr("settings.innerDiameter") ?? "Inner Diameter"
value: root.valueInnerDiameter
from: 0
to: 1
stepSize: 0.05
onMoved: value => {
root.valueInnerDiameter = value;
root.saveSettings();
}
}
// Bloom intensity slider
NValueSlider {
Layout.fillWidth: true
label: pluginApi?.tr("settings.bloomIntensity") ?? "Bloom Intensity"
value: root.valueBloomIntensity
from: 0.0
to: 1.0
stepSize: 0.05
onMoved: value => {
root.valueBloomIntensity = value;
root.saveSettings();
}
}
// Fade when idle toggle
NToggle {
label: pluginApi?.tr("settings.fadeWhenIdle") ?? "Fade When Idle"
description: pluginApi?.tr("settings.fadeWhenIdle-description") ?? "Fade out visualizer when no audio is playing"
checked: root.valueFadeWhenIdle
onToggled: checked => {
root.valueFadeWhenIdle = checked;
root.saveSettings();
}
}
// Use custom colors toggle
NToggle {
label: pluginApi?.tr("settings.useCustomColors") ?? "Use Custom Colors"
description: pluginApi?.tr("settings.useCustomColors-description") ?? "Override theme colors with custom colors"
checked: root.valueUseCustomColors
onToggled: checked => {
root.valueUseCustomColors = checked;
root.saveSettings();
}
}
// Custom primary color picker
RowLayout {
Layout.fillWidth: true
visible: root.valueUseCustomColors
spacing: Style.marginM
NText {
text: pluginApi?.tr("settings.customPrimaryColor") ?? "Primary Color"
Layout.fillWidth: true
}
NColorPicker {
screen: Screen
selectedColor: root.valueCustomPrimaryColor
onColorSelected: color => {
root.valueCustomPrimaryColor = color;
root.saveSettings();
}
}
}
// Custom secondary color picker
RowLayout {
Layout.fillWidth: true
visible: root.valueUseCustomColors
spacing: Style.marginM
NText {
text: pluginApi?.tr("settings.customSecondaryColor") ?? "Secondary Color"
Layout.fillWidth: true
}
NColorPicker {
screen: Screen
selectedColor: root.valueCustomSecondaryColor
onColorSelected: color => {
root.valueCustomSecondaryColor = color;
root.saveSettings();
}
}
}
// Called when user clicks Apply/Save
function saveSettings() {
if (!widgetSettings)
return;
widgetSettings.data.sensitivity = root.valueSensitivity;
widgetSettings.data.rotationSpeed = root.valueRotationSpeed;
widgetSettings.data.barWidth = root.valueBarWidth;
widgetSettings.data.ringOpacity = root.valueRingOpacity;
widgetSettings.data.bloomIntensity = root.valueBloomIntensity;
widgetSettings.data.visualizationMode = root.valueVisualizationMode;
widgetSettings.data.waveThickness = root.valueWaveThickness;
widgetSettings.data.innerDiameter = root.valueInnerDiameter;
widgetSettings.data.fadeWhenIdle = root.valueFadeWhenIdle;
widgetSettings.data.useCustomColors = root.valueUseCustomColors;
widgetSettings.data.customPrimaryColor = root.valueCustomPrimaryColor.toString();
widgetSettings.data.customSecondaryColor = root.valueCustomSecondaryColor.toString();
widgetSettings.save();
}
}
@@ -0,0 +1,38 @@
# Fancy Audiovisualizer
A circular audio visualizer desktop widget for Noctalia Shell with shader-based rendering and multiple visualization modes.
## Features
- **Multiple Visualization Modes**: Bars, Wave, Rings, or combinations (Bars+Rings, Wave+Rings, All)
- **Shader-Based Rendering**: Smooth, GPU-accelerated visualization using custom fragment shaders
- **Theme Integration**: Automatically uses Noctalia theme colors, with optional custom color override
- **Configurable Appearance**: Adjust sensitivity, rotation speed, bar width, ring opacity, bloom intensity, and more
- **Idle Fade**: Optional fade-out when no audio is playing
## Installation
This plugin is part of the `noctalia-plugins` repository.
## Configuration
Access the plugin settings in Noctalia to configure the following options:
- **Visualization Mode**: Choose between Bars, Wave, Rings, Bars+Rings, Wave+Rings, or All
- **Wave Thickness**: Thickness of the wave visualization (when enabled)
- **Sensitivity**: Audio response sensitivity (0.5 - 3.0)
- **Rotation Speed**: Speed of visualization rotation (0.0 - 2.0)
- **Bar Width**: Width of audio bars (when enabled)
- **Ring Opacity**: Opacity of background rings (when enabled)
- **Inner Diameter**: Size of the inner empty area
- **Bloom Intensity**: Glow/bloom effect strength
- **Fade When Idle**: Fade out when no audio is playing
- **Custom Colors**: Override theme colors with custom primary and secondary colors
## Usage
Add the widget to your desktop via the Noctalia desktop widgets interface. The visualizer responds to system audio.
## Requirements
- Noctalia 3.7.2 or later
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Balkenbreite",
"bloomIntensity": "Blütenintensität",
"customPrimaryColor": "Primärfarbe",
"customSecondaryColor": "Sekundärfarbe",
"description": "Audiovisualisierungsdarstellung konfigurieren",
"fadeWhenIdle": "Ausblenden bei Inaktivität",
"fadeWhenIdle-description": "Visualisierer ausblenden, wenn keine Audioausgabe erfolgt",
"innerDiameter": "Innendurchmesser",
"mode": {
"all": "Alle",
"bars": "Bars",
"barsRings": "Barren + Ringe",
"rings": "Ringe",
"wave": "Welle",
"waveRings": "Welle + Ringe"
},
"ringOpacity": "Ring-Opazität",
"rotationSpeed": "Drehzahl",
"sensitivity": "Empfindlichkeit",
"title": "Visualisierungseinstellungen",
"useCustomColors": "Benutzerdefinierte Farben verwenden",
"useCustomColors-description": "Themenfarben mit benutzerdefinierten Farben überschreiben",
"visualizationMode": "Visualisierungsmodus",
"visualizationMode-description": "Visualisierungsstil wählen",
"waveThickness": "Wellendicke"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Bar Width",
"bloomIntensity": "Bloom Intensity",
"customPrimaryColor": "Primary Color",
"customSecondaryColor": "Secondary Color",
"description": "Configure the audio visualizer appearance",
"fadeWhenIdle": "Fade When Idle",
"fadeWhenIdle-description": "Fade out visualizer when no audio is playing",
"innerDiameter": "Inner Diameter",
"mode": {
"all": "All",
"bars": "Bars",
"barsRings": "Bars + Rings",
"rings": "Rings",
"wave": "Wave",
"waveRings": "Wave + Rings"
},
"ringOpacity": "Ring Opacity",
"rotationSpeed": "Rotation Speed",
"sensitivity": "Sensitivity",
"title": "Visualizer Settings",
"useCustomColors": "Use Custom Colors",
"useCustomColors-description": "Override theme colors with custom colors",
"visualizationMode": "Visualization Mode",
"visualizationMode-description": "Choose visualization style",
"waveThickness": "Wave Thickness"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Ancho de barra",
"bloomIntensity": "Intensidad de floración",
"customPrimaryColor": "Color primario",
"customSecondaryColor": "Color secundario",
"description": "Configurar la apariencia del visualizador de audio",
"fadeWhenIdle": "Desvanecer al estar inactivo",
"fadeWhenIdle-description": "Atenuar el visualizador cuando no se reproduce audio.",
"innerDiameter": "Diámetro interior",
"mode": {
"all": "Todo",
"bars": "Barras",
"barsRings": "Barras + Anillas",
"rings": "Anillos",
"wave": "Ola",
"waveRings": "Onda + Anillos"
},
"ringOpacity": "Opacidad del anillo",
"rotationSpeed": "Velocidad de rotación",
"sensitivity": "Sensibilidad",
"title": "Ajustes del Visualizador",
"useCustomColors": "Usar colores personalizados",
"useCustomColors-description": "Anular los colores del tema con colores personalizados",
"visualizationMode": "Modo de visualización",
"visualizationMode-description": "Elegir estilo de visualización",
"waveThickness": "Espesor de la onda"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Largeur de la barre",
"bloomIntensity": "Intensité de l'éclat",
"customPrimaryColor": "Couleur primaire",
"customSecondaryColor": "Couleur secondaire",
"description": "Configurer l'apparence du visualiseur audio",
"fadeWhenIdle": "Estomper en cas d'inactivité",
"fadeWhenIdle-description": "Estomper le visualiseur en l'absence de son.",
"innerDiameter": "Diamètre intérieur",
"mode": {
"all": "Tout",
"bars": "Barres",
"barsRings": "Barres + Anneaux",
"rings": "Anneaux",
"wave": "Vague",
"waveRings": "Onde + Anneaux"
},
"ringOpacity": "Opacité de l'anneau",
"rotationSpeed": "Vitesse de rotation",
"sensitivity": "Sensibilité",
"title": "Paramètres du visualiseur",
"useCustomColors": "Utiliser des couleurs personnalisées",
"useCustomColors-description": "Remplacer les couleurs du thème par des couleurs personnalisées",
"visualizationMode": "Mode de visualisation",
"visualizationMode-description": "Choisir le style de visualisation",
"waveThickness": "Épaisseur de la vague"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Sávszélesség",
"bloomIntensity": "Virágzás intenzitása",
"customPrimaryColor": "Elsődleges szín",
"customSecondaryColor": "Másodlagos szín",
"description": "Az audio vizualizáló megjelenésének beállítása",
"fadeWhenIdle": "Halványítás tétlenség esetén",
"fadeWhenIdle-description": "A vizualizáció elhalványítása, ha nincs hang lejátszva",
"innerDiameter": "Belső átmérő",
"mode": {
"all": "Összes",
"bars": "Sávok",
"barsRings": "Sávok + Gyűrűk",
"rings": "Gyűrűk",
"wave": "Hullám",
"waveRings": "Hullám + Gyűrűk"
},
"ringOpacity": "Gyűrű átlátszósága",
"rotationSpeed": "Forgási sebesség",
"sensitivity": "Érzékenység",
"title": "Vizualizációs beállítások",
"useCustomColors": "Egyéni színek használata",
"useCustomColors-description": "A téma színeinek felülírása egyéni színekkel",
"visualizationMode": "Megjelenítési mód",
"visualizationMode-description": "Válassz megjelenítési stílust",
"waveThickness": "Hullámvastagság"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Breite der Leiste",
"bloomIntensity": "Intensità Bloom",
"customPrimaryColor": "Ngjyra parësore",
"customSecondaryColor": "Ngjyra dytësore",
"description": "Konfigurieren Sie das Aussehen des Audio-Visualisierers.",
"fadeWhenIdle": "Verblassen im Leerlauf",
"fadeWhenIdle-description": "Vizualizátor eltüntetése, ha nincs hang lejátszva.",
"innerDiameter": "Innendurchmesser",
"mode": {
"all": "Kaikki",
"bars": "Pritličja",
"barsRings": "Stangen + Ringe",
"rings": "Hringar",
"wave": "Val",
"waveRings": "Welle + Ringe"
},
"ringOpacity": "Neprůhlednost prstence",
"rotationSpeed": "Schnelllaufdrehzahl",
"sensitivity": "Ndjeshmëri",
"title": "Mga Setting ng Visualizer",
"useCustomColors": "Përdorni Ngjyra të Personalizuara",
"useCustomColors-description": "Ngjyrat e personalizuara mbivendosin ngjyrat e temës",
"visualizationMode": "Módszer megjelenítése",
"visualizationMode-description": "Zgjidhni stilin e vizualizimit",
"waveThickness": "Onda-lodiera"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "バーの幅",
"bloomIntensity": "開花強度",
"customPrimaryColor": "原色",
"customSecondaryColor": "二次色",
"description": "オーディオビジュアライザーの外観を設定します。",
"fadeWhenIdle": "アイドル時にフェード",
"fadeWhenIdle-description": "音声が再生されていないときは、ビジュアライザーをフェードアウトする",
"innerDiameter": "内径",
"mode": {
"all": "すべて",
"bars": "バー",
"barsRings": "鉄棒と輪",
"rings": "リング",
"wave": "波",
"waveRings": "波紋 + リング"
},
"ringOpacity": "リングの不透明度",
"rotationSpeed": "回転速度",
"sensitivity": "感度",
"title": "ビジュアライザー設定",
"useCustomColors": "カスタムカラーを使用する",
"useCustomColors-description": "カスタムカラーでテーマの色をオーバーライドする",
"visualizationMode": "可視化モード",
"visualizationMode-description": "視覚化のスタイルを選択",
"waveThickness": "波厚"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Firehiya Barê",
"bloomIntensity": "Zehfîya Gurtî",
"customPrimaryColor": "Rengê Seretayî",
"customSecondaryColor": "Rengê Duyemîn",
"description": "Xuyakirina dîmenê bihîstwerî yê dengî",
"fadeWhenIdle": "Dema de bêdengiyê vemirîne",
"fadeWhenIdle-description": "Dîmenkerê dema ku tu deng lê nabe, hêdî hêdî winda bike",
"innerDiameter": "Dirêjbûna Hundir",
"mode": {
"all": "Hemû",
"bars": "Bars",
"barsRings": "Bars + Rings",
"rings": "Xelqet",
"wave": "Pêl",
"waveRings": "Pêl + Xelek"
},
"ringOpacity": "Ronahiya Zengil",
"rotationSpeed": "Leza Şewitandinê",
"sensitivity": "Hesasiyet",
"title": "Mîhengên Dîmenderê",
"useCustomColors": "Rengên Xweser Bikar Bîne",
"useCustomColors-description": "Rengên mijarê bi rengên xwerû biguherîne",
"visualizationMode": "Şêwaza Dîmenkirinê",
"visualizationMode-description": "Şêwaza dîtinê hilbijêre",
"waveThickness": "Qalinîya Pêlê"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Balkbreedte",
"bloomIntensity": "Bloei Intensiteit",
"customPrimaryColor": "Primaire kleur",
"customSecondaryColor": "Secundaire kleur",
"description": "Configureer de weergave van de audio visualisatie.",
"fadeWhenIdle": "Vervagen bij inactiviteit",
"fadeWhenIdle-description": "Visualiseerder laten vervagen wanneer er geen audio wordt afgespeeld.",
"innerDiameter": "Binnendiameter",
"mode": {
"all": "Alles",
"bars": "Bars",
"barsRings": "Rekstok + Ringen",
"rings": "Ringen",
"wave": "Golf",
"waveRings": "Golf + Ringen"
},
"ringOpacity": "Ringondoorzichtigheid",
"rotationSpeed": "Rotatiesnelheid",
"sensitivity": "Gevoeligheid",
"title": "Visualisatie-instellingen",
"useCustomColors": "Aangepaste kleuren gebruiken",
"useCustomColors-description": "Themakleuren overschrijven met aangepaste kleuren",
"visualizationMode": "Visualisatiemodus",
"visualizationMode-description": "Kies visualisatiestijl",
"waveThickness": "Golflaagdikte"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Szerokość paska",
"bloomIntensity": "Intensywność Rozkwitu",
"customPrimaryColor": "Kolor podstawowy",
"customSecondaryColor": "Kolor dodatkowy",
"description": "Skonfiguruj wygląd wizualizatora dźwięku",
"fadeWhenIdle": "Przyciemnij przy bezczynności",
"fadeWhenIdle-description": "Wycisz wizualizator, gdy nie jest odtwarzany dźwięk",
"innerDiameter": "Średnica wewnętrzna",
"mode": {
"all": "Wszystkie",
"bars": "Paski",
"barsRings": "Paski + Pierścienie",
"rings": "Pierścienie",
"wave": "Fala",
"waveRings": "Fala + Pierścienie"
},
"ringOpacity": "Krycie pierścienia",
"rotationSpeed": "Prędkość obrotu",
"sensitivity": "Czułość",
"title": "Ustawienia wizualizatora",
"useCustomColors": "Użyj własnych kolorów",
"useCustomColors-description": "Zastąp kolory motywu własnymi kolorami",
"visualizationMode": "Tryb wizualizacji",
"visualizationMode-description": "Wybierz styl wizualizacji",
"waveThickness": "Grubość fali"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Largura da Barra",
"bloomIntensity": "Intensidade do Bloom",
"customPrimaryColor": "Cor primária",
"customSecondaryColor": "Cor secundária",
"description": "Configurar a aparência do visualizador de áudio",
"fadeWhenIdle": "Desaparecer quando inativo",
"fadeWhenIdle-description": "Desvanecer o visualizador quando nenhum áudio estiver sendo reproduzido.",
"innerDiameter": "Diâmetro interno",
"mode": {
"all": "Tudo",
"bars": "Barras",
"barsRings": "Barras + Argolas",
"rings": "Anéis",
"wave": "Onda",
"waveRings": "Onda + Anéis"
},
"ringOpacity": "Opacidade do Anel",
"rotationSpeed": "Velocidade de rotação",
"sensitivity": "Sensibilidade",
"title": "Configurações do Visualizador",
"useCustomColors": "Usar Cores Personalizadas",
"useCustomColors-description": "Substituir as cores do tema por cores personalizadas",
"visualizationMode": "Modo de Visualização",
"visualizationMode-description": "Escolha o estilo de visualização",
"waveThickness": "Espessura da onda"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Ширина полосы",
"bloomIntensity": "Интенсивность свечения",
"customPrimaryColor": "Основной цвет",
"customSecondaryColor": "Вторичный цвет",
"description": "Настроить внешний вид визуализатора звука",
"fadeWhenIdle": "Затухать в режиме ожидания",
"fadeWhenIdle-description": "Затухать визуализатору при отсутствии воспроизведения аудио.",
"innerDiameter": "Внутренний диаметр",
"mode": {
"all": "Всё",
"bars": "Бары",
"barsRings": "Турники + Кольца",
"rings": "Кольца",
"wave": "Волна",
"waveRings": "Волна + Кольца"
},
"ringOpacity": "Прозрачность кольца",
"rotationSpeed": "Скорость вращения",
"sensitivity": "Чувствительность",
"title": "Настройки визуализатора",
"useCustomColors": "Использовать пользовательские цвета",
"useCustomColors-description": "Переопределить цвета темы пользовательскими цветами",
"visualizationMode": "Режим визуализации",
"visualizationMode-description": "Выберите стиль визуализации",
"waveThickness": "Толщина волны"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Çubuk Genişliği",
"bloomIntensity": "Çiçeklenme Yoğunluğu",
"customPrimaryColor": "Ana Renk",
"customSecondaryColor": "İkincil Renk",
"description": "Ses görselleştirici görünümünü yapılandır",
"fadeWhenIdle": "Boşta Kalınca Karart",
"fadeWhenIdle-description": "Ses çalmadığında görselleştiriciyi soldur.",
"innerDiameter": "İç Çap",
"mode": {
"all": "Tüm",
"bars": "Barlar",
"barsRings": "Barfiks Demiri + Halkalar",
"rings": "Yüzükler",
"wave": "Dalga",
"waveRings": "Dalga + Halkalar"
},
"ringOpacity": "Halka Opaklığı",
"rotationSpeed": "Dönme Hızı",
"sensitivity": "Hassasiyet",
"title": "Görselleştirici Ayarları",
"useCustomColors": "Özel Renkleri Kullan",
"useCustomColors-description": "Tema renklerini özel renklerle geçersiz kıl",
"visualizationMode": "Görselleştirme Modu",
"visualizationMode-description": "Görselleştirme stilini seçin",
"waveThickness": "Dalga Kalınlığı"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Ширина бруска",
"bloomIntensity": "Інтенсивність світіння",
"customPrimaryColor": "Основний колір",
"customSecondaryColor": "Вторинний колір",
"description": "Налаштуйте вигляд аудіовізуалізатора",
"fadeWhenIdle": "Згасати в режимі очікування",
"fadeWhenIdle-description": "Затемнювати візуалізатор, коли не відтворюється звук.",
"innerDiameter": "Внутрішній діаметр",
"mode": {
"all": "Все",
"bars": "Бари",
"barsRings": "Бруси + Кільця",
"rings": "Кільця",
"wave": "Хвиля",
"waveRings": "Хвиля + Кільця"
},
"ringOpacity": "Прозорість кільця",
"rotationSpeed": "Швидкість обертання",
"sensitivity": "Чутливість",
"title": "Налаштування візуалізатора",
"useCustomColors": "Використовувати власні кольори",
"useCustomColors-description": "Перевизначити кольори теми власними кольорами",
"visualizationMode": "Режим візуалізації",
"visualizationMode-description": "Виберіть стиль візуалізації",
"waveThickness": "Товщина хвилі"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "Độ rộng thanh",
"bloomIntensity": "Cường độ hiệu ứng bloom",
"customPrimaryColor": "Màu chính tùy chỉnh",
"customSecondaryColor": "Màu phụ tùy chỉnh",
"description": "Cấu hình giao diện trình hiển thị âm thanh",
"fadeWhenIdle": "Làm mờ khi không hoạt động",
"fadeWhenIdle-description": "Làm mờ trình hiển thị khi không có âm thanh",
"innerDiameter": "Đường kính bên trong",
"mode": {
"all": "Tất cả",
"bars": "Thanh",
"barsRings": "Thanh + Vòng",
"rings": "Vòng",
"wave": "Sóng",
"waveRings": "Sóng + Vòng"
},
"ringOpacity": "Độ mờ vòng",
"rotationSpeed": "Tốc độ xoay",
"sensitivity": "Độ nhạy",
"title": "Cài đặt trình hiển thị",
"useCustomColors": "Dùng màu tùy chỉnh",
"useCustomColors-description": "Ghi đè màu giao diện bằng màu tùy chỉnh",
"visualizationMode": "Chế độ hiển thị",
"visualizationMode-description": "Chọn kiểu hiển thị",
"waveThickness": "Độ dày sóng"
}
}
@@ -0,0 +1,29 @@
{
"settings": {
"barWidth": "条宽",
"bloomIntensity": "光晕强度",
"customPrimaryColor": "原色",
"customSecondaryColor": "间色",
"description": "配置音频可视化效果外观",
"fadeWhenIdle": "空闲时淡出",
"fadeWhenIdle-description": "当没有音频播放时淡出可视化工具",
"innerDiameter": "内径",
"mode": {
"all": "全部",
"bars": "酒吧",
"barsRings": "单杠 + 吊环",
"rings": "戒指",
"wave": "波浪",
"waveRings": "波浪 + 环"
},
"ringOpacity": "环透明度",
"rotationSpeed": "转速",
"sensitivity": "敏感性",
"title": "可视化设置",
"useCustomColors": "使用自定义颜色",
"useCustomColors-description": "使用自定义颜色覆盖主题颜色",
"visualizationMode": "可视化模式",
"visualizationMode-description": "选择可视化风格",
"waveThickness": "波浪厚度"
}
}
@@ -0,0 +1,38 @@
{
"id": "fancy-audiovisualizer",
"name": "Fancy Audiovisualizer",
"version": "1.1.2",
"minNoctaliaVersion": "4.6.6",
"author": "Noctalia Team",
"official": true,
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Lemmy's fancy circular audio visualizer.",
"tags": [
"Desktop",
"Audio"
],
"entryPoints": {
"desktopWidget": "DesktopWidget.qml",
"desktopWidgetSettings": "DesktopWidgetSettings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"sensitivity": 1.5,
"rotationSpeed": 0.5,
"barWidth": 0.6,
"ringOpacity": 0.8,
"bloomIntensity": 0.5,
"visualizationMode": 3,
"waveThickness": 1.0,
"innerDiameter": 0.7,
"fadeWhenIdle": false,
"useCustomColors": false,
"customPrimaryColor": "#6750A4",
"customSecondaryColor": "#625B71"
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 575 KiB

@@ -0,0 +1,559 @@
#version 450
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
float time;
float itemWidth;
float itemHeight;
vec4 primaryColor;
vec4 secondaryColor;
float sensitivity;
float rotationSpeed;
float barWidth;
float ringOpacity;
float cornerRadius;
float bloomIntensity;
float visualizationMode; // 0=bars, 1=wave, 2=rings, 3=bars+rings, 4=wave+rings, 5=all
float waveThickness;
float innerDiameter;
} ubuf;
// Mode helper functions
bool hasRings() { return ubuf.visualizationMode >= 2.0; }
bool hasBars() { return ubuf.visualizationMode == 0.0 || ubuf.visualizationMode == 3.0 || ubuf.visualizationMode >= 5.0; }
bool hasWave() { return ubuf.visualizationMode == 1.0 || ubuf.visualizationMode == 4.0 || ubuf.visualizationMode >= 5.0; }
layout(binding = 1) uniform sampler2D source;
#define TWOPI 6.28318530718
#define PI 3.14159265359
#define NBARS 32
// Sample audio amplitude at normalized position (0.0-1.0)
float getAudio(float pos) {
return texture(source, vec2(clamp(pos, 0.0, 1.0), 0.5)).r;
}
// Smoothed audio sampling with interpolation
float smoothAudio(float pos) {
float idx = pos * float(NBARS - 1);
float frac = fract(idx);
float i0 = floor(idx) / float(NBARS - 1);
float i1 = ceil(idx) / float(NBARS - 1);
return mix(getAudio(i0), getAudio(i1), frac) * ubuf.sensitivity;
}
// Frequency band helpers
float getBass() { return smoothAudio(0.05); }
float getMid() { return smoothAudio(0.3); }
float getHighMid() { return smoothAudio(0.6); }
float getTreble() { return smoothAudio(0.9); }
// SDF for rounded rectangle
float roundedBoxSDF(vec2 center, vec2 size, float radius) {
vec2 q = abs(center) - size + radius;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}
// Compute polar wave visualization
vec4 computePolarWave(vec2 uv, float iTime, float bass, float mid, float highMid, float treble) {
float aspect = ubuf.itemWidth / ubuf.itemHeight;
vec2 centered = (uv - 0.5) * 2.0;
centered.x *= aspect;
float theta = atan(centered.y, centered.x);
float d = length(centered);
float innerRadius = ubuf.innerDiameter / 2.0;
float baseRadius = 0.35; // Fixed reference for outer extent
vec4 color = vec4(0.0);
// RING SYSTEM
if (hasRings()) {
// Center Waves
if (d < innerRadius * 0.6) {
float wave = mid * 0.8;
float ripple = sin(d * 25.0 + wave * 15.0 - iTime * 2.0);
if (ripple > 0.7) {
float intensity = clamp(mid * 0.6, 0.0, 0.4);
vec4 waveColor = ubuf.secondaryColor * intensity * ubuf.ringOpacity;
color = max(color, waveColor);
}
}
// Energy Ring
float energyRad = innerRadius * 0.65;
float energyThickness = 0.015 + clamp(highMid * 0.02, 0.0, 0.03);
if (d > energyRad - energyThickness && d < energyRad + energyThickness) {
float segmentAngle = theta * 8.0 + highMid * 3.0 + iTime;
if (mod(segmentAngle, 1.0) < 0.6) {
float alpha = clamp(highMid * 2.0, 0.3, 1.0) * ubuf.ringOpacity;
vec4 energyColor = mix(ubuf.primaryColor, ubuf.secondaryColor, 0.5) * alpha;
color = max(color, energyColor);
}
}
// Particle Ring
float particleRad = innerRadius * 0.75;
if (d > particleRad - 0.02 && d < particleRad + 0.02) {
float particleAngle = theta + treble * 2.0 + iTime * 0.5;
float particleSpacing = TWOPI / 16.0;
if (mod(particleAngle, particleSpacing) < 0.15) {
float brightness = 0.5 + clamp(treble * 1.5, 0.0, 0.5);
vec4 particleColor = ubuf.secondaryColor * brightness * ubuf.ringOpacity;
color = max(color, particleColor);
}
}
// Tech Grid Ring
float gridRad = innerRadius * 0.85;
if (d > gridRad - 0.012 && d < gridRad + 0.012) {
float gridAngle = theta + iTime * ubuf.rotationSpeed;
float gridDensity = 0.08 + clamp(mid * 0.05, 0.0, 0.1);
if (mod(gridAngle, 0.2) < gridDensity) {
vec4 gridColor = ubuf.primaryColor * 0.7 * ubuf.ringOpacity;
gridColor.rgb += vec3(0.1, 0.15, 0.2) * clamp(mid, 0.0, 0.8);
color = max(color, gridColor);
}
}
// Accent Ring
float accentRad = innerRadius * 0.92;
float pulse = clamp(bass * 0.08, 0.0, 0.05);
if (d > accentRad - pulse - 0.008 && d < accentRad + pulse + 0.015) {
float colorShift = clamp(bass * 0.5, 0.0, 1.0);
vec4 ringColor = mix(ubuf.secondaryColor * 0.7, ubuf.primaryColor, colorShift);
ringColor.a = ubuf.ringOpacity;
ringColor.rgb *= 1.0 + bass * 0.3;
color = max(color, ringColor);
}
// Outer Ring
float outerRad = innerRadius + bass * 0.05;
if (d > outerRad - 0.008 && d < outerRad + 0.008) {
vec4 outerColor = ubuf.primaryColor * ubuf.ringOpacity;
outerColor.rgb += vec3(0.2, 0.3, 0.4) * clamp(treble * 0.5, 0.0, 0.3);
outerColor.rgb *= 1.0 + bass * 0.4;
color = max(color, outerColor);
}
}
// POLAR WAVE - filled shape with mirrored bands (64 visual bands from 32 samples)
if (hasWave()) {
float adjustedTheta = theta + PI + iTime * ubuf.rotationSpeed * 0.2;
// Map angle to 0-1 range around the full circle
float normalizedAngle = mod(adjustedTheta, TWOPI) / TWOPI;
// Mirror: first half (0-0.5) maps to bands 0->31, second half (0.5-1) maps back 31->0
float mirroredPos = normalizedAngle < 0.5 ? normalizedAngle * 2.0 : (1.0 - normalizedAngle) * 2.0;
// Catmull-Rom spline interpolation for smooth curve through mirrored bands
float bandPos = mirroredPos * float(NBARS - 1);
float fband1 = floor(bandPos);
float fband0 = max(fband1 - 1.0, 0.0);
float fband2 = min(fband1 + 1.0, float(NBARS - 1));
float fband3 = min(fband1 + 2.0, float(NBARS - 1));
float t = fract(bandPos);
// Sample the 4 control points
float p0 = getAudio(fband0 / float(NBARS - 1)) * ubuf.sensitivity;
float p1 = getAudio(fband1 / float(NBARS - 1)) * ubuf.sensitivity;
float p2 = getAudio(fband2 / float(NBARS - 1)) * ubuf.sensitivity;
float p3 = getAudio(fband3 / float(NBARS - 1)) * ubuf.sensitivity;
// Catmull-Rom spline interpolation
float t2 = t * t;
float t3 = t2 * t;
float smoothedAudio = 0.5 * (
(2.0 * p1) +
(-p0 + p2) * t +
(2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3
);
smoothedAudio = max(smoothedAudio, 0.0);
// Calculate wave radius at this angle
float waveDisplacement = smoothedAudio * 0.5;
float waveRadius = baseRadius + waveDisplacement; // Fixed outer extent
// Fill the entire area from inner to wave edge
if (d >= innerRadius && d <= waveRadius) {
float fillFactor = (d - innerRadius) / max(waveRadius - innerRadius, 0.001);
// Gradient from primary at base to accent at edge
vec3 fillColor = mix(ubuf.primaryColor.rgb * 0.8, ubuf.secondaryColor.rgb, fillFactor);
// Boost brightness with bass
fillColor *= 1.0 + bass * 0.3;
// Alpha: stronger near the edge, fades toward center
float fillAlpha = mix(0.4, 1.0, fillFactor) * ubuf.waveThickness;
fillAlpha = clamp(fillAlpha, 0.0, 1.0);
vec4 fill = vec4(fillColor, fillAlpha);
color = max(color, fill);
}
// Bright edge line at the wave boundary
float edgeThickness = ubuf.waveThickness * 0.025;
float distToEdge = abs(d - waveRadius);
if (distToEdge < edgeThickness) {
float edgeFactor = 1.0 - smoothstep(0.0, edgeThickness, distToEdge);
vec3 edgeColor = ubuf.secondaryColor.rgb * (1.2 + smoothedAudio * 0.5);
// Add highlight at peaks
if (smoothedAudio > 0.5) {
edgeColor += vec3(0.3, 0.4, 0.5) * (smoothedAudio - 0.5);
}
vec4 edge = vec4(edgeColor, edgeFactor);
color = max(color, edge);
}
}
return color;
}
// Compute visualization color at given UV coordinates
// Returns vec4 with RGB color and alpha
vec4 computeVisualization(vec2 uv, float iTime, float bass, float mid, float highMid, float treble) {
float aspect = ubuf.itemWidth / ubuf.itemHeight;
vec2 centered = (uv - 0.5) * 2.0;
centered.x *= aspect;
float theta = atan(centered.y, centered.x);
float d = length(centered);
float innerRadius = ubuf.innerDiameter / 2.0;
float baseRadius = 0.35; // Fixed reference for outer extent
vec4 color = vec4(0.0);
// RING SYSTEM
if (hasRings()) {
// Center Waves
if (d < innerRadius * 0.6) {
float wave = mid * 0.8;
float ripple = sin(d * 25.0 + wave * 15.0 - iTime * 2.0);
if (ripple > 0.7) {
float intensity = clamp(mid * 0.6, 0.0, 0.4);
vec4 waveColor = ubuf.secondaryColor * intensity * ubuf.ringOpacity;
color = max(color, waveColor);
}
}
// Energy Ring
float energyRad = innerRadius * 0.65;
float energyThickness = 0.015 + clamp(highMid * 0.02, 0.0, 0.03);
if (d > energyRad - energyThickness && d < energyRad + energyThickness) {
float segmentAngle = theta * 8.0 + highMid * 3.0 + iTime;
if (mod(segmentAngle, 1.0) < 0.6) {
float alpha = clamp(highMid * 2.0, 0.3, 1.0) * ubuf.ringOpacity;
vec4 energyColor = mix(ubuf.primaryColor, ubuf.secondaryColor, 0.5) * alpha;
color = max(color, energyColor);
}
}
// Particle Ring
float particleRad = innerRadius * 0.75;
if (d > particleRad - 0.02 && d < particleRad + 0.02) {
float particleAngle = theta + treble * 2.0 + iTime * 0.5;
float particleSpacing = TWOPI / 16.0;
if (mod(particleAngle, particleSpacing) < 0.15) {
float brightness = 0.5 + clamp(treble * 1.5, 0.0, 0.5);
vec4 particleColor = ubuf.secondaryColor * brightness * ubuf.ringOpacity;
color = max(color, particleColor);
}
}
// Tech Grid Ring
float gridRad = innerRadius * 0.85;
if (d > gridRad - 0.012 && d < gridRad + 0.012) {
float gridAngle = theta + iTime * ubuf.rotationSpeed;
float gridDensity = 0.08 + clamp(mid * 0.05, 0.0, 0.1);
if (mod(gridAngle, 0.2) < gridDensity) {
vec4 gridColor = ubuf.primaryColor * 0.7 * ubuf.ringOpacity;
gridColor.rgb += vec3(0.1, 0.15, 0.2) * clamp(mid, 0.0, 0.8);
color = max(color, gridColor);
}
}
// Accent Ring
float accentRad = innerRadius * 0.92;
float pulse = clamp(bass * 0.08, 0.0, 0.05);
if (d > accentRad - pulse - 0.008 && d < accentRad + pulse + 0.015) {
float colorShift = clamp(bass * 0.5, 0.0, 1.0);
vec4 ringColor = mix(ubuf.secondaryColor * 0.7, ubuf.primaryColor, colorShift);
ringColor.a = ubuf.ringOpacity;
ringColor.rgb *= 1.0 + bass * 0.3;
color = max(color, ringColor);
}
// Outer Ring
float outerRad = innerRadius + bass * 0.05;
if (d > outerRad - 0.008 && d < outerRad + 0.008) {
vec4 outerColor = ubuf.primaryColor * ubuf.ringOpacity;
outerColor.rgb += vec3(0.2, 0.3, 0.4) * clamp(treble * 0.5, 0.0, 0.3);
outerColor.rgb *= 1.0 + bass * 0.4;
color = max(color, outerColor);
}
}
// CIRCULAR AUDIO BARS (64 bars, mirrored from 32 audio samples)
if (hasBars() && d > innerRadius) {
// Double the visual bars by using NBARS * 2
float section = TWOPI / float(NBARS * 2);
float center = section / 2.0;
float adjustedTheta = theta + PI + iTime * ubuf.rotationSpeed * 0.2;
float m = mod(adjustedTheta, section);
float ym = d * sin(center - m);
float barW = ubuf.barWidth * 0.015;
if (abs(ym) < barW) {
// Calculate position in the circle (0.0 to 1.0)
float circlePos = mod(adjustedTheta, TWOPI) / TWOPI;
// Mirror: first half (0-0.5) maps to 0-1, second half (0.5-1) maps back 1-0
float mirroredPos = circlePos < 0.5 ? circlePos * 2.0 : (1.0 - circlePos) * 2.0;
float v = smoothAudio(mirroredPos);
float wave = sin(theta * 3.0 + mid * 5.0) * clamp(mid * 0.03, 0.0, 0.05);
v += wave;
v = max(v, 0.0);
float barStart = innerRadius;
float barEnd = baseRadius + v * 0.5; // Fixed outer extent
if (d >= barStart && d <= barEnd) {
float heightFactor = (d - barStart) / max(barEnd - barStart, 0.001);
vec3 bottomColor = ubuf.primaryColor.rgb * 0.6;
vec3 middleColor = ubuf.primaryColor.rgb;
vec3 topColor = ubuf.secondaryColor.rgb;
vec3 barColor;
if (heightFactor < 0.5) {
barColor = mix(bottomColor, middleColor, heightFactor * 2.0);
} else {
barColor = mix(middleColor, topColor, (heightFactor - 0.5) * 2.0);
}
barColor *= 1.0 + bass * 0.4;
if (heightFactor > 0.85) {
barColor += vec3(0.3, 0.4, 0.5) * clamp(treble * 0.8, 0.0, 0.5);
}
float edgeFactor = 1.0 - smoothstep(barW * 0.7, barW, abs(ym));
vec4 finalBarColor = vec4(barColor, edgeFactor);
color = max(color, finalBarColor);
}
}
}
return color;
}
void main() {
// ============================================
// DYNAMIC CONTENT SCALING
// ============================================
// Calculate max possible extent from current settings to guarantee
// nothing ever reaches the widget border.
//
// Max visualization radius in centered space [-1,1]:
// Bars/Wave: baseRadius(0.35) + sensitivity * 0.5
// Rings: innerDiameter/2 + sensitivity * 0.05 (always smaller)
//
// Bloom reach: exp(-dist * minDecayRate / bloomIntensity) decays to
// < 1/255 at dist ≈ bloomIntensity * 1.0 (from solving exp decay
// with worst-case amplitude * multiplier chain)
//
// contentScale maps the widget edge to ±contentScale in centered space,
// so setting it to maxTotalRadius ensures everything fits.
float maxContentRadius = 0.35 + ubuf.sensitivity * 0.5;
float maxBloomReach = ubuf.bloomIntensity * 1.0;
float maxTotalRadius = maxContentRadius + maxBloomReach;
float contentScale = max(maxTotalRadius * 1.05, 1.0); // 5% safety margin
vec2 uv = (qt_TexCoord0 - 0.5) * contentScale + 0.5;
// Convert linear time (0-3600) to smooth oscillation for seamless looping
// sin() ensures perfect continuity when QML wraps from 3600 back to 0
float iTime = sin(ubuf.time * TWOPI / 3600.0) * 1800.0 + 1800.0;
// Frequency analysis
float bass = getBass();
float mid = getMid();
float highMid = getHighMid();
float treble = getTreble();
// Get base visualization color based on mode
// Mode 0: bars only, Mode 1: wave only, Mode 2: rings only
// Mode 3: bars+rings, Mode 4: wave+rings, Mode 5: all
vec4 color;
if (hasWave() && !hasBars()) {
// Wave only or wave+rings (modes 1, 4)
color = computePolarWave(uv, iTime, bass, mid, highMid, treble);
} else if (hasBars() && !hasWave()) {
// Bars only or bars+rings (modes 0, 3)
color = computeVisualization(uv, iTime, bass, mid, highMid, treble);
} else if (hasWave() && hasBars()) {
// All mode (5) - combine both
vec4 barsColor = computeVisualization(uv, iTime, bass, mid, highMid, treble);
vec4 waveColor = computePolarWave(uv, iTime, bass, mid, highMid, treble);
color = max(barsColor, waveColor);
} else {
// Rings only (mode 2) - still need to call one of them for ring rendering
color = computeVisualization(uv, iTime, bass, mid, highMid, treble);
}
// ============================================
// BLOOM EFFECT - Glow based on distance to geometry
// ============================================
if (ubuf.bloomIntensity > 0.01 && color.a < 0.01) {
// Only apply bloom where there's no geometry (empty space)
// Find distance to nearest bright element
float aspect = ubuf.itemWidth / ubuf.itemHeight;
vec2 centered = (uv - 0.5) * 2.0;
centered.x *= aspect;
float d = length(centered);
float theta = atan(centered.y, centered.x);
float innerRadius = ubuf.innerDiameter / 2.0;
float baseRadius = 0.35; // Fixed reference for outer extent
float glowAmount = 0.0;
vec3 glowColor = vec3(0.0);
// Glow from rings (if enabled)
if (hasRings()) {
// Outer ring glow
float outerRad = innerRadius + bass * 0.05;
float ringDist = abs(d - outerRad);
float ringGlow = exp(-ringDist * 8.0 / ubuf.bloomIntensity) * (1.0 + bass * 0.5);
glowColor += ubuf.primaryColor.rgb * ringGlow;
glowAmount = max(glowAmount, ringGlow);
// Accent ring glow
float accentRad = innerRadius * 0.92;
float accentDist = abs(d - accentRad);
float accentGlow = exp(-accentDist * 10.0 / ubuf.bloomIntensity) * (0.7 + bass * 0.3);
glowColor += mix(ubuf.secondaryColor.rgb, ubuf.primaryColor.rgb, 0.5) * accentGlow;
glowAmount = max(glowAmount, accentGlow);
}
// Glow from visualization (bars or polar wave)
if ((hasBars() || hasWave()) && d > innerRadius * 0.8) {
float adjustedTheta = theta + PI + iTime * ubuf.rotationSpeed * 0.2;
float circlePos = mod(adjustedTheta, TWOPI) / TWOPI;
float mirroredPos = circlePos < 0.5 ? circlePos * 2.0 : (1.0 - circlePos) * 2.0;
float v = smoothAudio(mirroredPos);
if (hasWave()) {
// Polar wave bloom - Catmull-Rom spline with mirroring matching main render
float mirroredPos = circlePos < 0.5 ? circlePos * 2.0 : (1.0 - circlePos) * 2.0;
float bandPos = mirroredPos * float(NBARS - 1);
float fband1 = floor(bandPos);
float fband0 = max(fband1 - 1.0, 0.0);
float fband2 = min(fband1 + 1.0, float(NBARS - 1));
float fband3 = min(fband1 + 2.0, float(NBARS - 1));
float t = fract(bandPos);
float p0 = getAudio(fband0 / float(NBARS - 1)) * ubuf.sensitivity;
float p1 = getAudio(fband1 / float(NBARS - 1)) * ubuf.sensitivity;
float p2 = getAudio(fband2 / float(NBARS - 1)) * ubuf.sensitivity;
float p3 = getAudio(fband3 / float(NBARS - 1)) * ubuf.sensitivity;
float t2 = t * t;
float t3 = t2 * t;
float smoothedAudio = 0.5 * (
(2.0 * p1) +
(-p0 + p2) * t +
(2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3
);
smoothedAudio = max(smoothedAudio, 0.0);
float waveRadius = baseRadius + smoothedAudio * 0.5;
// Glow from the filled area and edge
float distToWave = abs(d - waveRadius);
float waveGlow = exp(-distToWave * 8.0 / ubuf.bloomIntensity) * smoothedAudio * 2.5;
vec3 waveGlowColor = mix(ubuf.primaryColor.rgb, ubuf.secondaryColor.rgb, smoothedAudio);
glowColor += waveGlowColor * waveGlow;
glowAmount = max(glowAmount, waveGlow);
}
if (hasBars()) {
// Bars bloom
float section = TWOPI / float(NBARS * 2);
float m = mod(adjustedTheta, section);
float center = section / 2.0;
float barAngleDist = min(abs(m - center), section - abs(m - center));
float barEnd = baseRadius + v * 0.5; // Fixed outer extent
float radialDist = 0.0;
if (d < innerRadius) {
radialDist = innerRadius - d;
} else if (d > barEnd) {
radialDist = d - barEnd;
}
float totalDist = length(vec2(barAngleDist * d, radialDist));
float barGlow = exp(-totalDist * 15.0 / ubuf.bloomIntensity) * v * 2.0;
float heightFactor = clamp((d - innerRadius) / max(barEnd - innerRadius, 0.001), 0.0, 1.0);
vec3 barGlowColor = mix(ubuf.primaryColor.rgb, ubuf.secondaryColor.rgb, heightFactor);
glowColor += barGlowColor * barGlow;
glowAmount = max(glowAmount, barGlow);
}
}
// Apply bloom
float bloomMult = ubuf.bloomIntensity * (1.0 + bass * 0.5);
color.rgb = glowColor * bloomMult;
color.a = glowAmount * bloomMult * 0.6;
// Clamp to reasonable values
color.rgb = min(color.rgb, vec3(1.5));
color.a = min(color.a, 0.8);
}
// ============================================
// EDGE FADE - radial falloff that only affects the bloom zone
// ============================================
// Fade starts just past where main content ends (maxContentRadius)
// and reaches zero at the widget edge (contentScale) in centered space.
// This catches bloom tails without affecting the main visualization.
vec2 fromCenter = (qt_TexCoord0 - 0.5) * 2.0; // -1 to 1 in widget space
float edgeProximity = max(abs(fromCenter.x), abs(fromCenter.y)); // box distance
float fadeStart = maxContentRadius / contentScale; // where content ends in widget space
float edgeFade = 1.0 - smoothstep(fadeStart, 1.0, edgeProximity);
color *= edgeFade;
// ============================================
// CORNER MASKING
// ============================================
vec2 pixelPos = qt_TexCoord0 * vec2(ubuf.itemWidth, ubuf.itemHeight);
vec2 centerPos = pixelPos - vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
vec2 halfSize = vec2(ubuf.itemWidth, ubuf.itemHeight) * 0.5;
float dist = roundedBoxSDF(centerPos, halfSize, ubuf.cornerRadius);
float cornerMask = 1.0 - smoothstep(-1.0, 0.0, dist);
// Final output with premultiplied alpha
float finalAlpha = color.a * ubuf.qt_Opacity * cornerMask;
fragColor = vec4(color.rgb * finalAlpha, finalAlpha);
}
@@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Modules.Bar.Extras
import qs.Services.UI
import qs.Widgets
Item {
id: root
property var pluginApi: null
property ShellScreen screen
property string widgetId: ""
property string section: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property var main: pluginApi?.mainInstance ?? ({})
readonly property string screenName: screen?.name ?? ""
readonly property string barPosition: Settings.getBarPositionForScreen(screenName)
readonly property bool isBarVertical: barPosition === "left" || barPosition === "right"
readonly property real capsuleHeight: Style.getCapsuleHeightForScreen(screenName)
readonly property real barFontSize: Style.getBarFontSizeForScreen(screenName)
readonly property string displayMode: root.pluginSettings.displayMode ?? "onhover"
readonly property string connectedColor: root.pluginSettings.connectedColor
readonly property string disconnectedColor: root.pluginSettings.disconnectedColor
readonly property var vpnList: root.main.vpnList ?? []
readonly property real connectedCount: root.main.connectedCount ?? 0
readonly property bool isLoading: root.main.isLoading ?? false
implicitWidth: pill.width
implicitHeight: pill.height
Component.onCompleted: {
Logger.i("NetworkManagerVPN", "Bar widget loaded");
}
NPopupContextMenu {
id: contextMenu
model: [{
"label": pluginApi?.tr("settings.pluginSettings"),
"action": "plugin-settings",
"icon": "settings"
}]
onTriggered: (action) => {
contextMenu.close();
PanelService.closeContextMenu(screen);
if (action === "plugin-settings")
BarService.openPluginSettings(screen, pluginApi.manifest);
}
}
BarPill {
id: pill
screen: root.screen
oppositeDirection: BarService.getPillDirection(root)
autoHide: false
text: root.connectedCount > 0 ? pluginApi?.tr("common.connected") : pluginApi?.tr("common.disconnected")
icon: root.isLoading ? "reload" : root.connectedCount > 0 ? "shield-lock" : "shield"
onClicked: {
if (pluginApi)
pluginApi.openPanel(root.screen, root);
}
onRightClicked: {
if (pluginApi)
pluginApi.closePanel(root.screen);
PanelService.showContextMenu(contextMenu, pill, screen);
}
customIconColor: Color.resolveColorKeyOptional(root.connectedCount > 0 ? root.connectedColor : root.disconnectedColor)
customTextColor: Color.resolveColorKeyOptional(root.connectedCount > 0 ? root.connectedColor : root.disconnectedColor)
forceOpen: root.displayMode === "alwaysShow"
forceClose: root.displayMode === "alwaysHide"
}
}
@@ -0,0 +1,29 @@
import QtQuick
import Quickshell
import qs.Widgets
import qs.Commons
NIconButtonHot {
property ShellScreen screen
property var pluginApi: null
readonly property var main: pluginApi?.mainInstance ?? ({})
readonly property real connectedCount: main.connectedCount ?? 0
readonly property bool isLoading: main.isLoading ?? false
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property string connectedColor: pluginSettings.connectedColor
readonly property string disconnectedColor: pluginSettings.disconnectedColor
icon: isLoading ? "reload" : connectedCount > 0 ? "shield-lock" : "shield"
tooltipText: connectedCount > 0
? pluginApi?.tr("common.connected")
: pluginApi?.tr("common.disconnected")
colorFg: {
const key = connectedCount > 0 ? connectedColor : disconnectedColor;
if (!key || key === "none") return Color.mPrimary;
return Color.resolveColorKeyOptional(key) ?? Color.mPrimary;
}
onClicked: pluginApi?.togglePanel(screen, this)
}
@@ -0,0 +1,231 @@
import QtQuick
import Quickshell.Io
import qs.Commons
import qs.Services.UI
QtObject {
id: root
readonly property var pluginSettings: pluginApi?.pluginSettings ?? ({})
readonly property var toast: root.pluginSettings?.disableToastNotifications ? null : ToastService
property var pluginApi: null
property var vpnList: []
property real connectedCount: 0
readonly property bool isLoading: Object.keys(root._pending).length > 0
property var _pending: ({})
// Needed only to detect disconnection not initiated by the user
property var _pollTimer: Timer {
interval: 5000
running: true
repeat: true
onTriggered: root.refresh()
}
property var _lines: []
property var _listProc: Process {
command: ["nmcli", "-t", "-f", "NAME,TYPE,STATE,UUID", "connection", "show"]
running: true
stdout: SplitParser {
onRead: (line) => {
if (line.trim() !== "")
root._lines.push(line)
}
}
onExited: (exitCode) => {
if (exitCode === 0) {
const parsed = []
const newPending = Object.assign({}, root._pending)
for (const line of root._lines) {
const parts = line.split(":")
if (parts.length >= 4) {
const name = parts[0]
const type = parts[1]
const state = parts[2]
const uuid = parts[3]
if (type === "vpn" || type === "wireguard") {
if (newPending[uuid]) {
const wasConnecting = newPending[uuid] === "connect"
if (wasConnecting && state === "activated")
delete newPending[uuid]
else if (!wasConnecting && state !== "activated")
delete newPending[uuid]
}
parsed.push({
name,
type,
connected: state === "activated",
isLoading: !!newPending[uuid],
uuid
})
}
}
}
root._pending = newPending
root.vpnList = parsed
root.connectedCount = parsed.filter(v => v.connected).length
}
root._lines = []
}
}
property var _connectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "up", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.connectedTo", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.connectionError", { name: targetName }))
}
root.refresh()
}
}
property var _disconnectProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "down", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.disconnectedFrom", { name: targetName }))
else {
root.stopLoading(targetUuid)
toast?.showError(t("toast.disconnectionError", { name: targetName }))
}
root.refresh()
}
}
property var _addProc: Process {
property string targetType: ""
command: ["nm-connection-editor", "--create", "--type", targetType]
onExited: (exitCode) => {
root.refresh();
}
}
property var _editProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nm-connection-editor", "--edit", targetUuid]
onExited: (exitCode) => {
root.refresh();
}
}
property var _removeProc: Process {
property string targetName: ""
property string targetUuid: ""
command: ["nmcli", "connection", "delete", "uuid", targetUuid]
onExited: (exitCode) => {
if (exitCode === 0)
toast?.showNotice(t("toast.vpnRemoved", { "name": targetName }));
else
toast?.showError(t("toast.vpnRemoveError", { "name": targetName }));
root.refresh();
}
}
function t(key: string, data) {
if (!pluginApi)
return null;
return pluginApi.tr(key, data);
}
function refresh() {
_listProc.running = true
}
function stopLoading(uuid) {
if (uuid && _pending[uuid]) {
const p = Object.assign({}, _pending)
delete p[uuid]
_pending = p
}
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
return Object.assign({}, v, { isLoading: false })
})
}
function connectTo(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "connect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_connectProc.targetName = name
_connectProc.targetUuid = uuid
_connectProc.running = true
}
function disconnectFrom(uuid) {
const p = Object.assign({}, _pending)
p[uuid] = "disconnect"
_pending = p
let name = ""
vpnList = vpnList.map(v => {
if (v.uuid !== uuid) {
return v
}
name = v.name
return Object.assign({}, v, { isLoading: true })
})
_disconnectProc.targetName = name
_disconnectProc.targetUuid = uuid
_disconnectProc.running = true
}
function addConnection(type) {
_addProc.targetType = type || "vpn";
_addProc.running = true;
}
function editConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_editProc.targetName = vpn ? vpn.name : uuid;
_editProc.targetUuid = uuid;
_editProc.running = true;
}
function removeConnection(uuid) {
const vpn = vpnList.find((v) => {
return v.uuid === uuid;
});
_removeProc.targetName = vpn ? vpn.name : uuid;
_removeProc.targetUuid = uuid;
_removeProc.running = true;
}
Component.onCompleted: {
Logger.i("NetworkManagerVPN", "Started")
}
}
@@ -0,0 +1,236 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
Item {
id: root
property var pluginApi: null
property ShellScreen screen
readonly property var geometryPlaceholder: panelContainer
readonly property bool allowAttach: true
readonly property var main: pluginApi?.mainInstance ?? null
readonly property var vpnList: main?.vpnList ?? []
readonly property var activeList: vpnList.filter(v => v.connected || v.isLoading)
readonly property var inactiveList: vpnList.filter(v => !v.connected && !v.isLoading)
property real contentPreferredWidth: Math.round(500 * Style.uiScaleRatio)
property real contentPreferredHeight: Math.min(500, mainColumn.implicitHeight + Style.marginL * 2)
Component.onCompleted: {
if (main) main.refresh()
}
Rectangle {
id: panelContainer
anchors.fill: parent
color: "transparent"
ColumnLayout {
id: mainColumn
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
// HEADER
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(header.implicitHeight + Style.marginM * 2 + 1)
ColumnLayout {
id: header
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
NIcon {
icon: "shield"
pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NLabel {
label: pluginApi?.tr("common.vpn")
}
NBox {
Layout.fillWidth: true
}
NIconButton {
icon: "refresh"
tooltipText: pluginApi?.tr("common.refresh")
baseSize: Style.baseWidgetSize * 0.8
enabled: true
onClicked: {
if (main) {
main.refresh()
}
}
}
NIconButton {
icon: "close"
tooltipText: pluginApi?.tr("common.close")
baseSize: Style.baseWidgetSize * 0.8
onClicked: pluginApi.closePanel(pluginApi.panelOpenScreen)
}
}
}
}
NScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
reserveScrollbarSpace: false
ColumnLayout {
anchors.fill: parent
spacing: Style.marginM
// CONNECTED
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(networksListActive.implicitHeight + Style.marginXL)
visible: activeList.length > 0
ColumnLayout {
id: networksListActive
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS
spacing: Style.marginS
NLabel {
label: pluginApi?.tr("common.connected")
Layout.fillWidth: true
}
}
Repeater {
model: activeList
VpnListItem {
name: modelData.name
type: modelData.type
isConnected: true
isLoading: modelData.isLoading
onButtonClicked: {
if (!main) return
main.disconnectFrom(modelData.uuid)
}
}
}
}
}
// DISCONNECTED
NBox {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(networksListInactive.implicitHeight + Style.marginXL)
visible: inactiveList.length > 0
ColumnLayout {
id: networksListInactive
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.fillWidth: true
Layout.leftMargin: Style.marginS
spacing: Style.marginS
NLabel {
label: pluginApi?.tr("common.disconnected")
Layout.fillWidth: true
}
}
Repeater {
model: inactiveList
VpnListItem {
name: modelData.name
type: modelData.type
isConnected: false
isLoading: modelData.isLoading
onButtonClicked: {
if (!main) return
main.connectTo(modelData.uuid)
}
}
}
}
}
// EMPTY
NBox {
id: emptyBox
visible: vpnList.length < 1
Layout.fillWidth: true
Layout.preferredHeight: Math.round(emptyColumn.implicitHeight + Style.marginM * 2 + 1)
ColumnLayout {
id: emptyColumn
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginL
Item {
Layout.fillHeight: true
}
NIcon {
icon: "search"
pointSize: 48
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: pluginApi?.tr("panel.emptyTitle")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: pluginApi?.tr("panel.emptyDescription")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NButton {
text: pluginApi?.tr("common.refresh")
icon: "refresh"
Layout.alignment: Qt.AlignHCenter
onClicked: {
if (main) {
main.refresh()
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
}
}
}
}
@@ -0,0 +1,21 @@
# Network Manager VPN
![Preview](preview.png)
Plugin to connect to your VPN connections from the bar and Control Center. Supports OpenVPN and WireGuard connections managed by NetworkManager. Editing and creation are achieved using
nm-connection-editor
## Usage
Install the plugin and add it to your bar:
- **Bar widget** — A shield icon that shows if there is an active connection. Click it to open the VPN panel.
- **VPN panel** — Lists all configured VPN connections.
The panel refreshes automatically every 5 seconds. You can also trigger a manual refresh with the refresh button.
## Requirements
- **Noctalia Shell** ≥ 3.6.0
- **NetworkManager** with `nmcli`
- **nm-connection-editor** to edit and create connections
@@ -0,0 +1,256 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
ColumnLayout {
id: root
property var pluginApi: null
readonly property var pluginSettings: pluginApi?.pluginSettings ?? pluginApi?.manifest?.metadata?.defaultSettings ?? ({})
readonly property var main: pluginApi?.mainInstance ?? null
readonly property var vpnList: main ? main.vpnList : []
// Local state
property string editDisplayMode: root.pluginSettings.displayMode ?? ""
property string editConnectedColor: root.pluginSettings.connectedColor ?? ""
property string editDisconnectedColor: root.pluginSettings.disconnectedColor ?? ""
property bool disableToastNotifications: root.pluginSettings?.disableToastNotifications ?? false
property string pendingDeleteUuid: ""
property string pendingDeleteName: ""
readonly property var displayModeModel: [{
"key": "onhover",
"name": I18n.tr("display-modes.on-hover")
}, {
"key": "alwaysShow",
"name": I18n.tr("display-modes.always-show")
}, {
"key": "alwaysHide",
"name": I18n.tr("display-modes.always-hide")
}]
function saveSettings() {
pluginApi.pluginSettings.displayMode = root.editDisplayMode;
pluginApi.pluginSettings.connectedColor = root.editConnectedColor;
pluginApi.pluginSettings.disconnectedColor = root.editDisconnectedColor;
pluginApi.pluginSettings.disableToastNotifications = root.disableToastNotifications;
pluginApi.saveSettings();
Logger.i("NetworkManagerVpn", "Settings saved successfully");
}
NComboBox {
label: I18n.tr("common.display-mode")
description: I18n.tr("bar.volume.display-mode-description")
minimumWidth: 200
model: root.displayModeModel
currentKey: root.editDisplayMode
onSelected: (key) => {
root.editDisplayMode = key;
}
}
NColorChoice {
label: pluginApi?.tr("settings.connectedColor")
description: pluginApi?.tr("settings.connectedColorDescription")
currentKey: root.editConnectedColor
onSelected: (key) => {
root.editConnectedColor = key;
}
}
NColorChoice {
label: pluginApi?.tr("settings.disconnectedColor")
description: pluginApi?.tr("settings.disconnectedColorDescription")
currentKey: root.editDisconnectedColor
onSelected: (key) => {
root.editDisconnectedColor = key;
}
}
NToggle {
Layout.fillWidth: true
label: pluginApi?.tr("settings.disableToastNotifications")
description: pluginApi?.tr("settings.disableToastNotificationsDescription")
checked: root.disableToastNotifications
onToggled: checked => {
root.disableToastNotifications = checked;
}
}
NBox {
Layout.fillWidth: true
Layout.topMargin: Style.marginM
Layout.preferredHeight: Math.round(vpnSection.implicitHeight + Style.marginL * 2)
ColumnLayout {
id: vpnSection
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
NLabel {
label: pluginApi?.tr("settings.vpnConnections")
Layout.fillWidth: true
}
NText {
visible: root.vpnList.length === 0
text: pluginApi?.tr("settings.noVpnConnections")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
Layout.leftMargin: Style.marginXS
}
Repeater {
model: root.vpnList
NBox {
id: vpnRow
readonly property bool confirmingDelete: root.pendingDeleteUuid === modelData.uuid
Layout.fillWidth: true
Layout.leftMargin: Style.marginXS
Layout.rightMargin: Style.marginXS
implicitHeight: Math.round(rowContent.implicitHeight + Style.marginL)
color: vpnRow.confirmingDelete ? Qt.alpha(Color.mError, 0.12) : Color.mSurface
ColumnLayout {
id: rowContent
anchors.fill: parent
anchors.leftMargin: Style.marginM
anchors.rightMargin: Style.marginM
anchors.topMargin: Style.marginS
anchors.bottomMargin: Style.marginS
spacing: Style.marginS
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: "router"
pointSize: Style.fontSizeXXL
color: modelData.connected ? Color.mPrimary : Color.mOnSurfaceVariant
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: modelData.name
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
NText {
text: modelData.type || "vpn"
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
}
NIconButton {
visible: !vpnRow.confirmingDelete
icon: "edit"
tooltipText: pluginApi?.tr("common.edit")
baseSize: Style.baseWidgetSize * 0.75
onClicked: {
main.editConnection(modelData.uuid);
close();
}
}
NIconButton {
visible: !vpnRow.confirmingDelete
icon: "trash-x"
tooltipText: pluginApi?.tr("common.delete")
baseSize: Style.baseWidgetSize * 0.75
onClicked: {
root.pendingDeleteUuid = modelData.uuid;
root.pendingDeleteName = modelData.name;
}
}
NButton {
visible: vpnRow.confirmingDelete
text: pluginApi?.tr("common.delete")
fontSize: Style.fontSizeXS
backgroundColor: Color.mError
outlined: false
onClicked: {
main.removeConnection(root.pendingDeleteUuid);
root.pendingDeleteUuid = "";
root.pendingDeleteName = "";
}
}
NButton {
visible: vpnRow.confirmingDelete
text: pluginApi?.tr("common.cancel")
fontSize: Style.fontSizeXS
outlined: true
onClicked: {
root.pendingDeleteUuid = "";
root.pendingDeleteName = "";
}
}
}
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginXS
spacing: Style.marginS
Item {
Layout.fillWidth: true
}
NButton {
text: pluginApi?.tr("settings.addVpn")
icon: "add"
fontSize: Style.fontSizeS
outlined: true
onClicked: {
main.addConnection("vpn");
close();
}
}
NButton {
text: pluginApi?.tr("settings.addWireguard")
icon: "add"
fontSize: Style.fontSizeS
outlined: true
onClicked: {
main.addConnection("wireguard");
close();
}
}
}
}
}
}
@@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Widgets
import qs.Services.UI
NBox {
id: root
property string name: ""
property string type: ""
property bool isConnected: false
property bool isLoading: false
signal buttonClicked
Layout.fillWidth: true
Layout.leftMargin: Style.marginXS
Layout.rightMargin: Style.marginXS
implicitHeight: Math.round(netColumn.implicitHeight + (Style.marginXL))
color: root.isConnected ? Qt.alpha(Color.mPrimary, 0.15) : Color.mSurface
ColumnLayout {
id: netColumn
width: parent.width - (Style.marginXL)
x: Style.marginM
y: Style.marginM
spacing: Style.marginS
// Main row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NIcon {
icon: "router"
pointSize: Style.fontSizeXXL
color: root.isConnected ? Color.mPrimary : Color.mOnSurface
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: root.name
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
elide: Text.ElideRight
Layout.fillWidth: true
}
RowLayout {
spacing: Style.marginXS
NText {
text: root.type
pointSize: Style.fontSizeXXS
color: Color.mOnSurfaceVariant
}
Item {
Layout.preferredWidth: Style.marginXXS
}
Rectangle {
visible: root.isConnected
color: Color.mPrimary
radius: height * 0.5
width: Math.round(connectedText.implicitWidth + (Style.marginS * 2))
height: Math.round(connectedText.implicitHeight + (Style.marginXS))
NText {
id: connectedText
anchors.centerIn: parent
text: pluginApi?.tr("common.connected")
pointSize: Style.fontSizeXXS
color: Color.mOnPrimary
}
}
}
}
// Action area
RowLayout {
spacing: Style.marginS
NBusyIndicator {
visible: root.isLoading
running: visible
color: Color.mPrimary
size: Style.baseWidgetSize * 0.5
}
NButton {
visible: root.isConnected
text: pluginApi?.tr("common.disconnect")
outlined: !hovered
fontSize: Style.fontSizeS
backgroundColor: Color.mError
enabled: !root.isLoading
onClicked: {
root.buttonClicked()
}
}
NButton {
visible: !root.isConnected
text: pluginApi?.tr("common.connect")
outlined: !hovered
fontSize: Style.fontSizeS
enabled: !root.isLoading
onClicked: {
root.buttonClicked()
}
}
}
}
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "Kein VPN gefunden",
"emptyDescription": "Verwende den Netzwerk-Manager um ein VPN hinzuzufügen"
},
"toast": {
"connectedTo": "Verbunden mit {name}",
"disconnectedFrom": "Von {Name} getrennt",
"connectionError": "Verbindungsaufbau mit {name} fehlgeschlagen",
"disconnectionError": "Die Trennung von {name} ist fehlgeschlagen",
"vpnRemoved": "VPN \"{name}\" wurde entfernt",
"vpnRemoveError": "Der VPN \"{name}\" konnte nicht entfernt werden"
},
"settings": {
"pluginSettings": "Plugin Einstellungen",
"connectedColor": "Verknüpfte Farbe",
"connectedColorDescription": "Wähle die Farbe für das Symbol und den Text aus, die bei bestehender Verbindung angezeigt werden sollen",
"disconnectedColor": "Nicht verknüpfte Farbe",
"disconnectedColorDescription": "Wähle die Farbe für das Symbol und den Text bei unterbrochener Verbindung aus",
"vpnConnections": "VPN Verbindungen",
"noVpnConnections": "Keine VPN Verbindungen konfiguriert",
"addVpn": "VPN hinzufügen",
"addWireguard": "WireGuard hinzufügen",
"disableToastNotifications": "Toast-Benachrichtigungen deaktivieren",
"disableToastNotificationsDescription": "Keine Toast-Benachrichtigungen anzeigen, wenn eine VPN-Verbindung hergestellt oder getrennt wird"
},
"common": {
"vpn": "VPN",
"refresh": "Aktualisieren",
"connected": "Verbunden",
"disconnected": "Getrennt",
"connect": "Verbinden",
"disconnect": "Trennen",
"close": "Schließen",
"settings": "Einstellungen",
"edit": "Bearbeiten",
"delete": "Löschen",
"cancel": "Abbrechen"
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "No VPN found",
"emptyDescription": "Use Network Manager to add a VPN"
},
"toast": {
"connectedTo": "Connected to {name}",
"disconnectedFrom": "Disconnected from {name}",
"connectionError": "Failed to connect {name}",
"disconnectionError": "Failed to disconnect {name}",
"vpnRemoved": "VPN \"{name}\" removed",
"vpnRemoveError": "Failed to remove VPN \"{name}\""
},
"settings": {
"pluginSettings": "Plugin settings",
"connectedColor": "Connected color",
"connectedColorDescription": "Chose the color to use for icon and text when connected",
"disconnectedColor": "Disconnected color",
"disconnectedColorDescription": "Chose the color to use for icon and text when disconnected",
"vpnConnections": "VPN Connections",
"noVpnConnections": "No VPN connections configured.",
"addVpn": "Add VPN",
"addWireguard": "Add WireGuard",
"disableToastNotifications": "Disable toast notifications",
"disableToastNotificationsDescription": "Do not show toast notifications when a VPN connects or disconnects"
},
"common": {
"vpn": "VPN",
"refresh": "Refresh",
"connected": "Connected",
"disconnected": "Disconnected",
"connect": "Connect",
"disconnect": "Disconnect",
"close": "Close",
"settings": "Settings",
"edit": "Edit",
"delete": "Delete",
"cancel": "Cancel"
}
}
@@ -0,0 +1,40 @@
{
"panel": {
"emptyTitle": "VPN не найден",
"emptyDescription": "Используйте Менеджер Подключений для добавления VPN"
},
"toast": {
"connectedTo": "Подключиться к {name}",
"disconnectedFrom": "Отключиться от {name}",
"connectionError": "Ошибка подключения к {name}",
"disconnectionError": "Ошибка отключения от {name}",
"vpnRemoved": "VPN \"{name}\" удалён",
"vpnRemoveError": "Ошибка удаления VPN \"{name}\""
},
"settings": {
"pluginSettings": "Настройка плагина",
"connectedColor": "Цвет активного подключения",
"connectedColorDescription": "Выберите цвет значка и текста при активном подключении",
"disconnectedColor": "Цвет отсутствия подключения",
"disconnectedColorDescription": "Выберите цвет значка и текста при отсутствии подключения",
"vpnConnections": "VPN-соединения",
"noVpnConnections": "VPN-подключения не настроены.",
"addVpn": "Добавить VPN",
"addWireguard": "Добавить WireGuard",
"disableToastNotifications": "Отключить уведомления",
"disableToastNotificationsDescription": "Не показывать уведомления о статусе VPN-соединения"
},
"common": {
"vpn": "VPN",
"refresh": "Обновить",
"connected": "Подключено",
"disconnected": "Отключено",
"connect": "Подключить",
"disconnect": "Отключить",
"close": "Закрыть",
"settings": "Настройки",
"edit": "Редактировать",
"delete": "Удалить",
"cancel": "Отмена"
}
}
@@ -0,0 +1,33 @@
{
"id": "network-manager-vpn",
"name": "Network Manager VPN",
"version": "1.3.0",
"minNoctaliaVersion": "3.6.0",
"author": "nZO",
"license": "MIT",
"repository": "https://github.com/noctalia-dev/noctalia-plugins",
"description": "Connect to VPN via NetworkManager",
"tags": [
"Bar",
"Panel",
"Network"
],
"entryPoints": {
"main": "Main.qml",
"barWidget": "BarWidget.qml",
"controlCenterWidget": "ControlCenterWidget.qml",
"panel": "Panel.qml",
"settings": "Settings.qml"
},
"dependencies": {
"plugins": []
},
"metadata": {
"defaultSettings": {
"displayMode": "onhover",
"disconnectedColor": "none",
"connectedColor": "primary",
"disableToastNotifications": false
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

@@ -0,0 +1,6 @@
{
"displayMode": "onhover",
"disconnectedColor": "none",
"connectedColor": "primary",
"disableToastNotifications": false
}
+54
View File
@@ -0,0 +1,54 @@
import QtQuick
import Quickshell
import Quickshell.Services.Polkit
import qs.Commons
Item {
id: root
property var pluginApi: null
PolkitAgent {
id: agent
onIsActiveChanged: {
if (isActive) {
openWindow()
} else {
closeWindow()
}
}
}
property var window: null
function openWindow() {
if (agent.flow === null) {
Logger.w("polkit-agent: Cannot open window, agent.flow is null");
return;
}
if (window === null) {
var component = Qt.createComponent("PolkitWindow.qml");
if (component.status === Component.Ready) {
window = component.createObject(root, {
flow: agent.flow,
pluginApi: root.pluginApi
});
if (window !== null) {
window.visible = true;
}
}
component.destroy();
} else {
window.flow = agent.flow;
window.pluginApi = root.pluginApi;
window.visible = true;
}
}
function closeWindow() {
if (window !== null) {
window.destroy();
window = null;
}
}
}
@@ -0,0 +1,259 @@
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Effects
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Polkit
import Quickshell.Wayland
import qs.Commons
import qs.Widgets
import qs.Services.UI
PanelWindow {
id: polkitWindow
property AuthFlow flow
property var pluginApi
property string resolvedMessage: ""
property var transientMatch: null
Connections {
target: flow
function onFailedChanged() {
if (flow && flow.failed) {
ToastService.showError(
pluginApi ? pluginApi.tr("error.failed.title") : "Authentication Failed",
pluginApi ? pluginApi.tr("error.failed.message") : "The password you entered was incorrect."
)
}
}
}
// Resolve transient service names when flow changes
onFlowChanged: {
resolveTransientServiceName(flow.message);
}
// Resolve transient service names (run-PID-random.service) to actual command
Process {
id: cmdLineProcess
command: ["cat", "/proc/0/cmdline"]
running: false
stdout: StdioCollector {
onStreamFinished: function() {
var args = this.text.split(String.fromCharCode(0)).filter(function(s) { return s.length > 0; });
Logger.i("polkit-agent: Process output length=" + this.text.length + ", args=" + args.length);
if (args.length > 0 && polkitWindow.transientMatch) {
var resolvedCmd = args.join(' ');
var isCommand = args.length > 1 || args[0].includes('/');
polkitWindow.resolvedMessage = polkitWindow.resolvedMessage.replace(polkitWindow.transientMatch[0], resolvedCmd);
if (isCommand) {
polkitWindow.resolvedMessage = polkitWindow.resolvedMessage.replace(/transient unit/i, 'run command');
}
Logger.i("polkit-agent: Resolved message: " + polkitWindow.resolvedMessage);
}
}
}
}
function resolveTransientServiceName(message) {
if (!message) return;
var match = message.match(/run-p?(\d+)-[^.]+\.service/);
if (!match) {
Logger.i("polkit-agent: No transient service pattern in message: " + message);
polkitWindow.resolvedMessage = message;
polkitWindow.transientMatch = null;
return;
}
var pid = match[1];
Logger.i("polkit-agent: Found transient service pattern, PID=" + pid + ", message=" + message);
polkitWindow.resolvedMessage = message;
polkitWindow.transientMatch = match;
cmdLineProcess.command = ["cat", "/proc/" + pid + "/cmdline"];
cmdLineProcess.running = true;
}
// Layer above everything else (critical system prompt)
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
readonly property real shadowPadding: Style.shadowBlurMax + Style.marginL
// Explicit size - include shadowPadding so the shadow isn't clipped at window corners
implicitWidth: 400 * Style.uiScaleRatio + shadowPadding * 2
implicitHeight: contentLayout.implicitHeight + (Style.marginL * 2) + shadowPadding * 2
color: "transparent"
Item {
id: contentContainer
anchors.fill: parent
anchors.margins: shadowPadding
focus: true
Keys.onPressed: function(event) {
if (!flow) return;
if (Keybinds.checkKey(event, "escape", Settings)) {
flow.cancelAuthenticationRequest();
event.accepted = true;
} else if (Keybinds.checkKey(event, "enter", Settings)) {
if (passwordInput.text !== "") {
flow.submit(passwordInput.text);
passwordInput.text = "";
}
event.accepted = true;
}
}
transform: Translate {
id: shakeTranslate
x: 0
}
// Error animation
SequentialAnimation {
id: errorShake
running: flow && flow.failed
loops: 1
NumberAnimation { target: shakeTranslate; property: "x"; from: 0; to: -10; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: shakeTranslate; property: "x"; from: -10; to: 10; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: shakeTranslate; property: "x"; from: 10; to: -10; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: shakeTranslate; property: "x"; from: -10; to: 10; duration: 50; easing.type: Easing.InOutQuad }
NumberAnimation { target: shakeTranslate; property: "x"; from: 10; to: 0; duration: 50; easing.type: Easing.InOutQuad }
}
// Shadow effect (behind background)
NDropShadow {
anchors.fill: customBackground
source: customBackground
autoPaddingEnabled: true
z: -1
}
Rectangle {
id: customBackground
anchors.fill: parent
radius: Style.radiusL
color: Qt.alpha(Color.mSurface, 0.95)
border.color: (flow && (flow.failed || flow.supplementaryIsError)) ? Color.mError : Color.mOutline
border.width: Style.borderS
Behavior on border.color {
ColorAnimation { duration: Style.animationFast }
}
}
ColumnLayout {
id: contentLayout
anchors.centerIn: parent
width: parent.width - (Style.marginL * 2)
spacing: Style.marginM
// Header with Icon
RowLayout {
Layout.fillWidth: true
spacing: Style.marginM
NImageRounded {
Layout.preferredWidth: Style.fontSizeXXL * 2
Layout.preferredHeight: Style.fontSizeXXL * 2
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) || ((flow && flow.iconName) ? Quickshell.iconPath(flow.iconName) : "")
fallbackIcon: "lock"
borderWidth: 0
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXS
NText {
text: polkitWindow.resolvedMessage || (flow ? flow.message : (pluginApi ? pluginApi.tr("window.title") : "Authentication Required"))
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
wrapMode: Text.Wrap
Layout.fillWidth: true
}
NText {
text: flow ? flow.actionId : ""
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
wrapMode: Text.Wrap
Layout.fillWidth: true
visible: text !== ""
}
}
}
// Supplementary Message (Error or prompt)
NText {
visible: flow && flow.supplementaryMessage !== ""
text: flow ? flow.supplementaryMessage : ""
pointSize: Style.fontSizeS
color: (flow && flow.supplementaryIsError) ? Color.mError : Color.mOnSurfaceVariant
wrapMode: Text.Wrap
Layout.fillWidth: true
}
// Input Field
NTextInput {
id: passwordInput
Layout.fillWidth: true
placeholderText: flow ? flow.inputPrompt : (pluginApi ? pluginApi.tr("prompt.password") : "Password")
label: (flow && flow.isResponseRequired) ? (pluginApi ? pluginApi.tr("prompt.password") : "Password") : ""
inputItem.echoMode: (flow && !flow.responseVisible) ? TextInput.Password : TextInput.Normal
visible: flow && flow.isResponseRequired
onAccepted: {
if (flow) {
flow.submit(passwordInput.text)
passwordInput.text = ""
}
}
}
// Actions
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginS
spacing: Style.marginM
Item { Layout.fillWidth: true } // Spacer
NButton {
text: pluginApi ? pluginApi.tr("action.cancel") : "Cancel"
backgroundColor: Color.mSurfaceVariant
textColor: Color.mOnSurfaceVariant
outlined: false
onClicked: {
if (flow) flow.cancelAuthenticationRequest()
}
}
NButton {
text: pluginApi ? pluginApi.tr("action.authenticate") : "Authenticate"
backgroundColor: Color.mPrimary
textColor: Color.mOnPrimary
enabled: flow && flow.isResponseRequired
onClicked: {
if (flow) {
flow.submit(passwordInput.text)
passwordInput.text = ""
}
}
}
}
}
}
// Focus handling
Component.onCompleted: {
passwordInput.inputItem.forceActiveFocus()
}
}
+13
View File
@@ -0,0 +1,13 @@
# Polkit Agent
This plugin provides a Polkit authentication agent for Noctalia. It allows you to authenticate actions that require elevated privileges directly within the shell.
![Preview](preview.png)
## Important
To use this plugin, you **must disable or uninstall your existing polkit authentication agent** (e.g., `polkit-gnome`, `polkit-kde-agent`, `lxpolkit`, etc).
Having multiple polkit agents running simultaneously will cause conflicts and prevent this plugin from working correctly.
> You may need to **restart your session or computer** after enabling this plugin for the changes to take effect and for the new agent to be registered properly.

Some files were not shown because too many files have changed in this diff Show More