Compare commits
3
Commits
master
..
a2afca2d74
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2afca2d74
|
||
|
|
ca361ec226
|
||
|
|
f26e36bbb2
|
No files matched your search
@@ -1,9 +1,9 @@
|
||||
{ inputs, ... }: {
|
||||
imports = [ inputs.aagl.nixosModules.default ];
|
||||
nix.settings = inputs.aagl.nixConfig; # Set up Cachix
|
||||
programs.honkers-railway-launcher.enable = false;
|
||||
programs.sleepy-launcher.enable = false;
|
||||
programs.anime-game-launcher.enable = false;
|
||||
programs.honkers-railway-launcher.enable = true;
|
||||
programs.sleepy-launcher.enable = true;
|
||||
programs.anime-game-launcher.enable = true;
|
||||
networking.hosts."0.0.0.0" = [
|
||||
"overseauspider.yuanshen.com"
|
||||
"log-upload-os.hoyoverse.com"
|
||||
|
||||
@@ -8,31 +8,12 @@ let
|
||||
};
|
||||
in {
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
# Keep the animated wallpaper visible through the panel's translucent surface.
|
||||
layer_rule = [
|
||||
{
|
||||
match.namespace = "^waybar$";
|
||||
blur = true;
|
||||
# Blur the glass, but exclude its low-alpha CSS shadow from the mask.
|
||||
ignore_alpha = 0.2;
|
||||
xray = true;
|
||||
}
|
||||
{
|
||||
match.namespace = "^walker$";
|
||||
blur = true;
|
||||
ignore_alpha = 0.08;
|
||||
xray = true;
|
||||
}
|
||||
];
|
||||
|
||||
monitor = [
|
||||
{
|
||||
output = "DP-3";
|
||||
mode = "2560x1440@100";
|
||||
mode = "1920x1080@165";
|
||||
position = "0x0";
|
||||
scale = 1;
|
||||
cm = "srgb";
|
||||
supports_hdr = -1;
|
||||
}
|
||||
{
|
||||
output = "HDMI-A-1";
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
lucid.liveDecoder = "cuda";
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
lucid.prefsSeed = ./lucid-prefs-impreza.json;
|
||||
lucid.widgetSeed = ./lucid-widgets-impreza.json;
|
||||
lucid.initialFiles = {
|
||||
"${config.home.homeDirectory}/.config/hypr/lucid-settings.lua" = ./lucid-settings-impreza.lua;
|
||||
"${config.home.homeDirectory}/.config/hypr/lucid-glass.lua" = ./lucid-glass-impreza.lua;
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
-- written by Lucid Settings > Glass, and rewritten on every change there
|
||||
return {
|
||||
apps = {
|
||||
{ name = "VSCodium", class = { "codium", "vscodium" }, opacity = 0.90 },
|
||||
{ name = "Obsidian", class = { "obsidian" }, opacity = 0.88 },
|
||||
{ name = "Files", class = { "org.gnome.nautilus", "nautilus" }, opacity = 0.80 },
|
||||
},
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
{
|
||||
"accentPunch": 1.05,
|
||||
"audioCombine": "",
|
||||
"audioMeters": true,
|
||||
"audioMoveStreams": true,
|
||||
"autoColorMode": false,
|
||||
"barAutoHide": false,
|
||||
"barEnabled": true,
|
||||
"barFullCorner": 18,
|
||||
"barHeight": 40,
|
||||
"barHoverGrow": 3,
|
||||
"barHoverOpen": false,
|
||||
"barJoinDividers": true,
|
||||
"barJoins": "[\"workspaces+tray\"]",
|
||||
"barLayout": "{\"left\":[\"start\",\"workspaces\",\"tray\",\"media\"],\"center\":[\"clock\"],\"right\":[\"window\",\"notifications\",\"system\",\"privacy\",\"power\"]}",
|
||||
"barMotionScale": 1.2000000000000002,
|
||||
"barNotchFlare": 20,
|
||||
"barPopupGap": 10,
|
||||
"barPopupMode": false,
|
||||
"barPopupNotched": false,
|
||||
"barSideMargin": 0,
|
||||
"barSpacing": 11,
|
||||
"barStyle": "notch",
|
||||
"barTopMargin": 22,
|
||||
"barZoneGap": 28,
|
||||
"btAgent": true,
|
||||
"btReceive": true,
|
||||
"btReceiveAutoPaired": false,
|
||||
"btScanOnOpen": true,
|
||||
"btShowUnnamed": false,
|
||||
"clipboardEnabled": true,
|
||||
"clock24h": true,
|
||||
"clockDateFormat": "long",
|
||||
"clockShowDate": true,
|
||||
"clockShowSeconds": true,
|
||||
"clockShowTimer": true,
|
||||
"clockShowWeather": true,
|
||||
"clockStyle": "accent",
|
||||
"clockWorldZones": "Europe/Berlin,Europe/Moscow,America/New_York",
|
||||
"cornerScale": 0.5,
|
||||
"desktopIconSize": "medium",
|
||||
"desktopIconSpacing": "normal",
|
||||
"desktopIcons": false,
|
||||
"desktopIconsAutoArrange": false,
|
||||
"desktopIconsCorner": "left",
|
||||
"desktopIconsDrives": true,
|
||||
"desktopIconsHidden": false,
|
||||
"desktopIconsHideExt": false,
|
||||
"desktopIconsHome": false,
|
||||
"desktopIconsOpen": "double",
|
||||
"desktopIconsShown": true,
|
||||
"desktopIconsSort": "name",
|
||||
"desktopIconsSortDesc": false,
|
||||
"desktopIconsSpring": true,
|
||||
"desktopIconsThumbs": true,
|
||||
"desktopIconsTrash": true,
|
||||
"desktopKeys": "{}",
|
||||
"desktopMenu": true,
|
||||
"desktopSelection": true,
|
||||
"dndAllowCritical": true,
|
||||
"dndFullscreen": false,
|
||||
"doNotDisturb": false,
|
||||
"dockAutoHide": true,
|
||||
"dockBottomMargin": 13,
|
||||
"dockDynamicHide": true,
|
||||
"dockEnabled": true,
|
||||
"dockHoverEffect": 1,
|
||||
"dockIconPadding": 0,
|
||||
"dockIconSize": 37,
|
||||
"dockIconTiles": true,
|
||||
"dockMagnify": false,
|
||||
"dockNotchFlare": 25,
|
||||
"dockRadius": 40,
|
||||
"dockShowIndicators": true,
|
||||
"dockShowRunning": true,
|
||||
"dockShowTooltips": true,
|
||||
"dockSpacing": 10,
|
||||
"dockSplitGap": 8,
|
||||
"dockStyle": "notch",
|
||||
"envAdopted": true,
|
||||
"envAppFont": "DejaVu Sans",
|
||||
"envAppFontSize": 13,
|
||||
"envApplyGtk": true,
|
||||
"envApplyHypr": true,
|
||||
"envApplyQt": true,
|
||||
"envColorScheme": "auto",
|
||||
"envCursorShadow": true,
|
||||
"envCursorSize": 16,
|
||||
"envCursorTheme": "Miku",
|
||||
"envDocumentFont": "DejaVu Sans",
|
||||
"envDocumentFontSize": 13,
|
||||
"envFontSync": false,
|
||||
"envGtkTheme": "adw-gtk3",
|
||||
"envIconSet": "gravity",
|
||||
"envIconTheme": "Pop-Dark",
|
||||
"envLegacyIcons": false,
|
||||
"envMonoFont": "JetBrains Mono",
|
||||
"envMonoFontSize": 13,
|
||||
"envQtPlatformTheme": "",
|
||||
"envQtStyle": "kvantum",
|
||||
"fontFamily": "DejaVu Sans",
|
||||
"fontScale": 1,
|
||||
"gameModeOffCmd": "",
|
||||
"gameModeOnCmd": "",
|
||||
"gameModeStatusCmd": "",
|
||||
"glassApps": "vscodium,obsidian,nautilus",
|
||||
"glassValues": "nautilus=0.8,vscodium=0.9",
|
||||
"gpsEnabled": false,
|
||||
"hiddenThemes": "",
|
||||
"hyprOptions": "{\"general.border_size\":1,\"general.gaps_in\":2,\"decoration.dim_strength\":0.55,\"dwindle.split_width_multiplier\":1.1}",
|
||||
"idleAdopted": false,
|
||||
"idleAutostart": true,
|
||||
"idleDim": true,
|
||||
"idleDimAfter": 120,
|
||||
"idleDimKeyboard": true,
|
||||
"idleDimLevel": 10,
|
||||
"idleEnabled": false,
|
||||
"idleKeepAwake": false,
|
||||
"idleLock": false,
|
||||
"idleLockAfter": 600,
|
||||
"idleLockBeforeSleep": false,
|
||||
"idleRespectInhibitors": true,
|
||||
"idleScreenOff": true,
|
||||
"idleScreenOffAfter": 900,
|
||||
"idleSuspend": false,
|
||||
"idleSuspendAfter": 1800,
|
||||
"idleSuspendOnAc": false,
|
||||
"idleWakeAfterSleep": true,
|
||||
"idleWhileMedia": true,
|
||||
"jellyAmount": 0.75,
|
||||
"jellyMotion": true,
|
||||
"kdeConnectEnabled": true,
|
||||
"launcherActionHints": true,
|
||||
"launcherAddressFirst": true,
|
||||
"launcherAppActions": true,
|
||||
"launcherAppDescriptions": false,
|
||||
"launcherCalculator": true,
|
||||
"launcherContentScale": 0.8,
|
||||
"launcherDensity": "comfortable",
|
||||
"launcherFrequentFirst": true,
|
||||
"launcherHiddenApps": "",
|
||||
"launcherModeBar": true,
|
||||
"launcherPowerButtons": "suspend,reboot,shutdown",
|
||||
"launcherPowerChips": true,
|
||||
"launcherPowerSearch": true,
|
||||
"launcherSearchEngine": "google",
|
||||
"launcherSearchPosition": "bottom",
|
||||
"launcherSettingsResults": true,
|
||||
"launcherWebRow": true,
|
||||
"launcherWidth": "standard",
|
||||
"launcherWindows": true,
|
||||
"liveGameMode": "pause",
|
||||
"livePauseUnder": "fullscreen",
|
||||
"liveWallpapers": false,
|
||||
"locationLabel": "",
|
||||
"locationLat": 52.4083,
|
||||
"locationLon": 16.9336,
|
||||
"locationName": "",
|
||||
"locationTz": "",
|
||||
"matugenContrast": 0,
|
||||
"matugenScheme": "scheme-tonal-spot",
|
||||
"matugenSourceImage": "",
|
||||
"matugenSourceIndex": 0,
|
||||
"mediaArtist": true,
|
||||
"mediaHideIdle": true,
|
||||
"mediaPanelStyle": "side",
|
||||
"mediaPlayButton": true,
|
||||
"mediaStyle": "disc",
|
||||
"mediaTitleWidth": 210,
|
||||
"mediaWheelVolume": true,
|
||||
"motionScale": 1,
|
||||
"nightLight": false,
|
||||
"nightLightFrom": 1260,
|
||||
"nightLightSchedule": "off",
|
||||
"nightLightTemp": 4600,
|
||||
"nightLightTo": 420,
|
||||
"notifGrouping": true,
|
||||
"notifInlineReply": true,
|
||||
"notifMaxHistory": 50,
|
||||
"notifMutedApps": "",
|
||||
"notifProgress": true,
|
||||
"notifSeenApps": "",
|
||||
"notifShowIcons": true,
|
||||
"notifSound": false,
|
||||
"notifSoundName": "glint",
|
||||
"notifSoundUrgentOnly": false,
|
||||
"notifSoundVolume": 0.6,
|
||||
"notifTimestamps": true,
|
||||
"notificationsStyle": "badge",
|
||||
"osdCapsLock": true,
|
||||
"osdNumLock": true,
|
||||
"pinBadge": "top-right",
|
||||
"pinBorder": "gradient",
|
||||
"pinPulse": true,
|
||||
"pomodoroAutoFocus": true,
|
||||
"pomodoroAutoStart": true,
|
||||
"pomodoroFocus": 25,
|
||||
"pomodoroGoal": 0,
|
||||
"pomodoroLong": 15,
|
||||
"pomodoroRepeat": true,
|
||||
"pomodoroRounds": 4,
|
||||
"pomodoroShort": 5,
|
||||
"pomodoroSilence": false,
|
||||
"powerModuleActions": "suspend,hibernate,logout,reboot,shutdown",
|
||||
"powerModuleConfirm": true,
|
||||
"powerModulePanelStyle": "list",
|
||||
"powerModuleStyle": "icon",
|
||||
"powerModuleUptime": true,
|
||||
"privacyAlwaysShown": false,
|
||||
"privacyStyle": "marks",
|
||||
"privacyToast": true,
|
||||
"privacyWatch": "mic,camera,screen",
|
||||
"quietFrom": 1320,
|
||||
"quietHours": false,
|
||||
"quietTo": 420,
|
||||
"recordCountdown": 3,
|
||||
"recordEncoder": "auto",
|
||||
"recordFps": 0,
|
||||
"reminderSound": true,
|
||||
"shellGap": 5,
|
||||
"shotLoupe": true,
|
||||
"shotOcrLang": "eng+rus",
|
||||
"shotPreview": "preview",
|
||||
"shotPreviewSeconds": 6,
|
||||
"shotRoundWindows": true,
|
||||
"shotSaveFile": false,
|
||||
"showClock": true,
|
||||
"showKbLayout": true,
|
||||
"showMedia": true,
|
||||
"showNotifications": true,
|
||||
"showPower": false,
|
||||
"showPrivacy": false,
|
||||
"showStart": false,
|
||||
"showSystem": true,
|
||||
"showTray": true,
|
||||
"showWindow": false,
|
||||
"showWorkspaces": true,
|
||||
"soundOnBattery": true,
|
||||
"soundOnBluetooth": true,
|
||||
"soundOnBrightness": true,
|
||||
"soundOnCamera": true,
|
||||
"soundOnCaps": true,
|
||||
"soundOnCapture": true,
|
||||
"soundOnCharger": true,
|
||||
"soundOnMic": true,
|
||||
"soundOnUsb": true,
|
||||
"soundOnVolume": true,
|
||||
"specialBlur": true,
|
||||
"specialComms": true,
|
||||
"specialCommsApps": "vesktop,entry:com.ayugram.desktop",
|
||||
"specialCustom": "[{\"key\":\"daemons\",\"label\":\"Daemons\",\"glyph\":\"build\",\"on\":true,\"apps\":\"\"}]",
|
||||
"specialDim": 0.2,
|
||||
"specialGaps": 0,
|
||||
"specialHideOnSwitch": false,
|
||||
"specialKeepApps": true,
|
||||
"specialMusic": true,
|
||||
"specialMusicApps": "auto",
|
||||
"specialScratchpad": true,
|
||||
"specialSysmon": true,
|
||||
"specialSysmonApps": "auto",
|
||||
"specialTodo": true,
|
||||
"specialTodoApps": "auto",
|
||||
"startModuleStyle": "logo",
|
||||
"startupApps": "[]",
|
||||
"startupEnv": "[]",
|
||||
"startupXdg": false,
|
||||
"storageLowPercent": 10,
|
||||
"storageLowWarn": true,
|
||||
"storageTrashDays": 0,
|
||||
"surfaceDarkness": -1,
|
||||
"surfaceTint": -1,
|
||||
"sysSoundVolume": 0.6,
|
||||
"sysSounds": true,
|
||||
"systemTiles": "dnd,awake,dark,airplane,location,mic,capture,record,picker,keyboard,timer,session",
|
||||
"themeColour": "#6750a4",
|
||||
"themeOrder": "",
|
||||
"timeZoneAuto": true,
|
||||
"timerSound": true,
|
||||
"toastBodyLines": 4,
|
||||
"toastCriticalSticky": true,
|
||||
"toastEnabled": true,
|
||||
"toastMaxVisible": 3,
|
||||
"toastOnAudio": true,
|
||||
"toastOnBattery": true,
|
||||
"toastOnBluetooth": true,
|
||||
"toastOnCamera": true,
|
||||
"toastOnDisplays": true,
|
||||
"toastOnGameMode": true,
|
||||
"toastOnLayout": true,
|
||||
"toastOnPower": true,
|
||||
"toastOnUsb": true,
|
||||
"toastOnWifi": true,
|
||||
"toastShowActions": true,
|
||||
"toastShowBody": true,
|
||||
"toastTimeout": 5,
|
||||
"toastUseAppTimeout": true,
|
||||
"trayHidden": "",
|
||||
"trayIconColor": "original",
|
||||
"trayStyle": "collapsed",
|
||||
"uiScale": 1.05,
|
||||
"units": "metric",
|
||||
"updateBeta": false,
|
||||
"updateCheck": true,
|
||||
"urgentBadge": "bottom-right",
|
||||
"urgentPulse": true,
|
||||
"wallTransAngle": 45,
|
||||
"wallTransBezier": ".54,0,.34,.99",
|
||||
"wallTransCustomPresets": "",
|
||||
"wallTransDuration": 1,
|
||||
"wallTransOrigin": "center",
|
||||
"wallTransPreset": "soft",
|
||||
"wallTransType": "fade",
|
||||
"wallTransWave": "20,20",
|
||||
"wallpaperBentoHero": "medium",
|
||||
"wallpaperBentoPrev": true,
|
||||
"wallpaperPickerKeys": "sides",
|
||||
"wallpaperPickerStyle": "bento",
|
||||
"wallpaperPillsShape": "uniform",
|
||||
"wallpaperStripSides": 2,
|
||||
"wallpaperStripSteps": "normal",
|
||||
"wallpaperTilesAspect": "square",
|
||||
"wallpaperTilesRows": 2,
|
||||
"weekStartMonday": true,
|
||||
"widgetHideFullscreen": true,
|
||||
"widgetLockAll": false,
|
||||
"widgetOnTop": false,
|
||||
"widgetSnap": true,
|
||||
"widgetsEnabled": true,
|
||||
"windowModuleMiddleClose": false,
|
||||
"windowModuleScroll": true,
|
||||
"windowModuleText": "title",
|
||||
"windowModuleWidth": 260,
|
||||
"workspacesByDisplay": true,
|
||||
"workspacesShown": 1,
|
||||
"workspacesStyle": "numbers",
|
||||
"workspacesWheel": false
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
-- written by Lucid Settings > Windows and Input, and rewritten on every change there
|
||||
return {
|
||||
pinned = { border = "rgba(69dcc7ff) rgba(69dcc7ff)", focused = "rgba(69dcc7ff) rgba(43d6cdff) 45deg", size = 2 },
|
||||
options = {
|
||||
["decoration.dim_strength"] = 0.55,
|
||||
["dwindle.split_width_multiplier"] = 1.1,
|
||||
["general.border_size"] = 1,
|
||||
["general.gaps_in"] = 2,
|
||||
},
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
{
|
||||
"nextId": 11,
|
||||
"boardFull": true,
|
||||
"laidScale": 1.05,
|
||||
"preset": "",
|
||||
"saved": [],
|
||||
"last": [],
|
||||
"stash": [],
|
||||
"instances": [
|
||||
{
|
||||
"uid": "w1",
|
||||
"type": "system",
|
||||
"variant": "compact",
|
||||
"wx": 42,
|
||||
"wy": 773,
|
||||
"bw": 300,
|
||||
"bh": 80,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 46,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showCpu": true,
|
||||
"showRam": true,
|
||||
"showDisk": true,
|
||||
"showTemp": false,
|
||||
"showGpu": false,
|
||||
"interval": "2",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "media",
|
||||
"variant": "card",
|
||||
"wx": 2218,
|
||||
"wy": 137,
|
||||
"bw": 288,
|
||||
"bh": 435,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 52,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"artTint": true,
|
||||
"showProgress": true,
|
||||
"scroll": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w3",
|
||||
"type": "weather",
|
||||
"variant": "forecast",
|
||||
"wx": 32,
|
||||
"wy": 137,
|
||||
"bw": 320,
|
||||
"bh": 232,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 49,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tone": "surface"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w4",
|
||||
"type": "kdeconnect",
|
||||
"variant": "remote",
|
||||
"wx": 2211,
|
||||
"wy": 927,
|
||||
"bw": 300,
|
||||
"bh": 214,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 51,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showShare": true,
|
||||
"showRing": true,
|
||||
"showClipboard": true,
|
||||
"showBrowse": true,
|
||||
"deviceId": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w8",
|
||||
"type": "photo",
|
||||
"variant": "frame",
|
||||
"wx": 56,
|
||||
"wy": 443,
|
||||
"bw": 300,
|
||||
"bh": 220,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 48,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"source": "pictures",
|
||||
"every": "60",
|
||||
"shape": "circle",
|
||||
"showDate": false,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w9",
|
||||
"type": "photo",
|
||||
"variant": "shape",
|
||||
"wx": 2253,
|
||||
"wy": 640,
|
||||
"bw": 220,
|
||||
"bh": 220,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 53,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"source": "pictures",
|
||||
"every": "15",
|
||||
"shape": "cookie12",
|
||||
"showDate": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w10",
|
||||
"type": "fetch",
|
||||
"variant": "card",
|
||||
"wx": 26,
|
||||
"wy": 927,
|
||||
"bw": 330,
|
||||
"bh": 344,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 47,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +1,50 @@
|
||||
{ ... }: {
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
hyprLua = import ../../../modules/user/hyprland/lua.nix { inherit lib; };
|
||||
|
||||
monitor-switcher = pkgs.writers.writeBashBin "monitor_switcher" /*bash*/ ''
|
||||
MODE=$1
|
||||
|
||||
case $MODE in
|
||||
"tv")
|
||||
hyprctl eval 'hl.monitor({ output = "DP-3", mode = "1920x1080@60", position = "3840x0", scale = 1 })'
|
||||
hyprctl eval 'hl.monitor({ output = "HDMI-A-1", mode = "3840x2160@60", position = "0x0", scale = 1 })'
|
||||
notify-send "TV mode"
|
||||
;;
|
||||
|
||||
"mirror")
|
||||
hyprctl eval 'hl.monitor({ output = "HDMI-A-1", mode = "3840x2160@60", position = "0x0", scale = 1 })'
|
||||
hyprctl eval 'hl.monitor({ output = "DP-3", mode = "preferred", position = "auto", scale = 1, mirror = "HDMI-A-1" })'
|
||||
notify-send "TV-only mode"
|
||||
;;
|
||||
|
||||
"display"|*)
|
||||
hyprctl eval 'hl.monitor({ output = "HDMI-A-1", disabled = true })'
|
||||
hyprctl eval 'hl.monitor({ output = "DP-3", mode = "3440x1440@165", position = "0x0", scale = 1 })'
|
||||
notify-send "Display mode"
|
||||
;;
|
||||
esac
|
||||
'';
|
||||
in {
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
monitor = [
|
||||
{
|
||||
output = "desc:Huawei Technologies Co. Inc. ZQE-CAA 0xC080F622";
|
||||
mode = "3440x1440@120";
|
||||
position = "1920x0";
|
||||
output = "DP-3";
|
||||
mode = "3440x1440@165";
|
||||
position = "auto-right";
|
||||
scale = 1;
|
||||
cm = "srgb";
|
||||
supports_hdr = -1;
|
||||
}
|
||||
{
|
||||
output = "desc:Samsung Electric Company SAMSUNG 0x01000E00";
|
||||
output = "HDMI-A-1";
|
||||
disabled = true;
|
||||
mode = "1920x1080@60";
|
||||
position = "0x0";
|
||||
scale = 1;
|
||||
}
|
||||
];
|
||||
|
||||
bind = [
|
||||
(hyprLua.bind "XF86Calculator" (hyprLua.exec "ghostty --title=pulsemixer -e pulsemixer"))
|
||||
(hyprLua.bind "SUPER + F1" (hyprLua.exec "${lib.getExe monitor-switcher} tv"))
|
||||
(hyprLua.bind "SUPER + F2" (hyprLua.exec "${lib.getExe monitor-switcher} mirror"))
|
||||
(hyprLua.bind "SUPER + F3" (hyprLua.exec "${lib.getExe monitor-switcher}"))
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
lucid.widgetSeed = ./lucid-widgets-rias.json;
|
||||
lucid.liveDecoder = "cuda";
|
||||
lucid.bindsModule = lib.mkDefault "${config.lucid.upstreamSource}/support/hypr/modules/binds.lua";
|
||||
lucid.luaModules = {
|
||||
"modules/decorations.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/decorations.lua";
|
||||
"modules/gestures.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/gestures.lua";
|
||||
"modules/input.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/input.lua";
|
||||
"modules/layout.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/layout.lua";
|
||||
"modules/layerrules.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/layerrules.lua";
|
||||
"modules/lumen.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/lumen.lua";
|
||||
"modules/misc.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/misc.lua";
|
||||
"modules/plugins.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/plugins.lua";
|
||||
"modules/windowrules.lua" = "${config.lucid.upstreamSource}/support/hypr/modules/windowrules.lua";
|
||||
"modules/monitors.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/monitors.lua"; autoLoad = false; };
|
||||
"modules/glass.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/glass.lua"; autoLoad = false; };
|
||||
"modules/settings.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/settings.lua"; autoLoad = false; };
|
||||
"modules/startup.lua" = { content = "${config.lucid.upstreamSource}/support/hypr/modules/startup.lua"; autoLoad = false; };
|
||||
};
|
||||
}
|
||||
@@ -1,350 +0,0 @@
|
||||
{
|
||||
"nextId": 31,
|
||||
"boardFull": true,
|
||||
"laidScale": 1.1,
|
||||
"preset": "",
|
||||
"saved": [
|
||||
{
|
||||
"id": "user-mvzmbsn0",
|
||||
"name": "Mine",
|
||||
"refW": 3440,
|
||||
"refH": 1440,
|
||||
"refScale": 1.1,
|
||||
"saved": 1793656367208,
|
||||
"cards": [
|
||||
{
|
||||
"uid": "w1",
|
||||
"type": "clock",
|
||||
"variant": "poster",
|
||||
"at": "tr",
|
||||
"x": 3122,
|
||||
"y": 93,
|
||||
"w": 216,
|
||||
"h": 272,
|
||||
"zoom": 1.25,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"hourMode": "auto",
|
||||
"seconds": false,
|
||||
"dot": true,
|
||||
"showDate": true,
|
||||
"shape": "cookie9",
|
||||
"zones": "eu",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "system",
|
||||
"variant": "tiles",
|
||||
"at": "bl",
|
||||
"x": 20,
|
||||
"y": 1057,
|
||||
"w": 280,
|
||||
"h": 220,
|
||||
"zoom": 1.5,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"showCpu": true,
|
||||
"showRam": true,
|
||||
"showDisk": true,
|
||||
"showTemp": true,
|
||||
"showGpu": false,
|
||||
"interval": "5",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w3",
|
||||
"type": "progress",
|
||||
"variant": "dots",
|
||||
"at": "tr",
|
||||
"x": 3007,
|
||||
"y": 483,
|
||||
"w": 300,
|
||||
"h": 212,
|
||||
"zoom": 1.25,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"span": "day",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w4",
|
||||
"type": "network",
|
||||
"variant": "graph",
|
||||
"at": "bl",
|
||||
"x": 20,
|
||||
"y": 836,
|
||||
"w": 308,
|
||||
"h": 186,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"units": "bytes",
|
||||
"showAddress": true,
|
||||
"interval": "1",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w5",
|
||||
"type": "media",
|
||||
"variant": "card",
|
||||
"at": "tl",
|
||||
"x": 20,
|
||||
"y": 93,
|
||||
"w": 288,
|
||||
"h": 435,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"artTint": true,
|
||||
"showProgress": true,
|
||||
"scroll": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w7",
|
||||
"type": "games",
|
||||
"variant": "list",
|
||||
"at": "br",
|
||||
"x": 2925,
|
||||
"y": 958,
|
||||
"w": 300,
|
||||
"h": 280,
|
||||
"zoom": 1.5,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"order": "recent",
|
||||
"gameModeOnLaunch": false,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w8",
|
||||
"type": "kdeconnect",
|
||||
"variant": "remote",
|
||||
"at": "tr",
|
||||
"x": 2713,
|
||||
"y": 93,
|
||||
"w": 300,
|
||||
"h": 214,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"showShare": true,
|
||||
"showRing": true,
|
||||
"showClipboard": true,
|
||||
"showBrowse": true,
|
||||
"deviceId": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w9",
|
||||
"type": "fetch",
|
||||
"variant": "card",
|
||||
"at": "tl",
|
||||
"x": 375,
|
||||
"y": 105,
|
||||
"w": 330,
|
||||
"h": 344,
|
||||
"zoom": 1,
|
||||
"stretch": false,
|
||||
"pinned": false,
|
||||
"opts": {
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"last": [],
|
||||
"stash": [
|
||||
{
|
||||
"uid": "w19",
|
||||
"type": "notes",
|
||||
"variant": "sticky",
|
||||
"wx": 53,
|
||||
"wy": 515,
|
||||
"bw": 244,
|
||||
"bh": 244,
|
||||
"pinned": false,
|
||||
"zoom": 1.33,
|
||||
"zOrder": 66,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tint": "tertiary",
|
||||
"edited": 0,
|
||||
"text": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"uid": "w24",
|
||||
"type": "clock",
|
||||
"variant": "poster",
|
||||
"wx": 3122,
|
||||
"wy": 93,
|
||||
"bw": 216,
|
||||
"bh": 272,
|
||||
"pinned": false,
|
||||
"zoom": 1.25,
|
||||
"zOrder": 80,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"hourMode": "auto",
|
||||
"seconds": false,
|
||||
"dot": true,
|
||||
"showDate": true,
|
||||
"shape": "cookie9",
|
||||
"zones": "eu",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w26",
|
||||
"type": "progress",
|
||||
"variant": "dots",
|
||||
"wx": 3007,
|
||||
"wy": 483,
|
||||
"bw": 300,
|
||||
"bh": 212,
|
||||
"pinned": false,
|
||||
"zoom": 1.25,
|
||||
"zOrder": 82,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"span": "day",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w2",
|
||||
"type": "system",
|
||||
"variant": "tiles",
|
||||
"wx": 20,
|
||||
"wy": 1057,
|
||||
"bw": 280,
|
||||
"bh": 220,
|
||||
"pinned": false,
|
||||
"zoom": 1.5,
|
||||
"zOrder": 81,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showCpu": true,
|
||||
"showRam": true,
|
||||
"showDisk": true,
|
||||
"showTemp": true,
|
||||
"showGpu": false,
|
||||
"interval": "5",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w4",
|
||||
"type": "network",
|
||||
"variant": "graph",
|
||||
"wx": 20,
|
||||
"wy": 836,
|
||||
"bw": 308,
|
||||
"bh": 186,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 88,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"units": "bytes",
|
||||
"showAddress": true,
|
||||
"interval": "1",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w5",
|
||||
"type": "media",
|
||||
"variant": "card",
|
||||
"wx": 20,
|
||||
"wy": 93,
|
||||
"bw": 288,
|
||||
"bh": 435,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 89,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"artTint": true,
|
||||
"showProgress": true,
|
||||
"scroll": true,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w7",
|
||||
"type": "games",
|
||||
"variant": "list",
|
||||
"wx": 2925,
|
||||
"wy": 958,
|
||||
"bw": 300,
|
||||
"bh": 280,
|
||||
"pinned": false,
|
||||
"zoom": 1.5,
|
||||
"zOrder": 85,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"order": "recent",
|
||||
"gameModeOnLaunch": false,
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w8",
|
||||
"type": "kdeconnect",
|
||||
"variant": "remote",
|
||||
"wx": 2713,
|
||||
"wy": 93,
|
||||
"bw": 300,
|
||||
"bh": 214,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 86,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"showShare": true,
|
||||
"showRing": true,
|
||||
"showClipboard": true,
|
||||
"showBrowse": true,
|
||||
"deviceId": "",
|
||||
"tone": "auto"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uid": "w9",
|
||||
"type": "fetch",
|
||||
"variant": "terminal",
|
||||
"wx": 353,
|
||||
"wy": 93,
|
||||
"bw": 380,
|
||||
"bh": 318,
|
||||
"pinned": false,
|
||||
"zoom": 1,
|
||||
"zOrder": 90,
|
||||
"screenName": "",
|
||||
"opts": {
|
||||
"tone": "auto"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -24,10 +24,6 @@
|
||||
hyprland.package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
inputs.hyprland.inputs.hyprland-guiutils.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||
];
|
||||
|
||||
security.wrappers.Hyprland.source = lib.mkForce
|
||||
"${config.programs.hyprland.package}/bin/.Hyprland-wrapped";
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
./user/sops.nix
|
||||
./user/ssh.nix
|
||||
./user/walker.nix
|
||||
./user/wob.nix
|
||||
./user/yazi.nix
|
||||
./user/zsh.nix
|
||||
|
||||
|
||||
@@ -28,23 +28,13 @@ in {
|
||||
optImport ../../host/${hostname}/modules/hyprland.nix ++
|
||||
optImport ../../user/${username}/modules/hyprland.nix;
|
||||
|
||||
options.lucidHyprlandBinds = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
default = [];
|
||||
internal = true;
|
||||
};
|
||||
|
||||
config = {
|
||||
home.packages = with pkgs; [
|
||||
ghostty
|
||||
pamixer
|
||||
clipse
|
||||
cliphist
|
||||
wl-clipboard
|
||||
wl-clip-persist
|
||||
xclip
|
||||
rbw
|
||||
wtype
|
||||
awww
|
||||
wifitui
|
||||
];
|
||||
@@ -114,10 +104,7 @@ in {
|
||||
{ _args = [ "XDG_SCREENSHOTS_DIR" "~/screens" ]; }
|
||||
];
|
||||
|
||||
# Lucid's Rias integration loads support/hypr/modules/gestures.lua,
|
||||
# which defines this workspace swipe (and a four-finger variant).
|
||||
# Avoid registering the same 3-finger gesture twice in Hyprland.
|
||||
gesture = lib.optionals (hostname != "Rias") [
|
||||
gesture = [
|
||||
{
|
||||
fingers = 3;
|
||||
direction = "horizontal";
|
||||
@@ -128,7 +115,6 @@ in {
|
||||
workspace_rule = [
|
||||
{
|
||||
workspace = "w[t1]";
|
||||
gaps_in = 0;
|
||||
gaps_out = 0;
|
||||
}
|
||||
];
|
||||
@@ -164,18 +150,8 @@ in {
|
||||
])
|
||||
];
|
||||
|
||||
bind = lib.mkIf (username != "chest") config.lucidHyprlandBinds;
|
||||
};
|
||||
};
|
||||
|
||||
lucidHyprlandBinds = [
|
||||
bind = [
|
||||
(bind "SUPER + V" (exec "ghostty --title=clipse -e clipse"))
|
||||
(bind "SUPER + X" (exec "qs ipc call -- launcher search '>vault'"))
|
||||
(hyprLua.bind "SUPER + SHIFT + V" (hyprLua.execLauncher "qs ipc call launcher clipboard"))
|
||||
(bind "Print" (exec "qs ipc call screenshot full"))
|
||||
(bind "SUPER + Print" (exec "qs ipc call screenshot full"))
|
||||
(bind "CTRL + Print" (exec "qs ipc call snap region"))
|
||||
(bind "ALT + Print" (exec "qs ipc call snap window"))
|
||||
(bind "SUPER + RETURN" (exec "ghostty"))
|
||||
(bind "SUPER + Q" "hl.dsp.window.close()")
|
||||
(bind "SUPER + M" "hl.dsp.exit()")
|
||||
@@ -228,5 +204,7 @@ in {
|
||||
(bindWithFlags "SUPER + mouse:272" "hl.dsp.window.drag()" { mouse = true; })
|
||||
(bindWithFlags "SUPER + mouse:273" "hl.dsp.window.resize()" { mouse = true; })
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -7,12 +7,6 @@ in rec {
|
||||
|
||||
exec = command: "hl.dsp.exec_cmd(${quote command})";
|
||||
|
||||
# Hyprland 0.57 can leave exclusive layer-shell launchers unfocused when
|
||||
# follow_mouse = 1 and the pointer is already over a regular window.
|
||||
execLauncher = command: ''
|
||||
hl.dsp.exec_cmd(${quote ("hyprctl eval 'hl.config({ input = { follow_mouse = 2 } })' && " + command)})
|
||||
'';
|
||||
|
||||
bind = keys: dispatcher: {
|
||||
_args = [
|
||||
keys
|
||||
|
||||
@@ -17,7 +17,7 @@ let
|
||||
hl.config({ cursor = { zoom_factor = math.max(1, current * 0.9) } })
|
||||
end'';
|
||||
in {
|
||||
lucidHyprlandBinds = [
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
(bind "SUPER + SHIFT + mouse_up" resetZoom)
|
||||
(bind "SUPER + SHIFT + mouse_down" resetZoom)
|
||||
(bind "SUPER + SHIFT + minus" resetZoom)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,129 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var entries: []
|
||||
property bool loading: false
|
||||
property string error: ""
|
||||
property string targetAddress: ""
|
||||
property string pendingPassword: ""
|
||||
property var passwordProcess: null
|
||||
|
||||
signal passwordReady(string password, string targetAddress)
|
||||
|
||||
function refresh() {
|
||||
if (listReader.running)
|
||||
return;
|
||||
|
||||
root.loading = true;
|
||||
root.error = "";
|
||||
listReader.command = ["rbw", "list", "--fields", "id,name,user,folder"];
|
||||
listReader.running = true;
|
||||
}
|
||||
|
||||
function getPassword(id, target) {
|
||||
if (root.passwordProcess)
|
||||
return;
|
||||
|
||||
root.targetAddress = target || "";
|
||||
root.error = "";
|
||||
root.passwordProcess = passwordProcessComponent.createObject(root, { entryId: id });
|
||||
if (!root.passwordProcess) {
|
||||
root.error = "Could not start rbw.";
|
||||
return;
|
||||
}
|
||||
root.passwordProcess.running = true;
|
||||
}
|
||||
|
||||
function typePassword(password, target) {
|
||||
root.pendingPassword = password;
|
||||
root.targetAddress = target || "";
|
||||
typeDelay.restart();
|
||||
}
|
||||
|
||||
Process {
|
||||
id: listReader
|
||||
|
||||
command: []
|
||||
stdout: StdioCollector {
|
||||
id: listOutput
|
||||
}
|
||||
|
||||
onExited: (code) => {
|
||||
root.loading = false;
|
||||
if (code !== 0) {
|
||||
root.error = "Could not read the vault. Unlock it with rbw, then retry.";
|
||||
root.entries = [];
|
||||
return;
|
||||
}
|
||||
|
||||
root.entries = listOutput.text.split(/\r?\n/).filter((line) => line !== "").map((line) => {
|
||||
const fields = line.split("\t");
|
||||
return {
|
||||
id: fields[0] || "",
|
||||
name: fields[1] || "Untitled",
|
||||
user: fields[2] || "",
|
||||
folder: fields[3] || ""
|
||||
};
|
||||
}).filter((entry) => entry.id !== "");
|
||||
root.error = "";
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: passwordProcessComponent
|
||||
|
||||
Process {
|
||||
id: passwordJob
|
||||
|
||||
property string entryId: ""
|
||||
command: ["rbw", "get", entryId]
|
||||
stdout: StdioCollector {
|
||||
id: passwordOutput
|
||||
}
|
||||
|
||||
onExited: (code) => {
|
||||
if (root.passwordProcess === passwordJob)
|
||||
root.passwordProcess = null;
|
||||
if (code !== 0) {
|
||||
root.error = "Could not read that password.";
|
||||
} else {
|
||||
root.passwordReady(passwordOutput.text.replace(/\r?\n$/, ""), root.targetAddress);
|
||||
}
|
||||
passwordJob.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: typer
|
||||
|
||||
command: ["wtype", "-"]
|
||||
stdinEnabled: true
|
||||
onStarted: {
|
||||
write(root.pendingPassword);
|
||||
stdinEnabled = false;
|
||||
root.pendingPassword = "";
|
||||
root.targetAddress = "";
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: typeDelay
|
||||
|
||||
interval: 260
|
||||
onTriggered: {
|
||||
if (root.targetAddress !== "")
|
||||
Hyprland.dispatch("focuswindow address:" + root.targetAddress);
|
||||
|
||||
typer.running = false;
|
||||
typer.stdinEnabled = true;
|
||||
typer.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,617 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import qs
|
||||
|
||||
Variants {
|
||||
id: variants
|
||||
|
||||
model: Quickshell.screens
|
||||
|
||||
Scope {
|
||||
id: unit
|
||||
|
||||
required property var modelData
|
||||
|
||||
PanelWindow {
|
||||
id: layer
|
||||
|
||||
PaletteFade {
|
||||
active: layer.visible
|
||||
}
|
||||
|
||||
readonly property bool suppressed: Prefs.widgetHideFullscreen && layer.windowUp
|
||||
readonly property var workspaceMonitor: Hyprland.monitorFor(layer.screen)
|
||||
readonly property var activeWorkspace: workspaceMonitor ? workspaceMonitor.activeWorkspace : null
|
||||
readonly property bool windowUp: {
|
||||
const workspace = layer.activeWorkspace;
|
||||
if (!workspace || !workspace.toplevels)
|
||||
return false;
|
||||
|
||||
return workspace.toplevels.values.some((t) => {
|
||||
const o = t.lastIpcObject || {};
|
||||
if (o.mapped === false || o.hidden === true)
|
||||
return false;
|
||||
|
||||
if (o.size && (o.size[0] <= 0 || o.size[1] <= 0))
|
||||
return false;
|
||||
|
||||
return (o.fullscreen || 0) > 0 || o.floating === false;
|
||||
});
|
||||
}
|
||||
|
||||
screen: unit.modelData
|
||||
visible: Prefs.loaded && Widgets.loaded && (deck.panelLive || ((Prefs.widgetsEnabled || deck.stowing) && Widgets.count > 0 && !layer.suppressed))
|
||||
color: "transparent"
|
||||
// reserves nothing and refuses to be shrunk into the bar and dock's strips,
|
||||
// so a card can be dragged to a true screen edge and sit under the dock.
|
||||
// setting exclusiveZone at all would silently undo this
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
// the widget panel lifts the whole board over the windows until it has slid away
|
||||
WlrLayershell.layer: deck.panelLive ? WlrLayer.Overlay : (Prefs.widgetOnTop ? WlrLayer.Top : WlrLayer.Bottom)
|
||||
WlrLayershell.keyboardFocus: deck.panelUp ? WlrKeyboardFocus.Exclusive : (deck.wantsKeyboard ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None)
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// lastIpcObject only moves when something asks it to, so nudge it on
|
||||
// the events that can change whether a tiled or fullscreen window exists.
|
||||
Connections {
|
||||
function onRawEvent(event) {
|
||||
if (event.name === "fullscreen" || event.name === "activewindowv2" || event.name === "closewindow" || event.name === "openwindow" || event.name === "workspace" || event.name === "focusedmon" || event.name === "movewindow" || event.name === "changefloatingmode")
|
||||
windowPoll.restart();
|
||||
|
||||
}
|
||||
|
||||
enabled: Prefs.widgetHideFullscreen
|
||||
target: Hyprland
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: stowEnd
|
||||
|
||||
onTriggered: deck.stowing = false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: windowPoll
|
||||
|
||||
interval: 120
|
||||
onTriggered: Hyprland.refreshToplevels()
|
||||
}
|
||||
|
||||
Item {
|
||||
id: deck
|
||||
|
||||
// bumped whenever the repeater's children change, so frameAt() bindings re-run
|
||||
property int rev: 0
|
||||
property Item dragFrame: null
|
||||
|
||||
readonly property string screenName: layer.screen ? layer.screen.name : ""
|
||||
readonly property bool isPrimary: layer.screen !== null && layer.screen === Monitors.mainScreen
|
||||
// the widget panel is open on this screen, and still on its way out
|
||||
readonly property bool panelUp: Widgets.panelScreen !== "" && Widgets.panelScreen === deck.screenName
|
||||
readonly property bool panelLive: deck.panelUp || panel.shown
|
||||
// the cards are out: on, or the panel is up to place them
|
||||
readonly property bool widgetsOn: Prefs.widgetsEnabled || deck.panelLive
|
||||
// put away a moment ago: the board stays up while the cards go
|
||||
property bool stowing: false
|
||||
readonly property int stowStep: Math.round(30 * Theme.motionScale)
|
||||
readonly property int wakeStep: Math.round(55 * Theme.motionScale)
|
||||
|
||||
// the order the cards go and come back in: down the screen, then across
|
||||
function orderOf(f) {
|
||||
var n = 0;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var o = rep.itemAt(i);
|
||||
if (o && o !== f && o.onThisScreen && (o.y < f.y - 1 || (Math.abs(o.y - f.y) <= 1 && o.x < f.x)))
|
||||
n++;
|
||||
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
onWidgetsOnChanged: {
|
||||
if (deck.widgetsOn) {
|
||||
stowEnd.stop();
|
||||
deck.stowing = false;
|
||||
} else {
|
||||
stowEnd.interval = rep.count * deck.stowStep + Theme.durMedium + 80;
|
||||
deck.stowing = true;
|
||||
stowEnd.restart();
|
||||
}
|
||||
}
|
||||
readonly property int frameCount: rep.count
|
||||
readonly property bool editing: Widgets.editUid !== "" && deck.editFrame !== null
|
||||
// only ask for keys at all once something you can type into is placed;
|
||||
// on-demand hands focus back the moment a window is clicked, which
|
||||
// exclusive would not
|
||||
readonly property bool wantsKeyboard: {
|
||||
for (var i = 0; i < Widgets.model.count; i++) {
|
||||
var e = Widgets.model.get(i);
|
||||
if (!e.closing && (e.wtype === "notes" || e.wtype === "todo" || e.wtype === "github"))
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
readonly property bool grabbing: Widgets.dragUid !== "" || Widgets.menuUid !== "" || deck.editing
|
||||
readonly property Item menuFrame: {
|
||||
var _ = deck.rev;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === Widgets.menuUid && f.visible)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
readonly property Item editFrame: {
|
||||
var _ = deck.rev;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === Widgets.editUid && f.visible)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function frameAt(i) {
|
||||
var _ = deck.rev;
|
||||
return (i >= 0 && i < rep.count) ? rep.itemAt(i) : null;
|
||||
}
|
||||
|
||||
function frameFor(uid) {
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (f && f.uid === uid)
|
||||
return f;
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// a board point over the open panel, where a dragged card is let go of
|
||||
function binHit(px, py) {
|
||||
return deck.panelUp && panel.sheetHit(px, py);
|
||||
}
|
||||
|
||||
// the topmost card under a board point, for the menu overlay: it
|
||||
// covers the screen while it is up, so the cards never see the press
|
||||
function frameUnder(px, py) {
|
||||
var best = null;
|
||||
for (var i = 0; i < rep.count; i++) {
|
||||
var f = rep.itemAt(i);
|
||||
if (!f || !f.visible || f.closing)
|
||||
continue;
|
||||
|
||||
if (px < f.x || px > f.x + f.width || py < f.y || py > f.y + f.height)
|
||||
continue;
|
||||
|
||||
if (best === null || f.z >= best.z)
|
||||
best = f;
|
||||
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: {
|
||||
var busy = Widgets.editUid !== "" || Widgets.menuUid !== "" || Object.keys(Widgets.selection).length > 0;
|
||||
Widgets.editUid = "";
|
||||
Widgets.menuUid = "";
|
||||
Widgets.clearSelection();
|
||||
if (!busy && deck.panelUp)
|
||||
Widgets.closePanel();
|
||||
|
||||
}
|
||||
onWidthChanged: deck.report()
|
||||
onHeightChanged: deck.report()
|
||||
Component.onCompleted: deck.report()
|
||||
|
||||
function report() {
|
||||
if (deck.isPrimary && deck.width > 0) {
|
||||
Widgets.canvasW = deck.width;
|
||||
Widgets.canvasH = deck.height;
|
||||
}
|
||||
}
|
||||
|
||||
// the dim behind the cards while the widget panel is open, and the way out of it
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.alpha(Theme.cScrim, Theme.blurAmount > 0 ? 0.3 : 0.5)
|
||||
opacity: panel.reveal
|
||||
visible: opacity > 0.01
|
||||
|
||||
WheelHandler {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
||||
enabled: deck.panelUp
|
||||
onWheel: (event) => {
|
||||
return event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// click-through everywhere except the cards, so this only fires for the
|
||||
// open menu, a note being typed in, or the widget panel
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: deck.grabbing || deck.panelUp
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: {
|
||||
var busy = Widgets.menuUid !== "" || Widgets.editUid !== "";
|
||||
Widgets.menuUid = "";
|
||||
Widgets.editUid = "";
|
||||
if (!busy && deck.panelUp) {
|
||||
Widgets.clearSelection();
|
||||
Widgets.closePanel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// the cards stack among themselves, under the guides and the panel
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Repeater {
|
||||
id: rep
|
||||
|
||||
model: Widgets.model
|
||||
onItemAdded: deck.rev++
|
||||
onItemRemoved: deck.rev++
|
||||
|
||||
WidgetFrame {
|
||||
board: deck
|
||||
updatesEnabled: layer.visible
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: deck.height
|
||||
x: deck.dragFrame ? deck.dragFrame.guideX : -1
|
||||
color: Theme.accent
|
||||
opacity: (deck.dragFrame && deck.dragFrame.guideX >= 0) ? 0.65 : 0
|
||||
visible: opacity > 0.01
|
||||
z: 900
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.durQuick
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
width: deck.width
|
||||
y: deck.dragFrame ? deck.dragFrame.guideY : -1
|
||||
color: Theme.accent
|
||||
opacity: (deck.dragFrame && deck.dragFrame.guideY >= 0) ? 0.65 : 0
|
||||
visible: opacity > 0.01
|
||||
z: 900
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.durQuick
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WidgetPanel {
|
||||
id: panel
|
||||
|
||||
anchors.fill: parent
|
||||
z: 1000
|
||||
board: deck
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
x: 0
|
||||
y: 0
|
||||
width: (deck.grabbing || deck.panelUp) ? layer.width : 0
|
||||
height: (deck.grabbing || deck.panelUp) ? layer.height : 0
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(0)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(1)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(2)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(3)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(4)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(5)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(6)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(7)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(8)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(9)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(10)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(11)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(12)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(13)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(14)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(15)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(16)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(17)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(18)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
frame: deck.frameAt(19)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the open panel frosts the whole screen behind its dim, cards and sheet alike
|
||||
BackgroundEffect.blurRegion: Theme.blurAmount > 0 ? (deck.panelUp ? wholeBlur : widgetBlur) : null
|
||||
|
||||
Region {
|
||||
id: wholeBlur
|
||||
|
||||
width: layer.width
|
||||
height: layer.height
|
||||
}
|
||||
|
||||
Region {
|
||||
id: widgetBlur
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(0)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(1)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(2)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(3)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(4)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(5)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(6)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(7)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(8)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(9)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(10)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(11)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(12)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(13)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(14)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(15)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(16)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(17)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(18)
|
||||
}
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
frame: deck.frameAt(19)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// an options panel on the widget layer would sit under every window, so it
|
||||
// gets an overlay of its own that only exists while the panel is up
|
||||
PanelWindow {
|
||||
id: menuLayer
|
||||
|
||||
// the frame the panel belongs to, held past the close so the fade-out
|
||||
// keeps its geometry and its contents
|
||||
property Item held: null
|
||||
|
||||
readonly property Item target: deck.menuFrame
|
||||
|
||||
screen: unit.modelData
|
||||
// up the moment the menu opens, so the fade-in plays inside a mapped
|
||||
// surface, and stays up until the fade-out finishes
|
||||
visible: menuLayer.target !== null || menu.visible
|
||||
color: "transparent"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: menuLayer.target !== null ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
onTargetChanged: if (menuLayer.target)
|
||||
menuLayer.held = menuLayer.target
|
||||
onVisibleChanged: if (!menuLayer.visible)
|
||||
menuLayer.held = null
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Keys.onEscapePressed: Widgets.menuUid = ""
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onPressed: (mouse) => {
|
||||
var f = mouse.button === Qt.RightButton ? deck.frameUnder(mouse.x, mouse.y) : null;
|
||||
var was = Widgets.menuUid;
|
||||
Widgets.menuUid = "";
|
||||
// a right press on another card carries the panel over to it,
|
||||
// the way it did when the panel still sat on the widget layer
|
||||
if (f && f.uid !== was)
|
||||
f.toggleMenu(mouse.x - f.x, mouse.y - f.y);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
WidgetMenu {
|
||||
id: menu
|
||||
|
||||
frame: menuLayer.held
|
||||
fieldW: menuLayer.width
|
||||
fieldH: menuLayer.height
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// no input once the panel starts closing, so the fade-out is never a
|
||||
// dead region over the screen
|
||||
mask: Region {
|
||||
width: menuLayer.target ? menuLayer.width : 0
|
||||
height: menuLayer.target ? menuLayer.height : 0
|
||||
}
|
||||
|
||||
BackgroundEffect.blurRegion: Theme.blurAmount > 0 ? menuBlur : null
|
||||
|
||||
Region {
|
||||
id: menuBlur
|
||||
|
||||
WidgetRegion {
|
||||
blur: true
|
||||
part: "menu"
|
||||
panel: menu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
|
||||
Column {
|
||||
id: page
|
||||
|
||||
// the map is drawn over this
|
||||
property string wallpaper: ""
|
||||
|
||||
spacing: Theme.dp(26)
|
||||
// the header switch turns the whole layer off, so the page has nothing left to set
|
||||
enabled: Prefs.widgetsEnabled
|
||||
opacity: Prefs.widgetsEnabled ? 1 : 0.38
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.durShort
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
FileView {
|
||||
path: Quickshell.env("HOME") + "/.cache/current_wallpaper"
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
watchChanges: true
|
||||
onFileChanged: reload()
|
||||
onLoaded: page.wallpaper = text().trim()
|
||||
}
|
||||
|
||||
WidgetsMap {
|
||||
width: parent.width
|
||||
wallpaper: page.wallpaper
|
||||
}
|
||||
|
||||
SettingCard {
|
||||
title: "ON THE DESKTOP"
|
||||
|
||||
SettingRow {
|
||||
title: "Widget panel"
|
||||
description: "Every widget there is, to drag out onto the desktop or click in, the ones already out there, and layouts to start from or save. It opens from the desktop's right-click menu too."
|
||||
showDivider: false
|
||||
|
||||
M3Button {
|
||||
text: "Open the panel"
|
||||
variant: "tonal"
|
||||
iconPath: "widgets"
|
||||
onClicked: {
|
||||
Prefs.settingsHideRequested();
|
||||
Widgets.openPanel(Monitors.mainScreen ? Monitors.mainScreen.name : "");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
title: "Edit mode"
|
||||
description: "The desktop shades over and everything on it can be moved: widgets, the bar's modules and the dock. Right-click anything for its options."
|
||||
showDivider: false
|
||||
|
||||
M3Button {
|
||||
text: "Edit the desktop"
|
||||
variant: "tonal"
|
||||
iconPath: "edit"
|
||||
onClicked: Edit.enterFromSettings("widgets", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingCard {
|
||||
title: "BEHAVIOUR"
|
||||
|
||||
SettingRow {
|
||||
title: "Snap while dragging"
|
||||
resetKey: "widgetSnap"
|
||||
description: "Widgets catch on the screen's edges and centre lines, and line up with each other, so a hand-placed arrangement still looks deliberate."
|
||||
|
||||
M3Switch {
|
||||
checked: Prefs.widgetSnap
|
||||
onToggled: (v) => {
|
||||
return Prefs.widgetSnap = v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
title: "Pin everything"
|
||||
resetKey: "widgetLockAll"
|
||||
description: "Locks every widget where it stands, including the ones that are not individually pinned. Useful once the layout is settled."
|
||||
|
||||
M3Switch {
|
||||
checked: Prefs.widgetLockAll
|
||||
onToggled: (v) => {
|
||||
return Prefs.widgetLockAll = v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
title: "Keep above windows"
|
||||
resetKey: "widgetOnTop"
|
||||
description: "Off, widgets live on the desktop and windows cover them. On, they float over everything - handy for a clock or a timer you always want in sight."
|
||||
|
||||
M3Switch {
|
||||
checked: Prefs.widgetOnTop
|
||||
onToggled: (v) => {
|
||||
return Prefs.widgetOnTop = v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingRow {
|
||||
title: "Hide when tiled or fullscreen windows are open"
|
||||
resetKey: "widgetHideFullscreen"
|
||||
description: "Widgets step aside while a tiled or fullscreen window is on the workspace; they return on empty workspaces and stay over floating windows."
|
||||
showDivider: false
|
||||
|
||||
M3Switch {
|
||||
checked: Prefs.widgetHideFullscreen
|
||||
onToggled: (v) => {
|
||||
return Prefs.widgetHideFullscreen = v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,743 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
staticBindKeys = map (entry: builtins.elemAt entry._args 0) config.wayland.windowManager.hyprland.settings.bind;
|
||||
nonRiasBinds = pkgs.runCommand "lucid-nix-binds.lua" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
python3 - ${source}/support/hypr/modules/binds.lua "$out" ${lib.escapeShellArg (builtins.toJSON staticBindKeys)} <<'PY'
|
||||
import json
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
source = pathlib.Path(sys.argv[1]).read_text()
|
||||
keys = json.loads(sys.argv[3])
|
||||
lua_keys = "{ " + ", ".join(json.dumps(key, ensure_ascii=True) for key in keys) + " }"
|
||||
guard = r"""
|
||||
local function normalize_nix_bind(keys)
|
||||
local mods, key = {}, ""
|
||||
for part in tostring(keys or ""):gmatch("[^+]+") do
|
||||
local token = (part:match("^%s*(.-)%s*$") or ""):upper()
|
||||
if token == "CONTROL" then token = "CTRL" end
|
||||
if token == "WIN" or token == "LOGO" or token == "MOD4" then token = "SUPER" end
|
||||
if token == "SUPER" or token == "CTRL" or token == "ALT" or token == "SHIFT" then
|
||||
mods[token] = true
|
||||
else
|
||||
key = token
|
||||
end
|
||||
end
|
||||
local out = {}
|
||||
for _, mod in ipairs({"ALT", "CTRL", "SHIFT", "SUPER"}) do
|
||||
if mods[mod] then out[#out + 1] = mod end
|
||||
end
|
||||
return table.concat(out, "+") .. "+" .. key
|
||||
end
|
||||
local nix_binds = {}
|
||||
for _, key in ipairs(NIX_BIND_KEYS) do nix_binds[normalize_nix_bind(key)] = true end
|
||||
""".replace("NIX_BIND_KEYS", lua_keys)
|
||||
marker = "-- several dispatchers on one key, in order"
|
||||
binding = ' if type(keys) ~= "string" or keys == "" then error("no keys", 0) end'
|
||||
fallback = ' for _, entry in ipairs(EMERGENCY) do apply(entry) end'
|
||||
fallback_source = ' status.source = "emergency"'
|
||||
fallback_text = 'Emergency binds are active - SUPER+S opens Settings.'
|
||||
for needle in (marker, binding, fallback, fallback_source, fallback_text):
|
||||
if source.count(needle) != 1:
|
||||
raise SystemExit("Lucid bind loader changed; refusing to skip conflict protection")
|
||||
source = source.replace(marker, guard + "\n" + marker, 1)
|
||||
source = source.replace(binding, binding + '\n if nix_binds[normalize_nix_bind(keys)] then error("Already bound by Nix: " .. keys, 0) end', 1)
|
||||
source = source.replace(fallback, ' -- Nix supplies the fallback keys on these hosts.', 1)
|
||||
source = source.replace(fallback_source, ' status.source = "nix"', 1)
|
||||
source = source.replace(fallback_text, 'Nix bindings remain active.', 1)
|
||||
pathlib.Path(sys.argv[2]).write_text(source)
|
||||
PY
|
||||
'';
|
||||
vaultwardenLauncherPatch = pkgs.writeText "lucid-vaultwarden-launcher.py" ''
|
||||
import pathlib
|
||||
import sys
|
||||
|
||||
face_path, dock_path = map(pathlib.Path, sys.argv[1:])
|
||||
face = face_path.read_text()
|
||||
dock = dock_path.read_text()
|
||||
|
||||
def replace_once(source, old, new, label):
|
||||
count = source.count(old)
|
||||
if count != 1:
|
||||
raise SystemExit(f"Lucid {label} anchor changed (found {count}); refusing an unsafe launcher patch")
|
||||
return source.replace(old, new, 1)
|
||||
|
||||
face = replace_once(face,
|
||||
' "clipboard": "Search clipboard history",\n',
|
||||
' "clipboard": "Search clipboard history",\n "vaultwarden": "Search Vaultwarden",\n', "launcher placeholder")
|
||||
face = replace_once(face,
|
||||
' "clipboard": "content_paste",\n',
|
||||
' "clipboard": "content_paste",\n "vaultwarden": "key",\n', "launcher icon")
|
||||
face = replace_once(face,
|
||||
' "icon": "content_paste"\n }, {\n "key": "wallpaper",',
|
||||
' "icon": "content_paste"\n }, {\n "key": "vaultwarden",\n "label": "Vaultwarden",\n "icon": "key"\n }, {\n "key": "wallpaper",', "launcher mode")
|
||||
face = replace_once(face,
|
||||
' emptyLabel: {\n',
|
||||
' emptyLabel: {\n if (face.displayMode === "vaultwarden")\n return "No matching vault entries";\n', "launcher empty label")
|
||||
face = replace_once(face,
|
||||
' emptyHint: {\n',
|
||||
' emptyHint: {\n if (face.displayMode === "vaultwarden")\n return "Unlock the vault with rbw, then search by name, username, or folder.";\n', "launcher empty hint")
|
||||
|
||||
dock = replace_once(dock,
|
||||
' if (q.indexOf(">clip") === 0)\n return "clipboard";\n',
|
||||
' if (q.indexOf(">clip") === 0)\n return "clipboard";\n\n if (q.indexOf(">pass") === 0)\n return "vaultwarden";\n', "launcher prefix")
|
||||
dock = replace_once(dock,
|
||||
' if (mode === "clipboard")\n return ">clip";\n',
|
||||
' if (mode === "clipboard")\n return ">clip ";\n\n if (mode === "vaultwarden")\n return ">pass ";\n', "launcher mode prefix")
|
||||
dock = replace_once(dock,
|
||||
' if (mode === "wallpaper")\n return ">wallpaper";\n',
|
||||
' if (mode === "wallpaper")\n return ">wallpaper ";\n', "wallpaper prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' if (mode === "theme")\n return ">theme";\n',
|
||||
' if (mode === "theme")\n return ">theme ";\n', "theme prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' if (mode === "live")\n return ">live";\n',
|
||||
' if (mode === "live")\n return ">live ";\n', "live prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' if (mode === "commands")\n return ">";\n',
|
||||
' if (mode === "commands")\n return "> ";\n', "command prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' if (text === ">wallpaper" && !exact && Prefs.liveWallpapers && LiveWall.isVideo(dockWindow.appliedWallpaper))\n text = ">live";\n',
|
||||
' if (text.trim() === ">wallpaper" && !exact && Prefs.liveWallpapers && LiveWall.isVideo(dockWindow.appliedWallpaper))\n text = ">live ";\n', "wallpaper live prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' dockWindow.setSearchText(">wallpaper");\n } else if (id === "theme") {\n dockWindow.setSearchText(">theme");\n',
|
||||
' dockWindow.setSearchText(">wallpaper ");\n } else if (id === "theme") {\n dockWindow.setSearchText(">theme ");\n', "menu prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' } else if (id === "clipboard") {\n dockWindow.setSearchText(">clip");\n',
|
||||
' } else if (id === "clipboard") {\n dockWindow.setSearchText(">clip ");\n', "clipboard prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' dockWindow.setSearchText(">wallpaper", true);',
|
||||
' dockWindow.setSearchText(">wallpaper ", true);', "wallpaper picker prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
'onPickerKindRequested: (kind) => dockWindow.setSearchText(kind === "live" ? ">live" : ">wallpaper", true)',
|
||||
'onPickerKindRequested: (kind) => dockWindow.setSearchText(kind === "live" ? ">live " : ">wallpaper ", true)', "picker tab prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' dockWindow.openLauncher(">wallpaper");\n }\n\n function live(): void {\n dockWindow.openLauncher(">live");',
|
||||
' dockWindow.openLauncher(">wallpaper ");\n }\n\n function live(): void {\n dockWindow.openLauncher(">live ");', "wallpaper ipc prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
'onBackRequested: dockWindow.setSearchText(dockWindow.mode === "commands" ? "" : ">")',
|
||||
'onBackRequested: dockWindow.setSearchText(dockWindow.mode === "commands" ? "" : "> ")', "back prefix spacing")
|
||||
dock = replace_once(dock,
|
||||
' if (dockWindow.mode === "clipboard")\n Clip.refresh();\n',
|
||||
' if (dockWindow.mode === "clipboard")\n Clip.refresh();\n if (dockWindow.mode === "vaultwarden")\n Vaultwarden.refresh();\n', "launcher mode activation")
|
||||
dock = replace_once(dock,
|
||||
' readonly property var allCommands: [{',
|
||||
""" Connections {
|
||||
target: Vaultwarden
|
||||
|
||||
function onEntriesChanged() { dockWindow.rebuildResults(); }
|
||||
function onLoadingChanged() { dockWindow.rebuildResults(); }
|
||||
function onErrorChanged() { dockWindow.rebuildResults(); }
|
||||
function onPasswordReady(password, targetAddress) {
|
||||
dockWindow.menuOpen = false;
|
||||
Vaultwarden.typePassword(password, targetAddress);
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var allCommands: [{""", "vaultwarden updates")
|
||||
dock = replace_once(dock,
|
||||
' function rebuildResults() {',
|
||||
""" function vaultRows(query) {
|
||||
if (Vaultwarden.loading)
|
||||
return [dockWindow.headerRow("Loading Vaultwarden…")];
|
||||
if (Vaultwarden.error !== "")
|
||||
return [dockWindow.headerRow(Vaultwarden.error)];
|
||||
|
||||
var rows = [];
|
||||
var entries = Vaultwarden.entries || [];
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i];
|
||||
var haystack = (entry.name + " " + entry.user + " " + entry.folder).toLowerCase();
|
||||
if (query !== "" && haystack.indexOf(query) === -1)
|
||||
continue;
|
||||
var detail = [entry.user, entry.folder].filter((part) => part !== "").join(" · ");
|
||||
rows.push(dockWindow.makeRow("vaultwarden", "vault-" + entry.id, entry.name, detail, {
|
||||
"glyph": "key",
|
||||
"payload": entry.id
|
||||
}));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
function rebuildResults() {""", "vaultwarden search")
|
||||
dock = replace_once(dock,
|
||||
' } else if (mode === "clipboard") {\n var ents = Clip.entries;',
|
||||
' } else if (mode === "vaultwarden") {\n rows = dockWindow.vaultRows(q);\n } else if (mode === "clipboard") {\n var ents = Clip.entries;', "vaultwarden results")
|
||||
dock = replace_once(dock,
|
||||
' } else if (row.kind === "clip") {\n Clip.copy(row.payload);\n dockWindow.menuOpen = false;\n',
|
||||
' } else if (row.kind === "clip") {\n Clip.copy(row.payload);\n dockWindow.menuOpen = false;\n } else if (row.kind === "vaultwarden") {\n Vaultwarden.getPassword(row.payload, Hyprland.activeToplevel ? Hyprland.activeToplevel.address : "");\n', "vaultwarden selection")
|
||||
|
||||
face_path.write_text(face)
|
||||
dock_path.write_text(dock)
|
||||
'';
|
||||
baseSource = pkgs.applyPatches {
|
||||
src = pkgs.fetchgit {
|
||||
url = "https://github.com/Sn3akyy1/lucid.git";
|
||||
rev = "bdd7f79da18efd837f79dcecbe5d2ec178abc10c";
|
||||
hash = "sha256-b6qylKjCGYy2Yo64i1w9g4oGrYgcXf502jY9qNNWhSI=";
|
||||
sparseCheckout = [
|
||||
"/*"
|
||||
"!/wallpapers/*"
|
||||
"!/assets/*.webp"
|
||||
];
|
||||
nonConeMode = true;
|
||||
};
|
||||
patches = [
|
||||
(pkgs.writeText "lucid-scratchpad-target.patch" ''
|
||||
diff --git a/support/hypr/modules/specials.lua b/support/hypr/modules/specials.lua
|
||||
--- a/support/hypr/modules/specials.lua
|
||||
+++ b/support/hypr/modules/specials.lua
|
||||
@@ -176,8 +176,11 @@
|
||||
function M.scratchpad()
|
||||
return function()
|
||||
if space(config(), "special").enabled == false then return end
|
||||
- local shown = hl.get_active_special_workspace()
|
||||
- local name = shown and (shown.name:gsub("^special:", "")) or "special"
|
||||
- hl.dispatch(hl.dsp.workspace.toggle_special(name))
|
||||
+ local shown = hl.get_active_special_workspace()
|
||||
+ if shown and shown.name == "special:special" then
|
||||
+ hl.dispatch(hl.dsp.workspace.toggle_special("special"))
|
||||
+ else
|
||||
+ hl.dispatch(hl.dsp.focus({ workspace = "special:special" }))
|
||||
+ end
|
||||
end
|
||||
end
|
||||
'')
|
||||
(pkgs.writeText "lucid-nonpersistent-workspaces.patch" ''
|
||||
diff --git a/support/hypr/modules/gestures.lua b/support/hypr/modules/gestures.lua
|
||||
--- a/support/hypr/modules/gestures.lua
|
||||
+++ b/support/hypr/modules/gestures.lua
|
||||
@@ -6,9 +6,5 @@
|
||||
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
|
||||
hl.gesture({ fingers = 4, direction = "horizontal", action = "workspace" })
|
||||
|
||||
--- Pre-create workspaces 1-5 so gesture swipes can always live-track to them
|
||||
-for i = 1, 6 do
|
||||
- hl.workspace_rule({ workspace = tostring(i), persistent = true })
|
||||
-end
|
||||
|
||||
-- local function minimize_all()
|
||||
'')
|
||||
(pkgs.writeText "lucid-lair-search.patch" ''
|
||||
--- a/luciddocks/Dock.qml
|
||||
+++ b/luciddocks/Dock.qml
|
||||
@@ -683,4 +683,8 @@
|
||||
readonly property var searchEngines: ({
|
||||
+ "lair": {
|
||||
+ "name": "Lair",
|
||||
+ "url": "https://s.lair.moe/web?s="
|
||||
+ },
|
||||
"duckduckgo": {
|
||||
"name": "DuckDuckGo",
|
||||
"url": "https://duckduckgo.com/?q="
|
||||
--- a/lucidprefs/LauncherPage.qml
|
||||
+++ b/lucidprefs/LauncherPage.qml
|
||||
@@ -386,5 +386,8 @@
|
||||
current: Prefs.launcherSearchEngine
|
||||
options: [{
|
||||
+ "key": "lair",
|
||||
+ "label": "Lair"
|
||||
+ }, {
|
||||
"key": "duckduckgo",
|
||||
"label": "DuckDuckGo"
|
||||
}, {
|
||||
'')
|
||||
(pkgs.writeText "lucid-shot-special-workspace-name.patch" ''
|
||||
diff --git a/lucidshot/ShotOverlay.qml b/lucidshot/ShotOverlay.qml
|
||||
--- a/lucidshot/ShotOverlay.qml
|
||||
+++ b/lucidshot/ShotOverlay.qml
|
||||
@@ -228,1 +228,1 @@
|
||||
- const ws = mon.activeWorkspace ? mon.activeWorkspace.id : null;
|
||||
+ const ws = mon.activeWorkspace ? mon.activeWorkspace.name : null;
|
||||
@@ -229,1 +229,1 @@
|
||||
- const special = mon.specialWorkspace ? mon.specialWorkspace.id : 0;
|
||||
+ const special = mon.specialWorkspace ? mon.specialWorkspace.name : "";
|
||||
@@ -234,1 +234,1 @@
|
||||
- return c.workspace.id === ws || (special !== 0 && c.workspace.id === special) || (c.pinned && c.monitor === mon.id);
|
||||
+ return c.workspace.name === ws || (special && c.workspace.name === special) || (c.pinned && c.monitor === mon.id);
|
||||
@@ -246,1 +246,1 @@
|
||||
- "special": special !== 0 && c.workspace.id === special,
|
||||
+ "special": !!special && c.workspace.name === special,
|
||||
'')
|
||||
];
|
||||
};
|
||||
source = pkgs.runCommand "lucid-vaultwarden-launcher-source" {
|
||||
nativeBuildInputs = [ pkgs.python3 ];
|
||||
} ''
|
||||
cp -r ${baseSource} "$out"
|
||||
chmod -R u+w "$out"
|
||||
python3 ${vaultwardenLauncherPatch} "$out/luciddocks/LauncherFace.qml" "$out/luciddocks/Dock.qml"
|
||||
'';
|
||||
wallpaperDir = "${config.home.homeDirectory}/Wallpapers";
|
||||
wallpaperScript = "${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh";
|
||||
defaultWallpaper = "${wallpaperDir}/bluehour.jpg";
|
||||
hyprLua = import ../hyprland/lua.nix { inherit lib; };
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
paletteFile = pkgs.runCommand "lucid-stylix-palette.json" {
|
||||
nativeBuildInputs = [ pkgs.python3 ];
|
||||
} ''
|
||||
python3 - "$out" ${lib.escapeShellArg config.stylix.base16Scheme} \
|
||||
${lib.escapeShellArg "${source}/support/lucid"} \
|
||||
${lib.escapeShellArg config.stylix.accent} <<'PY'
|
||||
import json
|
||||
import runpy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
output, scheme_path, scripts, accent_key = sys.argv[1:]
|
||||
sys.path.insert(0, scripts)
|
||||
from lucid_palette import at_tone
|
||||
importer = runpy.run_path(str(Path(scripts) / "add-theme.py"))
|
||||
scheme = importer["parse_base16"](scheme_path, Path(scheme_path).read_text())
|
||||
if scheme is None:
|
||||
raise SystemExit("Stylix scheme is not valid Base16")
|
||||
bg, fg, accents, hints = importer["to_palette"](scheme)
|
||||
accent_key = accent_key.lower()
|
||||
hints["primary"] = scheme["colors"][accent_key]
|
||||
hints["secondary"] = scheme["colors"]["base0d" if accent_key != "base0d" else "base0e"]
|
||||
hints["tertiary"] = scheme["colors"]["base0c" if accent_key == "base0b" else "base0b"]
|
||||
palette = importer["build_palette"](
|
||||
bg, fg, accents, hints, mode="dark" if importer["is_dark"](bg) else "light"
|
||||
)
|
||||
if importer["is_dark"](bg):
|
||||
colors = scheme["colors"]
|
||||
# Keep the Base16 dark surface ladder. Lucid's built-in Mocha uses base00
|
||||
# for its main panels, with base01/base02 as the raised surfaces; the
|
||||
# generic Material builder starts at base02 and makes the whole shell pale.
|
||||
background_tone = importer["tone"](bg)
|
||||
darkest = importer["hx"](at_tone(bg, background_tone - 4))
|
||||
near_background = importer["hx"](at_tone(bg, background_tone - 2))
|
||||
palette.update({
|
||||
"surface": colors["base00"],
|
||||
"surface_dim": darkest,
|
||||
"surface_bright": importer["hx"](at_tone(bg, background_tone + 8)),
|
||||
"surface_container_lowest": darkest,
|
||||
"surface_container_low": near_background,
|
||||
"surface_container": colors["base00"],
|
||||
"surface_container_high": colors["base01"],
|
||||
"surface_container_highest": colors["base02"],
|
||||
"surface_variant": colors["base01"],
|
||||
"on_surface_variant": colors["base04"],
|
||||
"outline": colors["base03"],
|
||||
"outline_variant": colors["base01"],
|
||||
})
|
||||
Path(output).write_text(json.dumps(palette, indent=2) + "\n")
|
||||
PY
|
||||
'';
|
||||
themeMeta = pkgs.writeText "lucid-stylix-meta.json" (builtins.toJSON {
|
||||
id = "stylix";
|
||||
name = "Stylix";
|
||||
desc = "Active Stylix palette";
|
||||
swatchBg = colors.base00;
|
||||
swatchAccent = colors.${config.stylix.accent};
|
||||
source = "Home Manager";
|
||||
detected = "base16";
|
||||
user = true;
|
||||
});
|
||||
themeName = pkgs.writeText "lucid-current-theme" "stylix\n";
|
||||
themeAutoPatch = pkgs.writeText "lucid-theme-auto.patch" ''
|
||||
--- a/Prefs.qml
|
||||
+++ b/Prefs.qml
|
||||
@@ -455,5 +455,12 @@
|
||||
function setColorMode(mode) {
|
||||
+ if (mode === "auto") {
|
||||
+ root.setAutoColorMode(true);
|
||||
+ return;
|
||||
+ }
|
||||
- if ((mode !== "dark" && mode !== "light") || mode === root.colorMode)
|
||||
+ if (mode !== "dark" && mode !== "light")
|
||||
return;
|
||||
|
||||
+ root.setAutoColorMode(false);
|
||||
+ if (mode === root.colorMode)
|
||||
+ return;
|
||||
// set straight away so the control answers the click; the cache file is
|
||||
@@ -484,2 +491,4 @@
|
||||
function setAutoColorMode(on) {
|
||||
+ if (on && root.currentTheme !== "matugen" && root.currentTheme !== "pywal")
|
||||
+ return;
|
||||
root.autoColorMode = on;
|
||||
@@ -1669,1 +1678,5 @@
|
||||
- onLoaded: root.currentTheme = text().trim() || "matugen"
|
||||
+ onLoaded: {
|
||||
+ root.currentTheme = text().trim() || "matugen";
|
||||
+ if (root.currentTheme !== "matugen" && root.currentTheme !== "pywal" && root.autoColorMode)
|
||||
+ root.setAutoColorMode(false);
|
||||
+ }
|
||||
--- a/lucidprefs/ThemePage.qml
|
||||
+++ b/lucidprefs/ThemePage.qml
|
||||
@@ -11,9 +11,14 @@
|
||||
readonly property string home: Quickshell.env("HOME")
|
||||
readonly property string currentTheme: Prefs.currentTheme
|
||||
- readonly property var modeOptions: [{
|
||||
+ readonly property bool wallpaperDerivedTheme: page.currentTheme === "matugen" || page.currentTheme === "pywal"
|
||||
+ readonly property var modeOptions: [{
|
||||
"key": "dark",
|
||||
"label": "Dark"
|
||||
}, {
|
||||
"key": "light",
|
||||
"label": "Light"
|
||||
}]
|
||||
+ readonly property var autoModeOptions: page.modeOptions.concat([{
|
||||
+ "key": "auto",
|
||||
+ "label": "Auto"
|
||||
+ }])
|
||||
@@ -22,1 +27,1 @@
|
||||
- readonly property string modeHint: page.currentTheme === "matugen" || page.currentTheme === "pywal" ? "Re-derives the palette from your wallpaper in the mode you pick. Applications are asked to match." : (page.currentTheme === "colour" ? "Builds the palette from your colour again in the mode you pick. Applications are asked to match." : "Builds a light palette from this theme's own colours. Applications are asked to match.")
|
||||
+ readonly property string modeHint: page.wallpaperDerivedTheme ? "Auto follows the wallpaper brightness whenever you change the wallpaper. Dark and Light stay fixed until you choose another mode." : (page.currentTheme === "colour" ? "Builds the palette from your colour again in the mode you pick. Applications are asked to match." : "Builds a light palette from this theme's own colours. Applications are asked to match.")
|
||||
@@ -273,6 +278,6 @@
|
||||
M3Segmented {
|
||||
- width: Theme.dp(200)
|
||||
- current: Prefs.colorMode
|
||||
- options: page.modeOptions
|
||||
+ width: Theme.dp(page.wallpaperDerivedTheme ? 300 : 200)
|
||||
+ current: Prefs.autoColorMode && page.wallpaperDerivedTheme ? "auto" : Prefs.colorMode
|
||||
+ options: page.wallpaperDerivedTheme ? page.autoModeOptions : page.modeOptions
|
||||
onChosen: (key) => {
|
||||
return Prefs.setColorMode(key);
|
||||
@@ -284,13 +289,0 @@
|
||||
- SettingRow {
|
||||
- title: "Follow the wallpaper"
|
||||
- description: "Picks light for a bright wallpaper and dark for a dark one, every time the wallpaper changes. A hand-picked mode holds until the wallpaper changes next."
|
||||
-
|
||||
- M3Switch {
|
||||
- checked: Prefs.autoColorMode
|
||||
- onToggled: (v) => {
|
||||
- return Prefs.setAutoColorMode(v);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- }
|
||||
-
|
||||
'';
|
||||
settingsScreenPatch = pkgs.writeText "lucid-settings-screen.patch" ''
|
||||
--- a/lucidprefs/Settings.qml
|
||||
+++ b/lucidprefs/Settings.qml
|
||||
@@ -9,2 +9,4 @@
|
||||
FloatingWindow {
|
||||
id: win
|
||||
+ property var preferredScreen: null
|
||||
+ screen: win.preferredScreen || Monitors.mainScreen
|
||||
'';
|
||||
wallpaperAutoPatch = pkgs.writeText "lucid-wallpaper-auto.patch" ''
|
||||
--- a/set-wallpaper.sh
|
||||
+++ b/set-wallpaper.sh
|
||||
@@ -32,4 +32,5 @@
|
||||
MODE="''${2:-}"
|
||||
if [[ -z "$MODE" ]]; then
|
||||
- if grep -qE '"autoColorMode"[[:space:]]*:[[:space:]]*true' "$HOME/.config/quickshell/lucidprefs/prefs.json" 2>/dev/null; then
|
||||
+ if [[ "$CURRENT_THEME" == matugen || "$CURRENT_THEME" == pywal ]] && \
|
||||
+ grep -qE '"autoColorMode"[[:space:]]*:[[:space:]]*true' "$HOME/.config/quickshell/lucidprefs/prefs.json" 2>/dev/null; then
|
||||
MODE=auto
|
||||
'';
|
||||
kdeConnectBridgePython = pkgs.python3.withPackages (ps: [ ps.pygobject3 ]);
|
||||
applyTheme = pkgs.writeShellScript "lucid-apply-theme" ''
|
||||
case "''${1:-}" in
|
||||
--apps-only) exit 0 ;;
|
||||
--shell-only) shift ;;
|
||||
esac
|
||||
exec "$HOME/.config/lucid/apply-theme-upstream.sh" --shell-only "$@"
|
||||
'';
|
||||
qmlPackages = with pkgs.qt6; [ qt5compat qtmultimedia ];
|
||||
tesseract = pkgs.tesseract4.override { enableLanguages = [ "eng" "rus" ]; };
|
||||
lucid = pkgs.writeShellScript "lucid" ''
|
||||
export NIXPKGS_QT6_QML_IMPORT_PATH="${lib.makeSearchPath "lib/qt-6/qml" qmlPackages}:''${NIXPKGS_QT6_QML_IMPORT_PATH:-}"
|
||||
export QT_PLUGIN_PATH="${lib.makeSearchPath "lib/qt-6/plugins" qmlPackages}:''${QT_PLUGIN_PATH:-}"
|
||||
export PATH="${lib.makeBinPath (with pkgs; [ bash brightnessctl cliphist coreutils curl ffmpeg grim imagemagick jq libnotify pulseaudio kdeConnectBridgePython pywal16 wf-recorder wl-clipboard xdg-utils ])}:${tesseract}/bin:$PATH"
|
||||
exec ${pkgs.quickshell}/bin/qs --no-duplicate --path "${config.home.homeDirectory}/.config/quickshell"
|
||||
'';
|
||||
startWallpaper = pkgs.writeShellScript "lucid-wallpaper-start" ''
|
||||
export PATH="${lib.makeBinPath (with pkgs; [ awww bash coreutils ffmpeg imagemagick jq python3 pywal16 util-linux ])}:$PATH"
|
||||
selected="${defaultWallpaper}"
|
||||
if [ -s "${config.home.homeDirectory}/.cache/current_live_wallpaper" ]; then
|
||||
saved=$(${pkgs.coreutils}/bin/head -n1 "${config.home.homeDirectory}/.cache/current_live_wallpaper")
|
||||
if [ -f "$saved" ]; then
|
||||
selected="$saved"
|
||||
fi
|
||||
fi
|
||||
if [ -s "${config.home.homeDirectory}/.cache/current_wallpaper" ]; then
|
||||
saved=$(${pkgs.coreutils}/bin/cat "${config.home.homeDirectory}/.cache/current_wallpaper")
|
||||
if [ "$selected" = "${defaultWallpaper}" ] && [ -f "$saved" ]; then
|
||||
selected="$saved"
|
||||
fi
|
||||
fi
|
||||
if [ ! -f "$selected" ]; then
|
||||
echo "Lucid startup wallpaper not found; leaving the current wallpaper unchanged" >&2
|
||||
exit 0
|
||||
fi
|
||||
exec "${wallpaperScript}" "$selected"
|
||||
'';
|
||||
in {
|
||||
options.lucid = {
|
||||
upstreamSource = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = source;
|
||||
description = "Patched Lucid source tree used by profile modules.";
|
||||
};
|
||||
keybindSeed = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "Optional initial Lucid keybinding file.";
|
||||
};
|
||||
prefsSeed = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "${source}/defaults/prefs.json";
|
||||
description = "Initial Lucid preferences file.";
|
||||
};
|
||||
widgetSeed = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
description = "Optional initial Lucid widget layout.";
|
||||
};
|
||||
initialFiles = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.path;
|
||||
default = {};
|
||||
description = "Files to seed once at their absolute destinations.";
|
||||
};
|
||||
managedFiles = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.path;
|
||||
default = {};
|
||||
description = "Lucid files installed at their absolute destinations.";
|
||||
};
|
||||
prefsFilter = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .wallpaperFolder = $wallpaperDir | .liveDecoder = $decoder'';
|
||||
description = "Declarative jq updates applied to Lucid preferences.";
|
||||
};
|
||||
liveDecoder = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "vaapi";
|
||||
description = "Hardware decoder used by Lucid wallpaper playback.";
|
||||
};
|
||||
bindsModule = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = nonRiasBinds;
|
||||
description = "Lua module that loads this profile's Hyprland bindings.";
|
||||
};
|
||||
luaModules = lib.mkOption {
|
||||
type = lib.types.attrs;
|
||||
default = {};
|
||||
description = "Additional Lucid Hyprland Lua modules for this profile.";
|
||||
};
|
||||
extraHyprlandConfig = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "Profile-specific Hyprland Lua configuration loaded after the shared Lucid setup.";
|
||||
};
|
||||
startWallpaper = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Apply the configured wallpaper before Lucid starts.";
|
||||
};
|
||||
installWallpaperSupport = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Install Lucid wallpaper integration scripts.";
|
||||
};
|
||||
shellFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = ./lucid-shell.qml;
|
||||
description = "Lucid Quickshell entry point for this user profile.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
programs.waybar.enable = lib.mkForce false;
|
||||
services.mako.enable = lib.mkForce false;
|
||||
home.packages = with pkgs; [ cliphist ffmpeg grim imagemagick libnotify pywal16 wf-recorder tesseract ];
|
||||
|
||||
home.activation.installLucid = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||
target="${config.home.homeDirectory}/.config/quickshell"
|
||||
$DRY_RUN_CMD mkdir -p "$target"
|
||||
$DRY_RUN_CMD ${pkgs.rsync}/bin/rsync -rltp --chmod=Du+rwx,Fu+rw \
|
||||
--exclude='/shell.qml' \
|
||||
--exclude='/LucidBar.qml' \
|
||||
--exclude='/lucidprefs/prefs.json' \
|
||||
--exclude='/lucidbar/blur.json' \
|
||||
--exclude='/lucidbar/clock_reminders.json' \
|
||||
--exclude='/lucidbar/mpris_shazam.json' \
|
||||
--exclude='/luciddocks/pinned.json' \
|
||||
--exclude='/luciddocks/usage.json' \
|
||||
--exclude='/luciddocks/wallpaper.json' \
|
||||
--exclude='/lucidmoji/config.json' \
|
||||
--exclude='/lucidmoji/state.json' \
|
||||
--exclude='/lucidkeys/state.json' \
|
||||
--exclude='/lucidwidgets/widgets.json' \
|
||||
"${source}/" "$target/"
|
||||
$DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch -p1 -d "$target" < ${./idle-work.patch}
|
||||
if ! grep -Fq 'mode === "auto"' "$target/Prefs.qml"; then
|
||||
$DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${themeAutoPatch}
|
||||
fi
|
||||
if ! grep -Fq 'property var preferredScreen: null' "$target/lucidprefs/Settings.qml"; then
|
||||
$DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${settingsScreenPatch}
|
||||
fi
|
||||
settings_qml="$target/lucidprefs/Settings.qml"
|
||||
if ! grep -Fq 'win.preferredScreen = Monitors.focusedScreen || Monitors.mainScreen;' "$settings_qml"; then
|
||||
$DRY_RUN_CMD sed -i '/if (win.visible) {/a\ win.preferredScreen = Monitors.focusedScreen || Monitors.mainScreen;' "$settings_qml"
|
||||
fi
|
||||
dock_qml="$target/luciddocks/Dock.qml"
|
||||
if grep -Fq '/var/lib/snapd/desktop/applications; do' "$dock_qml"; then
|
||||
$DRY_RUN_CMD sed -i \
|
||||
's#/var/lib/snapd/desktop/applications; do#/var/lib/snapd/desktop/applications $HOME/.nix-profile/share/applications $HOME/.local/state/nix/profile/share/applications /etc/profiles/per-user/$USER/share/applications /nix/profile/share/applications /nix/var/nix/profiles/default/share/applications /run/current-system/sw/share/applications; do#' \
|
||||
"$dock_qml"
|
||||
elif ! grep -Fq '/etc/profiles/per-user/$USER/share/applications; do' "$dock_qml"; then
|
||||
echo "Lucid launcher application scan pattern changed; refusing to skip Nix profiles" >&2
|
||||
exit 1
|
||||
fi
|
||||
keyboard_focus='WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None'
|
||||
keyboard_focus_fixed='WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None'
|
||||
if grep -Fq "$keyboard_focus" "$dock_qml"; then
|
||||
$DRY_RUN_CMD sed -i \
|
||||
's/WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None/WlrLayershell.keyboardFocus: dockWindow.menuOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None/' \
|
||||
"$dock_qml"
|
||||
elif ! grep -Fq "$keyboard_focus_fixed" "$dock_qml"; then
|
||||
echo "Lucid launcher keyboard focus pattern changed; refusing to skip focus fix" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Fq 'Nix focus workaround for Hyprland 0.57' "$dock_qml"; then
|
||||
if [ "$(grep -Fxc ' onMenuOpenChanged: {' "$dock_qml")" -ne 1 ]; then
|
||||
echo "Lucid menu-open handler changed; refusing to skip focus workaround" >&2
|
||||
exit 1
|
||||
fi
|
||||
$DRY_RUN_CMD sed -i '/ onMenuOpenChanged: {/a\
|
||||
// Nix focus workaround for Hyprland 0.57: detach pointer and keyboard focus while menus are open.\
|
||||
Quickshell.execDetached(["hyprctl", "eval", "hl.config({ input = { follow_mouse = " + (dockWindow.menuOpen ? 2 : 1) + " } })"]);' "$dock_qml"
|
||||
fi
|
||||
$DRY_RUN_CMD install -m644 ${config.lucid.shellFile} "$target/shell.qml"
|
||||
$DRY_RUN_CMD install -m644 ${./LucidBar.qml} "$target/LucidBar.qml"
|
||||
$DRY_RUN_CMD install -m644 ${./Vaultwarden.qml} "$target/Vaultwarden.qml"
|
||||
$DRY_RUN_CMD install -m644 ${./WidgetLayer.qml} "$target/lucidwidgets/WidgetLayer.qml"
|
||||
$DRY_RUN_CMD install -m644 ${./WidgetsPage.qml} "$target/lucidprefs/WidgetsPage.qml"
|
||||
${lib.optionalString config.lucid.installWallpaperSupport ''
|
||||
$DRY_RUN_CMD mkdir -p "${wallpaperDir}" \
|
||||
"${config.home.homeDirectory}/.config/hypr/scripts/wallpaper"
|
||||
$DRY_RUN_CMD install -m755 "${source}/support/wallpaper/set-wallpaper.sh" \
|
||||
"${wallpaperScript}"
|
||||
if ! grep -Fq 'CURRENT_THEME" == matugen' "${wallpaperScript}"; then
|
||||
$DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$(dirname ${lib.escapeShellArg wallpaperScript})" < ${wallpaperAutoPatch}
|
||||
fi
|
||||
''}
|
||||
|
||||
lucid_dir="${config.home.homeDirectory}/.config/lucid"
|
||||
$DRY_RUN_CMD mkdir -p "$lucid_dir/themes"
|
||||
if [ ! -s "$lucid_dir/keybinds.json" ]; then
|
||||
${lib.optionalString (config.lucid.keybindSeed != null) ''$DRY_RUN_CMD install -m644 ${config.lucid.keybindSeed} "$lucid_dir/keybinds.json"''}
|
||||
fi
|
||||
$DRY_RUN_CMD ${pkgs.rsync}/bin/rsync -rltp --chmod=Du+rwx,Fu+rw --ignore-existing \
|
||||
"${source}/support/lucid/themes/" "$lucid_dir/themes/"
|
||||
for script in palette-edit.py lucid_palette.py add-theme.py scheme-gallery.py gen-light-palette.py; do
|
||||
$DRY_RUN_CMD install -m644 "${source}/support/lucid/$script" "$lucid_dir/$script"
|
||||
done
|
||||
for script in gen-pywal-palette.py set-mode.sh wallpaper-mode.sh; do
|
||||
$DRY_RUN_CMD install -m755 "${source}/support/lucid/$script" "$lucid_dir/$script"
|
||||
done
|
||||
$DRY_RUN_CMD install -m755 "${source}/support/lucid/apply-theme.sh" \
|
||||
"$lucid_dir/apply-theme-upstream.sh"
|
||||
$DRY_RUN_CMD install -m755 ${applyTheme} \
|
||||
"$lucid_dir/apply-theme.sh"
|
||||
|
||||
if [ ! -e "$target/lucidprefs/prefs.json" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${config.lucid.prefsSeed} "$target/lucidprefs/prefs.json"
|
||||
fi
|
||||
if [ -z "''${DRY_RUN_CMD:-}" ] && [ -f "$target/lucidprefs/prefs.json" ]; then
|
||||
prefs_tmp=$(${pkgs.coreutils}/bin/mktemp "$target/lucidprefs/prefs.json.XXXXXX")
|
||||
if ${pkgs.jq}/bin/jq --arg font ${lib.escapeShellArg config.stylix.fonts.sansSerif.name} \
|
||||
--arg wallpaperDir ${lib.escapeShellArg wallpaperDir} \
|
||||
--arg decoder ${lib.escapeShellArg config.lucid.liveDecoder} \
|
||||
${lib.escapeShellArg config.lucid.prefsFilter} \
|
||||
"$target/lucidprefs/prefs.json" > "$prefs_tmp"; then
|
||||
${pkgs.coreutils}/bin/chmod 644 "$prefs_tmp"
|
||||
${pkgs.coreutils}/bin/mv "$prefs_tmp" "$target/lucidprefs/prefs.json"
|
||||
else
|
||||
${pkgs.coreutils}/bin/rm -f "$prefs_tmp"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
${lib.optionalString (config.lucid.widgetSeed != null) ''
|
||||
if [ ! -e "$target/lucidwidgets/widgets.json" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${config.lucid.widgetSeed} "$target/lucidwidgets/widgets.json"
|
||||
fi
|
||||
''}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (destination: sourceFile: ''
|
||||
if [ ! -e ${lib.escapeShellArg destination} ]; then
|
||||
$DRY_RUN_CMD mkdir -p ${lib.escapeShellArg (builtins.dirOf destination)}
|
||||
$DRY_RUN_CMD install -m644 ${sourceFile} ${lib.escapeShellArg destination}
|
||||
fi
|
||||
'') config.lucid.initialFiles)}
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (destination: sourceFile: ''
|
||||
$DRY_RUN_CMD mkdir -p ${lib.escapeShellArg (builtins.dirOf destination)}
|
||||
$DRY_RUN_CMD install -m644 ${sourceFile} ${lib.escapeShellArg destination}
|
||||
'') config.lucid.managedFiles)}
|
||||
|
||||
for pair in \
|
||||
'blur.json lucidbar/blur.json' \
|
||||
'clock_reminders.json lucidbar/clock_reminders.json' \
|
||||
'mpris_shazam.json lucidbar/mpris_shazam.json' \
|
||||
'usage.json luciddocks/usage.json' \
|
||||
'wallpaper.json luciddocks/wallpaper.json' \
|
||||
'moji-config.json lucidmoji/config.json' \
|
||||
'moji-state.json lucidmoji/state.json' \
|
||||
'keys-state.json lucidkeys/state.json'; do
|
||||
set -- $pair
|
||||
if [ ! -e "$target/$2" ]; then
|
||||
$DRY_RUN_CMD install -m644 "${source}/defaults/$1" "$target/$2"
|
||||
fi
|
||||
done
|
||||
|
||||
$DRY_RUN_CMD mkdir -p "${config.home.homeDirectory}/.config/lucid/themes/stylix" \
|
||||
"${config.home.homeDirectory}/.cache/quickshell"
|
||||
$DRY_RUN_CMD install -m644 ${paletteFile} \
|
||||
"${config.home.homeDirectory}/.config/lucid/themes/stylix/quickshell.json"
|
||||
$DRY_RUN_CMD install -m644 ${themeMeta} \
|
||||
"${config.home.homeDirectory}/.config/lucid/themes/stylix/meta.json"
|
||||
if [ ! -e "${config.home.homeDirectory}/.cache/current_theme" ]; then
|
||||
$DRY_RUN_CMD install -m644 ${themeName} \
|
||||
"${config.home.homeDirectory}/.cache/current_theme"
|
||||
fi
|
||||
if [ ! -e "${config.home.homeDirectory}/.cache/current_theme" ] || \
|
||||
[ "$(${pkgs.coreutils}/bin/cat "${config.home.homeDirectory}/.cache/current_theme")" = stylix ]; then
|
||||
$DRY_RUN_CMD install -m644 ${paletteFile} \
|
||||
"${config.home.homeDirectory}/.cache/quickshell/matugen.json"
|
||||
fi
|
||||
'';
|
||||
|
||||
systemd.user.services.lucid = {
|
||||
Unit = {
|
||||
Description = "Lucid bar, dock and desktop widgets";
|
||||
After = [ "graphical-session.target" ];
|
||||
PartOf = [ "graphical-session.target" ];
|
||||
};
|
||||
Service = {
|
||||
ExecStart = "${lucid}";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 2;
|
||||
} // lib.optionalAttrs config.lucid.startWallpaper { ExecStartPre = "${startWallpaper}"; };
|
||||
Install.WantedBy = [ "hyprland-session.target" ];
|
||||
};
|
||||
|
||||
wayland.windowManager.hyprland = {
|
||||
settings = {
|
||||
on = [ (hyprLua.onStart [ "systemctl --user start lucid.service" ]) ];
|
||||
};
|
||||
# Home Manager autoloads these before its generated settings. Lucid's
|
||||
# stateful UI modules are installed too, then required after the Nix base
|
||||
# config below so saved UI choices take effect without losing that base.
|
||||
extraLuaFiles = {
|
||||
"modules/binds.lua" = config.lucid.bindsModule;
|
||||
"modules/json.lua" = builtins.toPath "${source}/support/hypr/modules/json.lua";
|
||||
"modules/specials.lua" = {
|
||||
content = builtins.toPath "${source}/support/hypr/modules/specials.lua";
|
||||
autoLoad = false;
|
||||
};
|
||||
} // config.lucid.luaModules;
|
||||
# Nix owns session startup and environment exports; don't load Lucid's
|
||||
# generic autostart/env defaults on top of the existing host setup.
|
||||
extraConfig = ''
|
||||
local config_home = os.getenv("XDG_CONFIG_HOME") or ${builtins.toJSON config.xdg.configHome}
|
||||
package.path = config_home .. "/hypr/?.lua;" .. config_home .. "/hypr/?/init.lua;" .. package.path
|
||||
require("modules.specials")
|
||||
LucidSpecials.apply()
|
||||
'' + config.lucid.extraHyprlandConfig;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
--- a/lucidbar/Clock.qml
|
||||
+++ b/lucidbar/Clock.qml
|
||||
@@ -94,7 +94,7 @@
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
- running: true
|
||||
+ running: root.visible && root.hostWindow !== null && root.hostWindow.visible
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.now = Loc.now()
|
||||
}
|
||||
@@ -477,36 +477,7 @@
|
||||
color: Theme.accent
|
||||
transformOrigin: Item.Top
|
||||
|
||||
- SequentialAnimation on rotation {
|
||||
- loops: Animation.Infinite
|
||||
- running: bell.visible
|
||||
-
|
||||
- NumberAnimation {
|
||||
- from: 0
|
||||
- to: -14
|
||||
- duration: Theme.barMs(100)
|
||||
- easing.type: Easing.OutQuad
|
||||
- }
|
||||
-
|
||||
- NumberAnimation {
|
||||
- from: -14
|
||||
- to: 14
|
||||
- duration: Theme.barMs(160)
|
||||
- easing.type: Easing.InOutQuad
|
||||
- }
|
||||
-
|
||||
- NumberAnimation {
|
||||
- from: 14
|
||||
- to: 0
|
||||
- duration: Theme.barMs(140)
|
||||
- easing.type: Easing.OutQuad
|
||||
- }
|
||||
-
|
||||
- PauseAnimation {
|
||||
- duration: Theme.barMs(2800)
|
||||
- }
|
||||
-
|
||||
- }
|
||||
+
|
||||
|
||||
}
|
||||
|
||||
--- a/lucidbar/Mpris.qml
|
||||
+++ b/lucidbar/Mpris.qml
|
||||
@@ -649,7 +649,7 @@
|
||||
// media card, both closed most of the time. a per-frame interpolation behind
|
||||
// a closed panel measured ~18% of a core, so it waits until something shows it
|
||||
FrameAnimation {
|
||||
- running: root.isPlaying && !trackHitArea.dragging && (root.expanded || root.posWanted)
|
||||
+ running: root.visible && root.hostWindow !== null && root.hostWindow.visible && root.isPlaying && !trackHitArea.dragging && (root.expanded || root.posWanted)
|
||||
onTriggered: root.livePosSec = Math.min(root.lenSec, root.posBase + (Date.now() - root.posTimestamp) / 1000)
|
||||
}
|
||||
|
||||
@@ -731,7 +731,7 @@
|
||||
|
||||
// silence costs the same as sound to capture and parse, so only listen
|
||||
// while the pill's bars can dance or the panel is open on the waveform
|
||||
- running: (root.isPlaying && compactBars.visible) || root.expanded
|
||||
+ running: root.visible && root.hostWindow !== null && root.hostWindow.visible && ((root.isPlaying && compactBars.visible) || root.expanded)
|
||||
command: ["cava", "-p", Quickshell.env('HOME') + "/.config/cava/quickshell.conf"]
|
||||
onRunningChanged: {
|
||||
if (!cavaProc.running && root.bars.length > 0)
|
||||
@@ -948,7 +948,7 @@
|
||||
to: 360
|
||||
duration: 14000
|
||||
loops: Animation.Infinite
|
||||
- running: root.isPlaying && compactArt.visible
|
||||
+ running: root.isPlaying && compactArt.visible && root.hostWindow !== null && root.hostWindow.visible
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2906,7 +2906,7 @@
|
||||
readonly property real segmentWidth: measure.implicitWidth
|
||||
readonly property real naturalWidth: bare.implicitWidth
|
||||
readonly property bool overflowing: mq.naturalWidth > mq.width
|
||||
- readonly property bool animating: mq.scrolling && mq.overflowing
|
||||
+ readonly property bool animating: mq.scrolling && mq.overflowing && mq.visible && mq.opacity > 0
|
||||
property real currentX: 0
|
||||
|
||||
function updateScroll() {
|
||||
--- a/lucidwidgets/WidgetFrame.qml
|
||||
+++ b/lucidwidgets/WidgetFrame.qml
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Item {
|
||||
id: frame
|
||||
+
|
||||
+ property bool updatesEnabled: true
|
||||
|
||||
required property string uid
|
||||
required property string wtype
|
||||
--- a/lucidwidgets/WidgetBody.qml
|
||||
+++ b/lucidwidgets/WidgetBody.qml
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
// set by the frame once the loader is ready
|
||||
property var host: null
|
||||
+ readonly property bool updatesEnabled: !body.host || (body.host.updatesEnabled !== false && body.host.onThisScreen !== false)
|
||||
// a variant that draws straight onto the wallpaper with no container behind it
|
||||
property bool bare: false
|
||||
// a body with nothing worth showing right now: the frame fades the whole card
|
||||
--- a/lucidwidgets/ClockWidget.qml
|
||||
+++ b/lucidwidgets/ClockWidget.qml
|
||||
@@ -43,7 +43,7 @@
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
- running: true
|
||||
+ running: w.updatesEnabled && w.visible
|
||||
triggeredOnStart: true
|
||||
onTriggered: w.now = Loc.now()
|
||||
}
|
||||
--- a/lucidwidgets/SystemWidget.qml
|
||||
+++ b/lucidwidgets/SystemWidget.qml
|
||||
@@ -127,17 +127,23 @@
|
||||
return w.inkAccent;
|
||||
}
|
||||
|
||||
- Component.onCompleted: {
|
||||
- if (!w.preview)
|
||||
- Sys.hold(w.uid || "w", true, w.pollMs);
|
||||
-
|
||||
- }
|
||||
- Component.onDestruction: Sys.hold(w.uid || "w", false)
|
||||
- onPollMsChanged: {
|
||||
- if (!w.preview)
|
||||
- Sys.hold(w.uid || "w", true, w.pollMs);
|
||||
-
|
||||
- }
|
||||
+ property string pollingTag: ""
|
||||
+
|
||||
+ function syncPolling() {
|
||||
+ const tag = w.uid;
|
||||
+ if (w.pollingTag !== "" && w.pollingTag !== tag)
|
||||
+ Sys.hold(w.pollingTag, false);
|
||||
+ w.pollingTag = tag;
|
||||
+ if (tag !== "")
|
||||
+ Sys.hold(tag, !w.preview && w.updatesEnabled, w.pollMs);
|
||||
+ }
|
||||
+
|
||||
+ Component.onCompleted: w.syncPolling()
|
||||
+ Component.onDestruction: Sys.hold(w.pollingTag, false)
|
||||
+ onUidChanged: w.syncPolling()
|
||||
+ onPollMsChanged: w.syncPolling()
|
||||
+ onUpdatesEnabledChanged: w.syncPolling()
|
||||
+ onPreviewChanged: w.syncPolling()
|
||||
|
||||
// rings: one thick m3 ring per reading, the icon inside, the number under it
|
||||
Row {
|
||||
--- a/lucidwidgets/FetchWidget.qml
|
||||
+++ b/lucidwidgets/FetchWidget.qml
|
||||
@@ -49,12 +49,22 @@
|
||||
readonly property var swatches: [Theme.primary, Theme.secondary, Theme.tertiary, Theme.error, Theme.primaryContainer, Theme.secondaryContainer, Theme.tertiaryContainer, Theme.surfaceHighest]
|
||||
|
||||
defaultTone: "surface"
|
||||
- Component.onCompleted: {
|
||||
- if (!w.preview)
|
||||
- Sys.hold("fetch-" + w.uid, true, 5000);
|
||||
-
|
||||
- }
|
||||
- Component.onDestruction: Sys.hold("fetch-" + w.uid, false)
|
||||
+ property string pollingTag: ""
|
||||
+
|
||||
+ function syncPolling() {
|
||||
+ const tag = w.uid === "" ? "" : "fetch-" + w.uid;
|
||||
+ if (w.pollingTag !== "" && w.pollingTag !== tag)
|
||||
+ Sys.hold(w.pollingTag, false);
|
||||
+ w.pollingTag = tag;
|
||||
+ if (tag !== "")
|
||||
+ Sys.hold(tag, !w.preview && w.updatesEnabled, 5000);
|
||||
+ }
|
||||
+
|
||||
+ Component.onCompleted: w.syncPolling()
|
||||
+ Component.onDestruction: Sys.hold(w.pollingTag, false)
|
||||
+ onUidChanged: w.syncPolling()
|
||||
+ onUpdatesEnabledChanged: w.syncPolling()
|
||||
+ onPreviewChanged: w.syncPolling()
|
||||
|
||||
// card: the mark and who you are, then a labelled list
|
||||
Item {
|
||||
--- a/lucidwidgets/VisualiserWidget.qml
|
||||
+++ b/lucidwidgets/VisualiserWidget.qml
|
||||
@@ -50,6 +50,11 @@
|
||||
if (w.preview || w.uid === "")
|
||||
return ;
|
||||
|
||||
+ if (!w.updatesEnabled) {
|
||||
+ Cava.drop(w.filed);
|
||||
+ w.filed = "";
|
||||
+ return;
|
||||
+ }
|
||||
Cava.want(w.uid, w.barCount);
|
||||
w.filed = w.uid;
|
||||
}
|
||||
@@ -124,6 +129,7 @@
|
||||
onWantCountChanged: w.refreshCount()
|
||||
onResizingChanged: w.refreshCount()
|
||||
onBarCountChanged: w.file()
|
||||
+ onUpdatesEnabledChanged: w.file()
|
||||
onUidChanged: {
|
||||
if (w.filed !== "" && w.filed !== w.uid)
|
||||
Cava.drop(w.filed);
|
||||
@@ -158,7 +164,7 @@
|
||||
Timer {
|
||||
interval: 55
|
||||
repeat: true
|
||||
- running: w.preview
|
||||
+ running: w.preview && w.updatesEnabled && w.visible
|
||||
onTriggered: w.phase += 0.055
|
||||
}
|
||||
|
||||
--- a/lucidprefs/ThemePage.qml
|
||||
+++ b/lucidprefs/ThemePage.qml
|
||||
@@ -770,6 +770,9 @@
|
||||
"key": "any",
|
||||
"label": "Any window"
|
||||
}, {
|
||||
+ "key": "non-float",
|
||||
+ "label": "Non-float"
|
||||
+ }, {
|
||||
"key": "fullscreen",
|
||||
"label": "Fullscreen"
|
||||
}, {
|
||||
|
||||
--- a/LiveWall.qml
|
||||
+++ b/LiveWall.qml
|
||||
@@ -42,13 +42,18 @@
|
||||
const out = {};
|
||||
const mons = Hyprland.monitors.values;
|
||||
for (let i = 0; i < mons.length; i++) {
|
||||
const ws = mons[i].activeWorkspace;
|
||||
- const windows = ws ? ws.toplevels.values.length : 0;
|
||||
+ const windows = ws ? ws.toplevels.values : [];
|
||||
let still = Lockscreen.locked || (root.gameMode && Prefs.liveGameMode === "pause");
|
||||
if (Prefs.livePauseUnder === "any")
|
||||
- still = still || windows > 0;
|
||||
+ still = still || windows.length > 0;
|
||||
+ else if (Prefs.livePauseUnder === "non-float")
|
||||
+ still = still || windows.some((t) => {
|
||||
+ const o = t && t.lastIpcObject;
|
||||
+ return o && o.mapped !== false && o.hidden !== true && o.floating !== true;
|
||||
+ });
|
||||
else if (Prefs.livePauseUnder === "fullscreen")
|
||||
still = still || (ws !== null && ws.hasFullscreen);
|
||||
out[mons[i].name] = !still;
|
||||
}
|
||||
@@ -1,483 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"binds": [
|
||||
{
|
||||
"id": "wallpaper-picker",
|
||||
"keys": "SUPER + W",
|
||||
"desc": "Wallpaper picker",
|
||||
"category": "Lucid"
|
||||
},
|
||||
{
|
||||
"id": "wallpaper-random",
|
||||
"keys": "SUPER + SHIFT + W",
|
||||
"desc": "Set a random wallpaper",
|
||||
"category": "Desktop"
|
||||
},
|
||||
{
|
||||
"id": "calculator-volume",
|
||||
"keys": "XF86Calculator",
|
||||
"desc": "Open volume mixer",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "zoom-reset-scroll-up",
|
||||
"keys": "SUPER + SHIFT + mouse_up",
|
||||
"desc": "Reset cursor zoom",
|
||||
"category": "Accessibility"
|
||||
},
|
||||
{
|
||||
"id": "zoom-reset-scroll-down",
|
||||
"keys": "SUPER + SHIFT + mouse_down",
|
||||
"desc": "Reset cursor zoom",
|
||||
"category": "Accessibility"
|
||||
},
|
||||
{
|
||||
"id": "zoom-reset-minus",
|
||||
"keys": "SUPER + SHIFT + minus",
|
||||
"desc": "Reset cursor zoom",
|
||||
"category": "Accessibility"
|
||||
},
|
||||
{
|
||||
"id": "zoom-reset-kp-subtract",
|
||||
"keys": "SUPER + SHIFT + KP_SUBTRACT",
|
||||
"desc": "Reset cursor zoom",
|
||||
"category": "Accessibility"
|
||||
},
|
||||
{
|
||||
"id": "zoom-reset-zero",
|
||||
"keys": "SUPER + SHIFT + 0",
|
||||
"desc": "Reset cursor zoom",
|
||||
"category": "Accessibility"
|
||||
},
|
||||
{
|
||||
"id": "zoom-in-equal",
|
||||
"keys": "SUPER + equal",
|
||||
"desc": "Zoom in",
|
||||
"category": "Accessibility",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "zoom-out-minus",
|
||||
"keys": "SUPER + minus",
|
||||
"desc": "Zoom out",
|
||||
"category": "Accessibility",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "zoom-in-kp-add",
|
||||
"keys": "SUPER + KP_ADD",
|
||||
"desc": "Zoom in",
|
||||
"category": "Accessibility",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "zoom-out-kp-subtract",
|
||||
"keys": "SUPER + KP_SUBTRACT",
|
||||
"desc": "Zoom out",
|
||||
"category": "Accessibility",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "widgets-panel",
|
||||
"keys": "SUPER + SHIFT + G",
|
||||
"desc": "Toggle widgets panel",
|
||||
"category": "Lucid"
|
||||
},
|
||||
{
|
||||
"id": "launcher",
|
||||
"keys": "SUPER + D",
|
||||
"desc": "App launcher",
|
||||
"category": "Lucid"
|
||||
},
|
||||
{
|
||||
"id": "vaultwarden-password",
|
||||
"keys": "SUPER + X",
|
||||
"desc": "Type a password from Vaultwarden",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "clipse",
|
||||
"keys": "SUPER + V",
|
||||
"desc": "Clipboard manager",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "clipboard",
|
||||
"keys": "SUPER + SHIFT + V",
|
||||
"desc": "Clipboard history",
|
||||
"category": "Lucid"
|
||||
},
|
||||
{
|
||||
"id": "screenshot",
|
||||
"keys": "Print",
|
||||
"desc": "Full screenshot",
|
||||
"category": "Screenshots"
|
||||
},
|
||||
{
|
||||
"id": "screenshot-full",
|
||||
"keys": "SUPER + Print",
|
||||
"desc": "Full screenshot",
|
||||
"category": "Screenshots"
|
||||
},
|
||||
{
|
||||
"id": "screenshot-region",
|
||||
"keys": "CTRL + Print",
|
||||
"desc": "Region screenshot",
|
||||
"category": "Screenshots"
|
||||
},
|
||||
{
|
||||
"id": "screenshot-window",
|
||||
"keys": "ALT + Print",
|
||||
"desc": "Window screenshot",
|
||||
"category": "Screenshots"
|
||||
},
|
||||
{
|
||||
"id": "terminal",
|
||||
"keys": "SUPER + RETURN",
|
||||
"desc": "Terminal",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "close-window",
|
||||
"keys": "SUPER + Q",
|
||||
"desc": "Close window",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "exit-hyprland",
|
||||
"keys": "SUPER + M",
|
||||
"desc": "Exit Hyprland",
|
||||
"category": "Session"
|
||||
},
|
||||
{
|
||||
"id": "file-manager",
|
||||
"keys": "SUPER + E",
|
||||
"desc": "File manager",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "toggle-float",
|
||||
"keys": "SUPER + F",
|
||||
"desc": "Toggle floating window",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "toggle-pseudo",
|
||||
"keys": "SUPER + P",
|
||||
"desc": "Toggle pseudo-tile",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "toggle-split",
|
||||
"keys": "SUPER + J",
|
||||
"desc": "Toggle split direction",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "swap-split",
|
||||
"keys": "SUPER + K",
|
||||
"desc": "Swap split direction",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "focus-left",
|
||||
"keys": "SUPER + LEFT",
|
||||
"desc": "Focus left",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "focus-right",
|
||||
"keys": "SUPER + RIGHT",
|
||||
"desc": "Focus right",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "focus-up",
|
||||
"keys": "SUPER + UP",
|
||||
"desc": "Focus up",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "focus-down",
|
||||
"keys": "SUPER + DOWN",
|
||||
"desc": "Focus down",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "swap-left",
|
||||
"keys": "SUPER + SHIFT + LEFT",
|
||||
"desc": "Move window left",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "swap-right",
|
||||
"keys": "SUPER + SHIFT + RIGHT",
|
||||
"desc": "Move window right",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "swap-up",
|
||||
"keys": "SUPER + SHIFT + UP",
|
||||
"desc": "Move window up",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "swap-down",
|
||||
"keys": "SUPER + SHIFT + DOWN",
|
||||
"desc": "Move window down",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "resize-left",
|
||||
"keys": "SUPER + CTRL + LEFT",
|
||||
"desc": "Resize window left",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "resize-right",
|
||||
"keys": "SUPER + CTRL + RIGHT",
|
||||
"desc": "Resize window right",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "resize-up",
|
||||
"keys": "SUPER + CTRL + UP",
|
||||
"desc": "Resize window up",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "resize-down",
|
||||
"keys": "SUPER + CTRL + DOWN",
|
||||
"desc": "Resize window down",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "workspace-focus",
|
||||
"keys": "SUPER + {key}",
|
||||
"desc": "Focus workspace {n}",
|
||||
"category": "Workspaces",
|
||||
"each": "workspace"
|
||||
},
|
||||
{
|
||||
"id": "workspace-move",
|
||||
"keys": "SUPER + SHIFT + {key}",
|
||||
"desc": "Move window to workspace {n}",
|
||||
"category": "Workspaces",
|
||||
"each": "workspace"
|
||||
},
|
||||
{
|
||||
"id": "scratchpad",
|
||||
"keys": "SUPER + ALT + P",
|
||||
"desc": "Toggle scratchpad",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.scratchpad()"
|
||||
},
|
||||
{
|
||||
"id": "scratchpad-stash",
|
||||
"keys": "SUPER + ALT + S",
|
||||
"desc": "Stash or restore the focused window",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.stash()"
|
||||
},
|
||||
{
|
||||
"id": "special-music",
|
||||
"keys": "SUPER + ALT + M",
|
||||
"desc": "Toggle Music workspace",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.toggle('music')"
|
||||
},
|
||||
{
|
||||
"id": "special-comms",
|
||||
"keys": "SUPER + ALT + C",
|
||||
"desc": "Toggle Comms workspace",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.toggle('comms')"
|
||||
},
|
||||
{
|
||||
"id": "special-todo",
|
||||
"keys": "SUPER + ALT + T",
|
||||
"desc": "Toggle To-do workspace",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.toggle('todo')"
|
||||
},
|
||||
{
|
||||
"id": "special-sysmon",
|
||||
"keys": "SUPER + ALT + B",
|
||||
"desc": "Toggle system monitor workspace",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.toggle('sysmon')"
|
||||
},
|
||||
{
|
||||
"id": "daemons-workspace",
|
||||
"keys": "SUPER + ALT + D",
|
||||
"desc": "Daemons workspace",
|
||||
"category": "Workspaces",
|
||||
"type": "lua",
|
||||
"lua": "specials.toggle('daemons')"
|
||||
},
|
||||
{
|
||||
"id": "fullscreen",
|
||||
"keys": "SUPER + SHIFT + F",
|
||||
"desc": "Toggle fullscreen",
|
||||
"category": "Windows"
|
||||
},
|
||||
{
|
||||
"id": "workspace-next-scroll",
|
||||
"keys": "SUPER + mouse_down",
|
||||
"desc": "Next workspace",
|
||||
"category": "Workspaces"
|
||||
},
|
||||
{
|
||||
"id": "workspace-previous-scroll",
|
||||
"keys": "SUPER + mouse_up",
|
||||
"desc": "Previous workspace",
|
||||
"category": "Workspaces"
|
||||
},
|
||||
{
|
||||
"id": "keyboard-brightness-up",
|
||||
"keys": "SUPER + F3",
|
||||
"desc": "Keyboard backlight up",
|
||||
"category": "Displays"
|
||||
},
|
||||
{
|
||||
"id": "keyboard-brightness-down",
|
||||
"keys": "SUPER + F2",
|
||||
"desc": "Keyboard backlight down",
|
||||
"category": "Displays"
|
||||
},
|
||||
{
|
||||
"id": "audio-mute",
|
||||
"keys": "XF86AudioMute",
|
||||
"desc": "Toggle audio mute",
|
||||
"category": "Media"
|
||||
},
|
||||
{
|
||||
"id": "microphone-mute",
|
||||
"keys": "XF86AudioMicMute",
|
||||
"desc": "Toggle microphone mute",
|
||||
"category": "Media"
|
||||
},
|
||||
{
|
||||
"id": "play-pause",
|
||||
"keys": "XF86AudioPlay",
|
||||
"desc": "Play or pause media",
|
||||
"category": "Media"
|
||||
},
|
||||
{
|
||||
"id": "file-manager-media",
|
||||
"keys": "XF86Explorer",
|
||||
"desc": "File manager",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "email",
|
||||
"keys": "XF86Mail",
|
||||
"desc": "Email",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "browser",
|
||||
"keys": "XF86WWW",
|
||||
"desc": "Web browser",
|
||||
"category": "Apps"
|
||||
},
|
||||
{
|
||||
"id": "display-brightness-down",
|
||||
"keys": "XF86MonBrightnessDown",
|
||||
"desc": "Brightness down",
|
||||
"category": "Displays"
|
||||
},
|
||||
{
|
||||
"id": "display-brightness-up",
|
||||
"keys": "XF86MonBrightnessUp",
|
||||
"desc": "Brightness up",
|
||||
"category": "Displays"
|
||||
},
|
||||
{
|
||||
"id": "volume-up",
|
||||
"keys": "XF86AudioRaiseVolume",
|
||||
"desc": "Volume up",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "volume-down",
|
||||
"keys": "XF86AudioLowerVolume",
|
||||
"desc": "Volume down",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"repeating": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "previous-seek",
|
||||
"keys": "XF86AudioPrev",
|
||||
"desc": "Seek backward",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"click": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "next-seek",
|
||||
"keys": "XF86AudioNext",
|
||||
"desc": "Seek forward",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"click": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "previous-track",
|
||||
"keys": "XF86AudioPrev",
|
||||
"desc": "Previous track",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"long_press": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "next-track",
|
||||
"keys": "XF86AudioNext",
|
||||
"desc": "Next track",
|
||||
"category": "Media",
|
||||
"opts": {
|
||||
"long_press": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "move-window-drag",
|
||||
"keys": "SUPER + mouse:272",
|
||||
"desc": "Move window with mouse",
|
||||
"category": "Windows",
|
||||
"opts": {
|
||||
"mouse": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "resize-window-drag",
|
||||
"keys": "SUPER + mouse:273",
|
||||
"desc": "Resize window with mouse",
|
||||
"category": "Windows",
|
||||
"opts": {
|
||||
"mouse": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
import "./luciddocks"
|
||||
import "./lucidedit"
|
||||
import "./lucidlock"
|
||||
import "./lucidosd"
|
||||
import "./lucidprefs"
|
||||
import "./lucidshot"
|
||||
import "./lucidwidgets"
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: {
|
||||
void Specials.moduleProbed;
|
||||
void Clip.probed;
|
||||
void Capture.state;
|
||||
}
|
||||
|
||||
function announceShot(entry) {
|
||||
if (Prefs.shotPreview === "preview") {
|
||||
shotCards.show(entry);
|
||||
return;
|
||||
}
|
||||
if (Prefs.shotPreview !== "notify")
|
||||
return;
|
||||
const where = String(entry.file).replace(Quickshell.env("HOME"), "~");
|
||||
if (entry.kind === "video")
|
||||
Quickshell.execDetached(["notify-send", "-a", "LucidShot", "-i", "media-record", "Recording saved", where]);
|
||||
else
|
||||
Quickshell.execDetached(["sh", "-c", "a=$(notify-send -a LucidShot -i \"$1\" -A 'edit=Mark up' -A 'open=Open' --wait \"$2\" \"$3\"); case \"$a\" in edit) qs ipc call snap edit \"$1\" ;; open) xdg-open \"$1\" ;; esac; true", "sh", entry.file, entry.temp ? "Screenshot copied" : "Screenshot saved", entry.temp ? "It is on the clipboard only" : where]);
|
||||
}
|
||||
|
||||
ShotEngine {
|
||||
id: shotEngine
|
||||
cards: shotCards
|
||||
onReady: (entry) => {
|
||||
if (entry.owner === "overlay") {
|
||||
shotCards.update(entry);
|
||||
return;
|
||||
}
|
||||
if (entry.owner === "pin")
|
||||
return;
|
||||
root.announceShot(entry);
|
||||
}
|
||||
onFailed: (id, why) => toastMod.popup("warning", why, true)
|
||||
onTextRead: (r) => {
|
||||
if (!shotEngine.claim(r.id))
|
||||
return;
|
||||
if (r.status === "ok") {
|
||||
if (Prefs.shotPreview === "preview")
|
||||
shotCards.show({ "id": r.id, "kind": "text", "text": r.text, "codes": r.codes, "screen": Monitors.focusedScreen ? Monitors.focusedScreen.name : "" });
|
||||
else
|
||||
toastMod.popup("copy", "Text copied", false);
|
||||
} else if (r.status === "notool") {
|
||||
toastMod.popup("alert", "Install tesseract to copy text", true);
|
||||
} else {
|
||||
toastMod.popup("alert", "No text found", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShotOverlay {
|
||||
id: shotOverlay
|
||||
engine: shotEngine
|
||||
cards: shotCards
|
||||
pins: shotPins
|
||||
onNotify: (icon, text, warn) => toastMod.popup(icon, text, warn)
|
||||
onSwatch: (hex, text) => toastMod.popupSwatch(hex, text)
|
||||
onFreezeFailed: (why) => toastMod.popup("alert", "Couldn't capture the screen", true)
|
||||
}
|
||||
|
||||
ShotCards {
|
||||
id: shotCards
|
||||
engine: shotEngine
|
||||
pins: shotPins
|
||||
overlay: shotOverlay
|
||||
}
|
||||
|
||||
PinBoard {
|
||||
id: shotPins
|
||||
engine: shotEngine
|
||||
}
|
||||
|
||||
RecordHud {}
|
||||
|
||||
Connections {
|
||||
target: Capture
|
||||
function onSaved(file, seconds) {
|
||||
root.announceShot({ "id": "rec-" + Date.now(), "kind": "video", "file": file, "duration": seconds, "screen": Capture.output });
|
||||
}
|
||||
function onFailed(why) {
|
||||
toastMod.popup("warning", why, true);
|
||||
}
|
||||
}
|
||||
|
||||
LucidBar {
|
||||
id: barRoot
|
||||
dock: dockRoot
|
||||
toastMod: toastMod
|
||||
}
|
||||
|
||||
Dock {
|
||||
id: dockRoot
|
||||
}
|
||||
|
||||
Toast {
|
||||
id: toastMod
|
||||
}
|
||||
|
||||
Osd {
|
||||
}
|
||||
|
||||
ToastEvents {
|
||||
toast: toastMod
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: toastMod
|
||||
property: "screen"
|
||||
value: Monitors.shellPlacement
|
||||
when: Monitors.shellPlacement !== null
|
||||
}
|
||||
|
||||
WidgetLayer {
|
||||
}
|
||||
|
||||
WidgetIpc {
|
||||
}
|
||||
|
||||
Lock {
|
||||
}
|
||||
|
||||
EditLayer {
|
||||
}
|
||||
|
||||
SettingsHost {
|
||||
}
|
||||
|
||||
DisplayTrial {
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: Monitors
|
||||
property: "barWindows"
|
||||
value: barRoot.instances
|
||||
}
|
||||
}
|
||||
+4
-181
@@ -1,194 +1,17 @@
|
||||
{ config, lib, osConfig, ... }: let
|
||||
{ lib, ... }:
|
||||
let
|
||||
hyprLua = import ./hyprland/lua.nix { inherit lib; };
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
in {
|
||||
services = {
|
||||
walker = {
|
||||
enable = true;
|
||||
systemd.enable = true;
|
||||
|
||||
theme = {
|
||||
name = "stylix";
|
||||
# Follow Walker's default CSS structure so provider layouts keep their styling.
|
||||
# https://github.com/abenz1267/walker/blob/master/resources/themes/default/style.css
|
||||
style = ''
|
||||
@define-color window_bg_color ${colors.base00};
|
||||
@define-color surface_color ${colors.base01};
|
||||
@define-color border_color ${colors.base03};
|
||||
@define-color accent_bg_color ${accent};
|
||||
@define-color theme_fg_color ${colors.base05};
|
||||
@define-color error_bg_color ${colors.base08};
|
||||
@define-color error_fg_color ${colors.base00};
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
popover {
|
||||
background: @surface_color;
|
||||
border: 1px solid @border_color;
|
||||
border-radius: 18px;
|
||||
padding: 10px;
|
||||
font-family: "${config.stylix.fonts.sansSerif.name}";
|
||||
font-size: ${toString config.stylix.fonts.sizes.applications}pt;
|
||||
}
|
||||
|
||||
.box-wrapper {
|
||||
box-shadow:
|
||||
0 19px 38px rgba(0, 0, 0, 0.3),
|
||||
0 15px 12px rgba(0, 0, 0, 0.22);
|
||||
background: @window_bg_color;
|
||||
border: 1px solid @border_color;
|
||||
border-radius: 20px;
|
||||
padding: 20px;
|
||||
font-family: "${config.stylix.fonts.sansSerif.name}";
|
||||
font-size: ${toString config.stylix.fonts.sizes.applications}pt;
|
||||
}
|
||||
|
||||
.normal-icons { -gtk-icon-size: 16px; }
|
||||
.large-icons { -gtk-icon-size: 32px; }
|
||||
scrollbar { opacity: 0; }
|
||||
|
||||
.search-container { border-radius: 10px; }
|
||||
|
||||
.input {
|
||||
caret-color: @theme_fg_color;
|
||||
background: @surface_color;
|
||||
padding: 10px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.input placeholder { opacity: 0.5; }
|
||||
.input selection { background: ${colors.base02}; }
|
||||
|
||||
.preview-box,
|
||||
.elephant-hint,
|
||||
.placeholder,
|
||||
.list { color: @theme_fg_color; }
|
||||
|
||||
.item-box {
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.item-quick-activation {
|
||||
background: alpha(@accent_bg_color, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
child:selected .item-box,
|
||||
row:selected .item-box { background: alpha(@accent_bg_color, 0.25); }
|
||||
|
||||
.item-subtext {
|
||||
font-size: 12px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.providerlist .item-subtext {
|
||||
font-size: unset;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.item-image-text { font-size: 28px; }
|
||||
|
||||
.preview {
|
||||
border: 1px solid alpha(@accent_bg_color, 0.25);
|
||||
border-radius: 10px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.calc .item-text,
|
||||
.symbols .item-image,
|
||||
.todo.urgent { font-size: 24px; }
|
||||
|
||||
.todo.done .item-text-box { opacity: 0.25; }
|
||||
.todo.active { font-weight: bold; }
|
||||
.bluetooth.disconnected { opacity: 0.5; }
|
||||
.preview .large-icons { -gtk-icon-size: 64px; }
|
||||
|
||||
.keybinds {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid @border_color;
|
||||
font-size: 12px;
|
||||
color: @theme_fg_color;
|
||||
}
|
||||
|
||||
.keybind-button { opacity: 0.5; }
|
||||
.keybind-button:hover { opacity: 0.75; }
|
||||
.keybind-bind {
|
||||
text-transform: lowercase;
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.keybind-label {
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid @theme_fg_color;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 10px;
|
||||
background: @error_bg_color;
|
||||
color: @error_fg_color;
|
||||
}
|
||||
|
||||
:not(.calc).current { font-style: italic; }
|
||||
|
||||
.preview-content.archlinuxpkgs,
|
||||
.preview-content.dnfpackages,
|
||||
.preview-content.aptpackages {
|
||||
font-family: "${config.stylix.fonts.monospace.name}";
|
||||
}
|
||||
'' + lib.optionalString (config.home.username == "chest" && osConfig.networking.hostName == "Eclipse") ''
|
||||
/* Keep the existing layout; let the compositor blur the wallpaper. */
|
||||
window { background: transparent; }
|
||||
.box-wrapper {
|
||||
background: alpha(@surface_color, 0.62);
|
||||
border: 2px solid alpha(@accent_bg_color, 0.38);
|
||||
padding: 19px;
|
||||
box-shadow: none;
|
||||
}
|
||||
.input {
|
||||
background: alpha(${colors.base02}, 0.42);
|
||||
border-radius: 10px;
|
||||
}
|
||||
popover {
|
||||
background: alpha(@surface_color, 0.86);
|
||||
border: 2px solid alpha(@accent_bg_color, 0.38);
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
/* Paint only the rounded item, never both its row and child. */
|
||||
child, row,
|
||||
child:hover, row:hover,
|
||||
child:selected, row:selected {
|
||||
background: transparent;
|
||||
border-radius: 10px;
|
||||
}
|
||||
child:hover .item-box,
|
||||
row:hover .item-box {
|
||||
background: alpha(@accent_bg_color, 0.12);
|
||||
}
|
||||
child:selected .item-box,
|
||||
row:selected .item-box {
|
||||
background: alpha(@accent_bg_color, 0.25);
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
elephant.enable = true;
|
||||
};
|
||||
|
||||
xdg.configFile = lib.optionalAttrs (config.home.username == "chest") {
|
||||
"elephant/desktopapplications.toml".text = ''
|
||||
history_when_empty = true
|
||||
'';
|
||||
};
|
||||
|
||||
lucidHyprlandBinds = [
|
||||
(hyprLua.bind "SUPER + D" (hyprLua.execLauncher "qs ipc call launcher toggle"))
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
(hyprLua.bind "SUPER + D" (hyprLua.exec "walker"))
|
||||
];
|
||||
}
|
||||
+1
-9
@@ -1,14 +1,6 @@
|
||||
{ pkgs, pkgs-pinned, lib, osConfig, ... }: {
|
||||
{ pkgs, pkgs-pinned, host, ... }: {
|
||||
imports = [
|
||||
../../modules/user/packages/cs_utils.nix
|
||||
../../modules/user/lucid
|
||||
./modules/lucid
|
||||
] ++ lib.optionals (osConfig.networking.hostName == "Impreza") [
|
||||
../../host/Impreza/modules/lucid
|
||||
] ++ lib.optionals (osConfig.networking.hostName == "Rias") [
|
||||
../../host/Rias/modules/lucid
|
||||
] ++ lib.optionals (osConfig.networking.hostName == "Eclipse") [
|
||||
../../host/Eclipse/modules/lucid
|
||||
];
|
||||
disabledModules = [
|
||||
../../modules/user/packages/art.nix
|
||||
|
||||
@@ -71,9 +71,9 @@ in {
|
||||
{ leaf = "fade"; enabled = true; speed = 7; bezier = "default"; }
|
||||
{ leaf = "workspaces"; enabled = true; speed = 6; bezier = "default"; }
|
||||
];
|
||||
};
|
||||
|
||||
lucidHyprlandBinds = [
|
||||
(hyprLua.bind "SUPER + SHIFT + S" (hyprLua.exec ''${lib.getExe pkgs.grim} -g "$(${lib.getExe pkgs.slurp})" - | ${lib.getExe pkgs.swappy} -f -''))
|
||||
];
|
||||
bind = [
|
||||
(hyprLua.bind "SUPER + SHIFT + S" (hyprLua.exec ''${lib.getExe pkgs.grim} -g "$(${lib.getExe pkgs.slurp})" - | ${lib.getExe pkgs.swappy} -f -''))
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
source = config.lucid.upstreamSource;
|
||||
specialBindIds = [ "scratchpad" "scratchpad-stash" "special-music" "special-comms" "special-todo" "special-sysmon" "daemons-workspace" ];
|
||||
specialSeed = pkgs.writeText "lucid-special-bindings.json" (builtins.toJSON {
|
||||
version = 1;
|
||||
binds = builtins.filter (entry: builtins.elem entry.id specialBindIds)
|
||||
(builtins.fromJSON (builtins.readFile ../../../../modules/user/lucid/lucid-keybind-catalog.json)).binds;
|
||||
});
|
||||
chestBindingsScript = pkgs.writeText "lucid-chest-bindings.py" ''
|
||||
import hashlib, json, re, sys
|
||||
from collections import Counter
|
||||
source, reference, special, output = sys.argv[1:]
|
||||
binds = json.loads(source)
|
||||
refs = json.load(open(reference))["binds"]
|
||||
specials = json.load(open(special))["binds"]
|
||||
counts, result = Counter(), []
|
||||
for entry in binds:
|
||||
args = entry["_args"]
|
||||
if not 2 <= len(args) <= 3 or args[1].get("_type") != "lua-inline":
|
||||
raise ValueError(f"Unsupported Hyprland binding: {entry}")
|
||||
keys, expr = args[0], args[1]["expr"]
|
||||
opts = args[2] if len(args) == 3 else {}
|
||||
signature = json.dumps([keys.upper().replace(" ", ""), opts], sort_keys=True)
|
||||
counts[signature] += 1
|
||||
identifier = hashlib.sha256(signature.encode()).hexdigest()[:12]
|
||||
b = {"id": f"chest-{identifier}-{counts[signature]}", "keys": keys}
|
||||
match = next((r for r in refs if r.get("keys", "").upper().replace(" ", "") == keys.upper().replace(" ", "") and r.get("opts", {}) == opts), None)
|
||||
b["desc"] = match.get("desc", keys) if match else keys
|
||||
b["category"] = match.get("category", "Other") if match else "Other"
|
||||
workspace = re.search(r"workspace = (\d+)", expr)
|
||||
if workspace and expr.startswith("hl.dsp.focus("):
|
||||
b.update(desc=f"Focus workspace {workspace.group(1)}", category="Workspaces")
|
||||
elif workspace and expr.startswith("hl.dsp.window.move("):
|
||||
b.update(desc=f"Move window to workspace {workspace.group(1)}", category="Workspaces")
|
||||
if keys.upper().replace(" ", "") == "SUPER+SHIFT+S": b.update(desc="Region screenshot and edit", category="Screenshots")
|
||||
m = re.fullmatch(r'hl\.dsp\.exec_cmd\(("(?:\\.|[^"\\])*")\)\s*', expr, re.S)
|
||||
if m:
|
||||
cmd = json.loads(m.group(1))
|
||||
cmd = re.sub(r'/nix/store/[0-9a-z]{32}-[^/\s"\\]+/bin/([A-Za-z0-9_.+-]+)', r'\1', cmd)
|
||||
b.update(type="exec", cmd=cmd)
|
||||
else: b.update(type="lua", lua=expr)
|
||||
if opts: b["opts"] = opts
|
||||
result.append(b)
|
||||
json.dump({"version": 1, "binds": result + specials}, open(output, "w"), indent=2)
|
||||
'';
|
||||
chestSeed = pkgs.runCommand "lucid-chest-bindings.json" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
python3 ${chestBindingsScript} ${lib.escapeShellArg (builtins.toJSON config.lucidHyprlandBinds)} \
|
||||
${../../../../modules/user/lucid/lucid-keybind-catalog.json} ${specialSeed} "$out"
|
||||
'';
|
||||
chestBinds = pkgs.runCommand "lucid-chest-loader.lua" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
python3 - ${source}/support/hypr/modules/binds.lua "$out" <<'PY'
|
||||
import pathlib, sys
|
||||
source = pathlib.Path(sys.argv[1]).read_text()
|
||||
old = '{ id = "terminal", keys = "F9", type = "exec", cmd = "kitty" }'
|
||||
new = '{ id = "terminal", keys = "SUPER + RETURN", type = "exec", cmd = "ghostty" }'
|
||||
if source.count(old) != 1: raise SystemExit("Lucid emergency bindings changed")
|
||||
pathlib.Path(sys.argv[2]).write_text(source.replace(old, new, 1))
|
||||
PY
|
||||
'';
|
||||
chestMonitors = pkgs.runCommand "lucid-chest-monitors.lua" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
python3 - ${source}/support/hypr/modules/monitors.lua "$out" <<'PY'
|
||||
import pathlib, sys
|
||||
source = pathlib.Path(sys.argv[1]).read_text()
|
||||
rule = """hl.monitor({
|
||||
output = "",
|
||||
mode = M.defaults.mode,
|
||||
position = M.defaults.position,
|
||||
scale = M.defaults.scale,
|
||||
})
|
||||
"""
|
||||
if source.count(rule) != 1: raise SystemExit("Lucid monitor defaults changed")
|
||||
pathlib.Path(sys.argv[2]).write_text(source.replace(rule, "", 1))
|
||||
PY
|
||||
'';
|
||||
chestBorders = pkgs.runCommand "lucid-chest-decorations.lua" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
||||
python3 - ${source}/support/hypr/modules/decorations.lua "$out" <<'PY'
|
||||
import pathlib, sys
|
||||
source = pathlib.Path(sys.argv[1]).read_text()
|
||||
marker = "local active_border, inactive_border = borders()"
|
||||
if source.count(marker) != 1: raise SystemExit("Lucid border module changed")
|
||||
pathlib.Path(sys.argv[2]).write_text(source.split(marker, 1)[0])
|
||||
PY
|
||||
'';
|
||||
chestPrefs = pkgs.writeText "lucid-chest-prefs.json" (builtins.toJSON (
|
||||
(builtins.fromJSON (builtins.readFile "${source}/defaults/prefs.json")) // {
|
||||
specialCustom = builtins.toJSON [ { key = "daemons"; label = "Daemons"; glyph = "build"; on = true; apps = ""; } ];
|
||||
}
|
||||
));
|
||||
in
|
||||
{
|
||||
lucid.keybindSeed = chestSeed;
|
||||
lucid.prefsSeed = chestPrefs;
|
||||
lucid.bindsModule = chestBinds;
|
||||
lucid.luaModules = {
|
||||
"modules/decorations.lua" = lib.mkForce chestBorders;
|
||||
"modules/monitors.lua" = lib.mkForce { content = chestMonitors; autoLoad = false; };
|
||||
"modules/glass.lua" = { content = "${source}/support/hypr/modules/glass.lua"; autoLoad = false; };
|
||||
"modules/settings.lua" = { content = "${source}/support/hypr/modules/settings.lua"; autoLoad = false; };
|
||||
"modules/startup.lua" = { content = "${source}/support/hypr/modules/startup.lua"; autoLoad = false; };
|
||||
};
|
||||
lucid.extraHyprlandConfig = ''
|
||||
require("modules.monitors")
|
||||
require("modules.settings")
|
||||
require("modules.glass")
|
||||
require("modules.startup")
|
||||
'';
|
||||
lucid.shellFile = pkgs.writeText "lucid-chest-shell.qml" (builtins.replaceStrings [
|
||||
''import "./lucidlock"''
|
||||
" Lock {\n }\n"
|
||||
" void Clip.probed;"
|
||||
] [
|
||||
""
|
||||
""
|
||||
" void Notifs.count;\n void KdeConnect.installed;\n void HyprConfig.moduleProbed;\n void Monitors.moduleProbed;\n void Glass.moduleProbed;\n void Startup.moduleProbed;\n void Clip.probed;"
|
||||
] (builtins.readFile ../../../../modules/user/lucid/lucid-shell.qml));
|
||||
|
||||
lucid.prefsFilter = ''.barEnabled = true | .widgetOnTop = false | .widgetHideFullscreen = true | .fontFamily = $font | .liveWallpapers = false | .idleLock = false | .idleLockBeforeSleep = false | .launcherPowerButtons = "suspend,reboot,shutdown" | .powerModuleActions = "suspend,hibernate,logout,reboot,shutdown"'';
|
||||
|
||||
home.packages = with pkgs; [ brightnessctl playerctl slurp swappy ];
|
||||
|
||||
wayland.windowManager.hyprland.settings = {
|
||||
bind = lib.mkForce [];
|
||||
window_rule = [
|
||||
{
|
||||
name = "float-lucid-settings";
|
||||
match = { class = "org.quickshell"; title = "Lucid Settings"; };
|
||||
float = true;
|
||||
size = [ 1180 800 ];
|
||||
center = true;
|
||||
}
|
||||
{
|
||||
name = "float-lucid-choosers";
|
||||
match = {
|
||||
class = "^(org\\.gnome\\.Nautilus|xdg-desktop-portal-gtk|xdg-desktop-portal-gnome|zenity|org\\.kde\\.kdialog|kdialog)$";
|
||||
title = "^(Choose an account picture|Choose a template|Choose a colour scheme|Export the palette|Save received files to|Send files|Send to .+)$";
|
||||
};
|
||||
float = true;
|
||||
center = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
home.activation.installChestLucidFixes = lib.hm.dag.entryAfter [ "installLucid" ] ''
|
||||
target="${config.home.homeDirectory}/.config/quickshell"
|
||||
lock_qml="$target/Lockscreen.qml"
|
||||
if ! grep -Fq 'return; // Lucid lock disabled for chest' "$lock_qml"; then
|
||||
grep -Fq 'function lock(settle) {' "$lock_qml" && \
|
||||
grep -Fq 'function pushLockedHint() {' "$lock_qml" && \
|
||||
grep -Fq 'id: logindWatch' "$lock_qml" || {
|
||||
echo "Lucid lock integration changed; refusing to leave it active for chest" >&2
|
||||
exit 1
|
||||
}
|
||||
$DRY_RUN_CMD sed -i \
|
||||
-e '/function lock(settle) {/a\ return; // Lucid lock disabled for chest' \
|
||||
-e '/function pushLockedHint() {/a\ return; // Lucid lock disabled for chest' \
|
||||
-e '/id: logindWatch/{n;n;s/running: true/running: false/;}' "$lock_qml"
|
||||
fi
|
||||
if ! grep -Fq 'id: chestBrightnessPoll' "$target/lucidosd/Osd.qml"; then
|
||||
$DRY_RUN_CMD ${pkgs.patch}/bin/patch --batch --forward -p1 -d "$target" < ${pkgs.writeText "lucid-chest-osd.patch" ''
|
||||
--- a/lucidosd/Osd.qml
|
||||
+++ b/lucidosd/Osd.qml
|
||||
@@ -26 +26 @@
|
||||
- readonly property int brightnessPercent: osdWindow.maxBrightness > 0 ? Math.round((parseInt(brightnessFile.text()) / osdWindow.maxBrightness) * 100) : 0
|
||||
+ property int brightnessPercent: 0
|
||||
@@ -378,2 +378,15 @@
|
||||
onFileChanged: reload()
|
||||
+ onLoaded: {
|
||||
+ const value = parseInt(brightnessFile.text());
|
||||
+ if (osdWindow.maxBrightness > 0 && !isNaN(value))
|
||||
+ osdWindow.brightnessPercent = Math.round(value / osdWindow.maxBrightness * 100);
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+ Timer {
|
||||
+ id: chestBrightnessPoll
|
||||
+ interval: 500
|
||||
+ repeat: true
|
||||
+ running: osdWindow.backlightDevice !== ""
|
||||
+ onTriggered: brightnessFile.reload()
|
||||
+ }
|
||||
''}
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -1,29 +1,4 @@
|
||||
{ pkgs, config, lib, osConfig, inputs, ... }: let
|
||||
mikuFrost = builtins.elem osConfig.networking.hostName [ "Eclipse" "Impreza" ];
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
# Render the same Stylix templates, changing only native translucency and accent.
|
||||
kvantumConfig = config.lib.stylix.colors {
|
||||
template = builtins.readFile "${inputs.stylix}/modules/qt/kvconfig.mustache";
|
||||
extension = ".kvconfig";
|
||||
};
|
||||
kvantumSvg = config.lib.stylix.colors {
|
||||
template = builtins.readFile "${inputs.stylix}/modules/qt/kvantum.svg.mustache";
|
||||
extension = ".svg";
|
||||
};
|
||||
frostKvantum = pkgs.runCommandLocal "miku-frost-kvantum" {} ''
|
||||
directory="$out/share/Kvantum/Base16Kvantum"
|
||||
mkdir -p "$directory"
|
||||
cp ${kvantumConfig} "$directory/Base16Kvantum.kvconfig"
|
||||
cp ${kvantumSvg} "$directory/Base16Kvantum.svg"
|
||||
substituteInPlace "$directory/Base16Kvantum.kvconfig" \
|
||||
--replace-fail 'translucent_windows=false' 'translucent_windows=true' \
|
||||
--replace-fail 'blurring=false' 'blurring=true' \
|
||||
--replace-fail 'reduce_window_opacity=10' 'reduce_window_opacity=18' \
|
||||
--replace-fail 'highlight.color=${colors.base0E}' 'highlight.color=${accent}'
|
||||
substituteInPlace "$directory/Base16Kvantum.svg" \
|
||||
--replace-fail '${colors.base0D}' '${accent}'
|
||||
'';
|
||||
{ pkgs, config, lib, ... }: let
|
||||
miku-cursor = pkgs.stdenv.mkDerivation {
|
||||
name = "Miku cursor";
|
||||
|
||||
@@ -111,46 +86,10 @@ in {
|
||||
hyprland.enable = false;
|
||||
waybar.enable = false;
|
||||
kitty.variant256Colors = true;
|
||||
gtk.extraCss = lib.mkIf mikuFrost ''
|
||||
/* Native background alpha keeps text and controls fully opaque. */
|
||||
@define-color accent_color ${accent};
|
||||
@define-color accent_bg_color ${accent};
|
||||
@define-color theme_selected_bg_color ${accent};
|
||||
@define-color window_bg_color alpha(${colors.base01}, 0.78);
|
||||
@define-color view_bg_color alpha(${colors.base00}, 0.48);
|
||||
@define-color headerbar_bg_color alpha(${colors.base01}, 0.76);
|
||||
@define-color headerbar_backdrop_color @window_bg_color;
|
||||
@define-color sidebar_bg_color alpha(${colors.base01}, 0.72);
|
||||
@define-color sidebar_backdrop_color @window_bg_color;
|
||||
@define-color dialog_bg_color alpha(${colors.base01}, 0.84);
|
||||
@define-color popover_bg_color alpha(${colors.base01}, 0.90);
|
||||
@define-color theme_bg_color @window_bg_color;
|
||||
@define-color theme_base_color @view_bg_color;
|
||||
'';
|
||||
};
|
||||
|
||||
# Keep a file-backed scheme for consumers such as Neofetch.
|
||||
base16Scheme = if mikuFrost then toString (pkgs.writeText "miku-frost.yaml" ''
|
||||
scheme: "Miku Frost"
|
||||
author: "chest"
|
||||
base00: "101724"
|
||||
base01: "192534"
|
||||
base02: "253247"
|
||||
base03: "47586B"
|
||||
base04: "93A8BA"
|
||||
base05: "E1EFF2"
|
||||
base06: "F1F8F8"
|
||||
base07: "F9FFFF"
|
||||
base08: "EF829A"
|
||||
base09: "F5AB82"
|
||||
base0A: "EED69A"
|
||||
base0B: "69DCC7"
|
||||
base0C: "43D6CD"
|
||||
base0D: "7BB9E8"
|
||||
base0E: "B29DF4"
|
||||
base0F: "E3A8D8"
|
||||
'') else "${pkgs.base16-schemes}/share/themes/eris.yaml";
|
||||
accent = if mikuFrost then "base0C" else "base0E";
|
||||
base16Scheme = "${pkgs.base16-schemes}/share/themes/eris.yaml";
|
||||
accent = "base0E";
|
||||
polarity = "dark";
|
||||
|
||||
image = pkgs.fetchurl {
|
||||
@@ -193,59 +132,6 @@ in {
|
||||
};
|
||||
};
|
||||
|
||||
qt.kvantum.themes = lib.mkIf mikuFrost (lib.mkForce [ frostKvantum ]);
|
||||
|
||||
# Notifications share the island's surface and rounded, borderless silhouette.
|
||||
services.mako.settings = lib.mkIf mikuFrost {
|
||||
background-color = lib.mkForce "${colors.base01}D9";
|
||||
text-color = lib.mkForce colors.base05;
|
||||
border-size = lib.mkForce 0;
|
||||
border-radius = 22;
|
||||
padding = "12";
|
||||
progress-color = lib.mkForce "over ${accent}33";
|
||||
"urgency=low".background-color = lib.mkForce "${colors.base01}D9";
|
||||
"urgency=critical" = {
|
||||
background-color = lib.mkForce "${colors.base01}F0";
|
||||
text-color = lib.mkForce colors.base08;
|
||||
};
|
||||
};
|
||||
|
||||
# Yazi uses its own true-colour theme rather than the terminal ANSI palette.
|
||||
programs.yazi.theme = lib.mkIf mikuFrost {
|
||||
filetype.rules = lib.mkBefore [
|
||||
{ url = "*/"; fg = colors.base05; bold = false; }
|
||||
];
|
||||
icon.conds = lib.mkBefore [
|
||||
{ "if" = "dir"; text = ""; fg = accent; }
|
||||
];
|
||||
mgr.border_style.fg = lib.mkForce colors.base03;
|
||||
indicator = {
|
||||
current = { fg = colors.base05; bg = lib.mkForce colors.base02; };
|
||||
preview = { fg = colors.base05; bg = lib.mkForce colors.base02; };
|
||||
};
|
||||
tabs = {
|
||||
active = { fg = lib.mkForce colors.base00; bg = lib.mkForce accent; };
|
||||
inactive = { fg = lib.mkForce colors.base04; bg = lib.mkForce colors.base01; };
|
||||
};
|
||||
mode = {
|
||||
normal_main = { fg = lib.mkForce colors.base00; bg = lib.mkForce accent; };
|
||||
normal_alt = { fg = lib.mkForce accent; bg = lib.mkForce colors.base01; };
|
||||
};
|
||||
input = {
|
||||
border.fg = lib.mkForce accent;
|
||||
selected.bg = lib.mkForce colors.base02;
|
||||
};
|
||||
pick.border.fg = lib.mkForce accent;
|
||||
completion.border.fg = lib.mkForce accent;
|
||||
tasks.border.fg = lib.mkForce accent;
|
||||
};
|
||||
|
||||
# Keep the original terminal glass, with colours inherited from Stylix.
|
||||
programs.ghostty.themes.stylix = lib.mkIf mikuFrost {
|
||||
background = lib.mkForce config.lib.stylix.colors.base00;
|
||||
cursor-color = lib.mkForce config.lib.stylix.colors.base0C;
|
||||
};
|
||||
|
||||
dconf.settings = {
|
||||
"org/gnome/desktop/interface".color-scheme = "prefer-dark";
|
||||
};
|
||||
|
||||
+25
-127
@@ -5,9 +5,7 @@
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
mikuFrost = osConfig.networking.hostName == "Eclipse";
|
||||
in {
|
||||
}: {
|
||||
home.packages = with pkgs; [
|
||||
font-awesome
|
||||
nerd-fonts.symbols-only
|
||||
@@ -61,20 +59,11 @@ in {
|
||||
"pulseaudio"
|
||||
"custom/sep-left"
|
||||
"battery"
|
||||
] ++ lib.optional mikuFrost "custom/sep-battery" ++ [
|
||||
"clock"
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
"hyprland/workspaces" = lib.optionalAttrs mikuFrost {
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
tray = lib.optionalAttrs mikuFrost {
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
cava = {
|
||||
bars = 9;
|
||||
bar_delimiter = 0;
|
||||
@@ -90,7 +79,6 @@ in {
|
||||
};
|
||||
|
||||
pulseaudio = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{icon} {volume}%";
|
||||
format-icons = {
|
||||
headphone = "";
|
||||
@@ -106,20 +94,18 @@ in {
|
||||
};
|
||||
|
||||
mpris = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{dynamic}";
|
||||
dynamic-len = if osConfig.host.laptop then 16 else 32;
|
||||
dynamic-order = [ "title" "artist" "album" ];
|
||||
};
|
||||
|
||||
battery = {
|
||||
tooltip = !mikuFrost;
|
||||
interval = 5;
|
||||
states = {
|
||||
warning = 30;
|
||||
critical = 15;
|
||||
};
|
||||
format = if mikuFrost then "{icon} {capacity}%" else "{icon} {capacity}% | ";
|
||||
format = "{icon} {capacity}% | ";
|
||||
format-icons = {
|
||||
default = ["" "" "" "" "" "" "" "" "" "" ""];
|
||||
charging = ["" "" "" "" "" "" "" "" "" "" ""];
|
||||
@@ -131,17 +117,7 @@ in {
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
# Follow battery visibility; the divider has no hover or click target.
|
||||
"custom/sep-battery" = {
|
||||
format = " {} ";
|
||||
exec = "printf '|'";
|
||||
tooltip = false;
|
||||
exec-if = "for battery in /sys/class/power_supply/*; do test \"$(cat \"$battery/type\" 2>/dev/null)\" = Battery && exit 0; done; exit 1";
|
||||
interval = 5;
|
||||
};
|
||||
|
||||
"keyboard-state" = {
|
||||
tooltip = !mikuFrost;
|
||||
capslock = true;
|
||||
format = "{icon}";
|
||||
format-icons = {
|
||||
@@ -151,7 +127,6 @@ in {
|
||||
};
|
||||
|
||||
"hyprland/language" = {
|
||||
tooltip = !mikuFrost;
|
||||
format-en = "en";
|
||||
format-ru = "ru";
|
||||
};
|
||||
@@ -171,7 +146,6 @@ in {
|
||||
};
|
||||
|
||||
network = {
|
||||
tooltip = !mikuFrost;
|
||||
format = "{ifname}";
|
||||
format-wifi = " {essid} ({signalStrength}%)";
|
||||
format-ethernet = "{ifname}";
|
||||
@@ -197,7 +171,6 @@ in {
|
||||
};
|
||||
|
||||
cpu = {
|
||||
tooltip = !mikuFrost;
|
||||
interval = 2;
|
||||
format = "{usage}% ";
|
||||
min-length = 6;
|
||||
@@ -215,97 +188,47 @@ in {
|
||||
style = let
|
||||
colors = config.lib.stylix.colors.withHashtag;
|
||||
accent = colors.${config.stylix.accent};
|
||||
themed = frost: original: if mikuFrost then frost else original;
|
||||
scssFile = pkgs.writeText "waybar.scss" /*scss*/ ''
|
||||
window#waybar {
|
||||
background: transparent;
|
||||
color: ${colors.base05};
|
||||
font-weight: bold;
|
||||
font-size: .85em;
|
||||
& > * { padding: ${themed "0 12px 14px" "0 0 6px 0"}; }
|
||||
& > * { padding: 0 0 6px 0; }
|
||||
}
|
||||
|
||||
${lib.optionalString mikuFrost /*scss*/ ''
|
||||
// Only the centered island paints a surface or a shadow.
|
||||
window#waybar,
|
||||
window#waybar > box {
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
''}
|
||||
|
||||
.modules-center {
|
||||
background: ${themed "rgba(${colors.base01}, 0.58)" "${colors.base01}CC"};
|
||||
border: none;
|
||||
border-radius: ${themed "22px" "12px"};
|
||||
background: ${colors.base01}CC;
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
box-shadow: ${themed "0 3px 8px rgba(0, 0, 0, 0.18)" "5px 5px 10px rgba(0, 0, 0, 0.4)"};
|
||||
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
#left,
|
||||
#right,
|
||||
#center {
|
||||
background: ${themed "rgba(40, 59, 79, 0.68)" colors.base02};
|
||||
background: ${colors.base02};
|
||||
padding: 0 2px;
|
||||
margin: 0 8px;
|
||||
border-radius: ${themed "12px" "8px"};
|
||||
border-radius: 8px;
|
||||
}
|
||||
#left { margin-left: 0; }
|
||||
#right { margin-right: 0; }
|
||||
|
||||
${lib.optionalString mikuFrost /*scss*/ ''
|
||||
// Keep hover surfaces inside the rounded groups.
|
||||
#language, #keyboard-state, #keyboard-state label,
|
||||
#cava, #mpris, #tray widget, #tray widget > image,
|
||||
#network, #custom-mem, #cpu, #temperature,
|
||||
#pulseaudio, #battery, #clock {
|
||||
border-radius: 10px;
|
||||
}
|
||||
#clock { padding-right: 10px; }
|
||||
|
||||
#network, #custom-mem, #cpu, #temperature,
|
||||
#pulseaudio, #battery, #clock {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
// Reserve the same width in active, inactive and hover states.
|
||||
#workspaces button {
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
tooltip {
|
||||
color: ${colors.base05};
|
||||
background: rgba(25, 37, 52, 0.94);
|
||||
border: 2px solid rgba(67, 214, 205, 0.38);
|
||||
border-radius: ${themed "12px" "8px"};
|
||||
}
|
||||
tooltip label { padding: 6px; }
|
||||
''}
|
||||
|
||||
#clock { padding-right: ${themed "6px" "10px"}; }
|
||||
|
||||
#cava { padding: 0 10px; color: ${themed accent colors.base05}; }
|
||||
|
||||
#custom-sep-left,
|
||||
#custom-sep-battery {
|
||||
color: ${themed "rgba(147, 168, 186, 0.38)" colors.base05};
|
||||
background: transparent;
|
||||
}
|
||||
#cava { padding: 0 10px; }
|
||||
|
||||
#language { padding: 8px; }
|
||||
|
||||
#workspaces button {
|
||||
color: ${themed colors.base04 colors.base05};
|
||||
color: ${colors.base05};
|
||||
padding: 0 2px;
|
||||
border-radius: 999px;
|
||||
border: 1pt solid transparent;
|
||||
min-width: ${themed "25px" "20px"};
|
||||
min-width: 20px;
|
||||
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
&:hover { background: ${colors.base03}; }
|
||||
|
||||
&.active {
|
||||
background: ${accent};
|
||||
@@ -313,50 +236,27 @@ in {
|
||||
min-width: 25px;
|
||||
|
||||
&:hover {
|
||||
border-color: ${themed "transparent" accent};
|
||||
background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03};
|
||||
border-color: ${accent};
|
||||
background: ${colors.base03};
|
||||
color: ${accent};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#mpris {
|
||||
border-radius: ${themed "10px" "8px"};
|
||||
// Do not leave a styled placeholder when the player has no text.
|
||||
padding: ${themed "0" "4px"};
|
||||
border: none;
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
&:hover { background: ${colors.base03}; }
|
||||
&.playing {
|
||||
border: ${themed "none" "2px solid ${accent}"};
|
||||
padding: ${themed "0" "2px"};
|
||||
color: ${themed colors.base0E colors.base05};
|
||||
border: 2px solid ${accent};
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
${if mikuFrost then /*scss*/ ''
|
||||
// Each SNI item is one event box containing one image. Paint it once.
|
||||
#tray > widget {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#tray > widget:hover {
|
||||
background: rgba(67, 214, 205, 0.12);
|
||||
}
|
||||
#tray > widget > image {
|
||||
padding: 8px;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#tray > widget.needs-attention {
|
||||
background: rgba(245, 171, 130, 0.16);
|
||||
}
|
||||
'' else /*scss*/ ''
|
||||
#tray {
|
||||
widget {
|
||||
border: 1pt solid transparent;
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
&:hover { background: ${colors.base03}; }
|
||||
& > image { padding: 8px; }
|
||||
}
|
||||
|
||||
@@ -364,23 +264,21 @@ in {
|
||||
& > .needs-attention { border-color: ${colors.base09}; }
|
||||
}
|
||||
|
||||
''}
|
||||
|
||||
#pulseaudio {
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03}; }
|
||||
&:hover { background: ${colors.base03}; }
|
||||
&.muted {
|
||||
background: ${colors.base08};
|
||||
color: ${colors.base01};
|
||||
|
||||
&:hover {
|
||||
color: ${colors.base08};
|
||||
background: ${themed "rgba(67, 214, 205, 0.12)" colors.base03};
|
||||
background: ${colors.base03};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#network {
|
||||
&:hover { background: ${themed "rgba(67, 214, 205, 0.12)" colors.base02}; }
|
||||
&:hover { background: ${colors.base02}; }
|
||||
&.disconnected {
|
||||
color: ${colors.base00};
|
||||
background: ${colors.base08};
|
||||
@@ -388,7 +286,7 @@ in {
|
||||
}
|
||||
|
||||
#system .drawer-child > * {
|
||||
margin-right: 4px;
|
||||
margin-right: 4px
|
||||
}
|
||||
|
||||
#keyboard-state label.locked {
|
||||
|
||||
@@ -44,8 +44,6 @@
|
||||
];
|
||||
|
||||
programs.zsh.enable = true;
|
||||
programs.kdeconnect.enable = true;
|
||||
home-manager.users.${name}.services.kdeconnect.enable = true;
|
||||
|
||||
users = {
|
||||
defaultUserShell = pkgs.zsh;
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
{ pkgs, lib, osConfig, ... }: let
|
||||
# Match the Electron FFmpeg build recommended by Vivaldi 8.2.
|
||||
vivaldiCodecs = pkgs.runCommand "vivaldi-ffmpeg-electron-44.4.2" {
|
||||
nativeBuildInputs = [ pkgs.unzip ];
|
||||
} ''
|
||||
mkdir -p "$out/lib"
|
||||
unzip -p ${pkgs.fetchurl {
|
||||
url = "https://github.com/electron/electron/releases/download/v44.4.2/electron-v44.4.2-linux-x64.zip";
|
||||
hash = "sha256-rXEGPnfaKaZH+HKwkEePn6nM/9Z957KuYxVAnPHxNDY=";
|
||||
}} libffmpeg.so > "$out/lib/libffmpeg.so"
|
||||
echo "39f63af3ef6f8db9eea20803c6f6dff8b2d5ad596d506ea1effb3f856715ac1c $out/lib/libffmpeg.so" | sha256sum -c
|
||||
'';
|
||||
in {
|
||||
imports = [
|
||||
../../modules/user/lucid
|
||||
./modules/lucid
|
||||
] ++ lib.optionals (osConfig.networking.hostName == "Rias") [
|
||||
../../host/Rias/modules/lucid
|
||||
./modules/lucid/rias.nix
|
||||
];
|
||||
|
||||
{ pkgs, ... }: {
|
||||
programs.hyprlock.enable = true;
|
||||
home.packages = with pkgs; [
|
||||
android-studio
|
||||
(vivaldi.override {
|
||||
proprietaryCodecs = true;
|
||||
vivaldi-ffmpeg-codecs = vivaldiCodecs;
|
||||
enableWidevine = true;
|
||||
})
|
||||
weechat
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{ pkgs, lib, config, osConfig, ... }:
|
||||
let
|
||||
{ pkgs, lib, config, osConfig, inputs, ... }: {
|
||||
wayland.windowManager.hyprland = let
|
||||
colors = config.lib.stylix.colors;
|
||||
accent = colors.${config.stylix.accent};
|
||||
hyprLua = import ../../../modules/user/hyprland/lua.nix { inherit lib; };
|
||||
|
||||
wallpaper_changer = pkgs.writers.writePython3Bin "wallpaper_changer" {
|
||||
libraries = [ pkgs.python3Packages.requests ];
|
||||
flakeIgnore = [ "E111" "E121" "E241" "E501" "E701" "E731" ];
|
||||
} /*py*/ ''
|
||||
import requests as requests
|
||||
from random import choice
|
||||
from os import listdir, makedirs, remove, replace
|
||||
from os import listdir, makedirs, remove, replace, system
|
||||
from os.path import exists
|
||||
from subprocess import run
|
||||
|
||||
@@ -121,15 +123,9 @@ let
|
||||
finally:
|
||||
if filename is not None:
|
||||
notify("Applying wallpaper", 100)
|
||||
run([
|
||||
"${config.home.homeDirectory}/.config/hypr/scripts/wallpaper/set-wallpaper.sh",
|
||||
f"{folder}/{filename}",
|
||||
], check=False)
|
||||
system(f"awww img {folder}/{filename} --transition-type center")
|
||||
'';
|
||||
in {
|
||||
home.packages = [ wallpaper_changer ];
|
||||
|
||||
wayland.windowManager.hyprland = {
|
||||
in {
|
||||
settings = {
|
||||
config = {
|
||||
general = {
|
||||
@@ -192,6 +188,10 @@ in {
|
||||
{ leaf = "workspaces"; enabled = true; speed = 6; bezier = "default"; }
|
||||
];
|
||||
|
||||
on = [
|
||||
(hyprLua.onStart [ "${lib.getExe wallpaper_changer}" ])
|
||||
];
|
||||
|
||||
workspace_rule = [
|
||||
{
|
||||
workspace = "2";
|
||||
@@ -199,6 +199,12 @@ in {
|
||||
}
|
||||
];
|
||||
|
||||
bind = [
|
||||
(hyprLua.bind "Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m active -m output"))
|
||||
(hyprLua.bind "CTRL + Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m region"))
|
||||
(hyprLua.bind "ALT + Print" (hyprLua.exec "${lib.getExe pkgs.hyprshot} -z -o ~/Screenshots -m active -m window"))
|
||||
(hyprLua.bind "SUPER + W" (hyprLua.exec "${lib.getExe wallpaper_changer}"))
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
pragma Singleton
|
||||
|
||||
// reminders: what the clock rings for, what the calendar marks, and what the
|
||||
// widgets and the lock screen list as coming up
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// [{ id, year, month, day, hour, minute, name, repeat }]; repeat is
|
||||
// "" | "daily" | "weekdays" | "weekly", counted from the stored date
|
||||
property alias items: store.items
|
||||
property int tick: 0
|
||||
// reminders due and not yet dealt with, oldest first
|
||||
property var ringing: []
|
||||
readonly property var firing: root.ringing.length > 0 ? root.ringing[0] : null
|
||||
property var snoozedUntil: ({})
|
||||
property var dismissedAt: ({})
|
||||
property string checkedDay: ""
|
||||
// when this copy of the shell started, so a reload does not ring out loud
|
||||
readonly property real bornAt: Date.now()
|
||||
// changes once a minute; what the countdown text needs, instead of every tick
|
||||
property string minuteKey: ""
|
||||
|
||||
readonly property var next: {
|
||||
root.tick;
|
||||
CalendarSource.tick;
|
||||
return root.upcoming(1)[0] || null;
|
||||
}
|
||||
readonly property bool soon: {
|
||||
var n = root.next;
|
||||
return n !== null && n.at.getTime() - Loc.nowMs() <= 7 * 86400000;
|
||||
}
|
||||
|
||||
readonly property var repeatOptions: [
|
||||
{ "key": "", "label": "Once" },
|
||||
{ "key": "daily", "label": "Daily" },
|
||||
{ "key": "weekdays", "label": "Weekdays" },
|
||||
{ "key": "weekly", "label": "Weekly" }
|
||||
]
|
||||
|
||||
function dayStart(y, m, d) {
|
||||
return new Date(y, m, d, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// does this reminder happen on the given day?
|
||||
function occursOn(r, y, m, d) {
|
||||
var day = root.dayStart(y, m, d);
|
||||
var start = root.dayStart(r.year, r.month, r.day);
|
||||
if (day < start)
|
||||
return false;
|
||||
|
||||
switch (r.repeat || "") {
|
||||
case "daily":
|
||||
return true;
|
||||
case "weekdays":
|
||||
return day.getDay() !== 0 && day.getDay() !== 6;
|
||||
case "weekly":
|
||||
return day.getDay() === start.getDay();
|
||||
}
|
||||
return day.getTime() === start.getTime();
|
||||
}
|
||||
|
||||
// the next time a reminder rings at or after `from`
|
||||
// `next` re-reads this every second for every reminder, so it must not walk
|
||||
// the calendar: a one-off happens on exactly its own date, and daily,
|
||||
// weekdays and weekly all come round inside a week of whichever is later,
|
||||
// today or the date the repeat starts from
|
||||
function nextAt(r, from) {
|
||||
if (!r.repeat) {
|
||||
var at = new Date(r.year, r.month, r.day, r.hour, r.minute, 0, 0);
|
||||
return at >= from ? at : null;
|
||||
}
|
||||
var fromDay = root.dayStart(from.getFullYear(), from.getMonth(), from.getDate());
|
||||
var startDay = root.dayStart(r.year, r.month, r.day);
|
||||
var probe = startDay > fromDay ? startDay : fromDay;
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if (root.occursOn(r, probe.getFullYear(), probe.getMonth(), probe.getDate())) {
|
||||
var a = new Date(probe.getFullYear(), probe.getMonth(), probe.getDate(), r.hour, r.minute, 0, 0);
|
||||
if (a >= from)
|
||||
return a;
|
||||
|
||||
}
|
||||
probe.setDate(probe.getDate() + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function upcoming(limit) {
|
||||
var now = Loc.now();
|
||||
var out = [];
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
var at = root.nextAt(root.items[i], now);
|
||||
if (at)
|
||||
out.push({ "item": root.items[i], "at": at });
|
||||
|
||||
}
|
||||
out = out.concat(CalendarSource.upcoming());
|
||||
out.sort((a, b) => {
|
||||
return a.at - b.at;
|
||||
});
|
||||
return out.slice(0, limit || out.length);
|
||||
}
|
||||
|
||||
function forDay(y, m, d) {
|
||||
var local = root.items.filter((r) => {
|
||||
return root.occursOn(r, y, m, d);
|
||||
});
|
||||
var fromThunderbird = CalendarSource.forDay(y, m, d).map((event) => CalendarSource.asAgendaItem(event));
|
||||
return local.concat(fromThunderbird).sort((a, b) => {
|
||||
return (a.hour * 60 + a.minute) - (b.hour * 60 + b.minute);
|
||||
});
|
||||
}
|
||||
|
||||
function hasOn(y, m, d) {
|
||||
if (CalendarSource.hasOn(y, m, d))
|
||||
return true;
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
if (root.occursOn(root.items[i], y, m, d))
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function add(y, m, d, hour, minute, name, repeat) {
|
||||
root.items = root.items.concat([{
|
||||
"id": Date.now() + "-" + Math.random().toString(36).slice(2, 7),
|
||||
"year": y,
|
||||
"month": m,
|
||||
"day": d,
|
||||
"hour": hour,
|
||||
"minute": minute,
|
||||
"name": name,
|
||||
"repeat": repeat || ""
|
||||
}]);
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
root.items = root.items.filter((r) => {
|
||||
return r.id !== id;
|
||||
});
|
||||
root.ringing = root.ringing.filter((r) => {
|
||||
return r.id !== id;
|
||||
});
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
function timeText(r) {
|
||||
if (!r)
|
||||
return "";
|
||||
|
||||
if (Prefs.clock24h)
|
||||
return String(r.hour).padStart(2, "0") + ":" + String(r.minute).padStart(2, "0");
|
||||
|
||||
var h = r.hour % 12 === 0 ? 12 : r.hour % 12;
|
||||
return h + ":" + String(r.minute).padStart(2, "0") + " " + (r.hour < 12 ? "AM" : "PM");
|
||||
}
|
||||
|
||||
function untilText(at) {
|
||||
if (!at)
|
||||
return "";
|
||||
|
||||
var mins = Math.round((at - Loc.now()) / 60000);
|
||||
if (mins < 1)
|
||||
return "now";
|
||||
|
||||
if (mins < 60)
|
||||
return "in " + mins + " min";
|
||||
|
||||
var hrs = Math.round(mins / 60);
|
||||
if (hrs < 24)
|
||||
return "in " + hrs + (hrs === 1 ? " hour" : " hours");
|
||||
|
||||
var days = Math.round(hrs / 24);
|
||||
return days === 1 ? "tomorrow" : "in " + days + " days";
|
||||
}
|
||||
|
||||
function repeatText(r) {
|
||||
switch (r.repeat || "") {
|
||||
case "daily":
|
||||
return "Every day";
|
||||
case "weekdays":
|
||||
return "Weekdays";
|
||||
case "weekly":
|
||||
return "Every " + root.dayStart(r.year, r.month, r.day).toLocaleDateString(Qt.locale(), "dddd");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function snooze(id, minutes) {
|
||||
var m = Object.assign({}, root.snoozedUntil);
|
||||
m[id] = Loc.nowMs() + minutes * 60000;
|
||||
root.snoozedUntil = m;
|
||||
root.ringing = root.ringing.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
}
|
||||
|
||||
// a one-off goes once it has rung; a repeating one waits for its next turn
|
||||
function dismiss(id) {
|
||||
var m = Object.assign({}, root.dismissedAt);
|
||||
m[id] = Loc.nowMs();
|
||||
root.dismissedAt = m;
|
||||
root.ringing = root.ringing.filter((q) => {
|
||||
return q.id !== id;
|
||||
});
|
||||
var r = root.items.find((x) => {
|
||||
return x.id === id;
|
||||
});
|
||||
if (r && !r.repeat)
|
||||
root.remove(id);
|
||||
|
||||
}
|
||||
|
||||
function prune(now) {
|
||||
var today = root.dayStart(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
var kept = root.items.filter((r) => {
|
||||
return r.repeat || root.dayStart(r.year, r.month, r.day) >= today;
|
||||
});
|
||||
if (kept.length !== root.items.length)
|
||||
root.items = kept;
|
||||
|
||||
}
|
||||
|
||||
function check() {
|
||||
var now = Loc.now();
|
||||
var key = now.getFullYear() + "-" + now.getMonth() + "-" + now.getDate();
|
||||
if (root.checkedDay !== key) {
|
||||
root.prune(now);
|
||||
root.checkedDay = key;
|
||||
}
|
||||
var mkey = key + "-" + now.getHours() + "-" + now.getMinutes();
|
||||
if (root.minuteKey !== mkey)
|
||||
root.minuteKey = mkey;
|
||||
|
||||
var fresh = false;
|
||||
for (var i = 0; i < root.items.length; i++) {
|
||||
var r = root.items[i];
|
||||
if (root.ringing.some((q) => {
|
||||
return q.id === r.id;
|
||||
}))
|
||||
continue;
|
||||
|
||||
var until = root.snoozedUntil[r.id];
|
||||
if (until && now.getTime() < until)
|
||||
continue;
|
||||
|
||||
// today's occurrence, if there is one and it has come round
|
||||
if (!root.occursOn(r, now.getFullYear(), now.getMonth(), now.getDate()))
|
||||
continue;
|
||||
|
||||
var due = new Date(now.getFullYear(), now.getMonth(), now.getDate(), r.hour, r.minute, 0, 0);
|
||||
if (now < due)
|
||||
continue;
|
||||
|
||||
var last = root.dismissedAt[r.id];
|
||||
if (last && last >= due.getTime())
|
||||
continue;
|
||||
|
||||
// an hour late is too late to ring
|
||||
if (!until && now.getTime() - due.getTime() > 3600000)
|
||||
continue;
|
||||
|
||||
root.ringing = root.ringing.concat([r]);
|
||||
// a reload forgets dismissals, so one already overdue as the shell
|
||||
// starts rings without a sound; after that, one that came due while
|
||||
// the machine slept still gets it
|
||||
if (until || Date.now() - root.bornAt > 5000 || now.getTime() - due.getTime() < 60000)
|
||||
fresh = true;
|
||||
|
||||
}
|
||||
if (fresh && Prefs.reminderSound)
|
||||
Sounds.play("reminder", 1);
|
||||
|
||||
root.tick++;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: root.check()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveDebounce
|
||||
|
||||
interval: 300
|
||||
onTriggered: file.writeAdapter()
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: file
|
||||
|
||||
path: Quickshell.env("HOME") + "/.config/quickshell/lucidbar/clock_reminders.json"
|
||||
blockLoading: true
|
||||
onAdapterUpdated: saveDebounce.restart()
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: store
|
||||
|
||||
property var items: []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
pragma Singleton
|
||||
|
||||
// Read-only events already synchronized by Thunderbird. This deliberately does
|
||||
// not connect to DAV or store credentials; Thunderbird remains the DAV client.
|
||||
Singleton {
|
||||
id: source
|
||||
|
||||
property var events: []
|
||||
property int tick: 0
|
||||
property string error: ""
|
||||
property real updatedAt: 0
|
||||
|
||||
function refresh() {
|
||||
if (reader.running)
|
||||
return;
|
||||
reader.command = ["python3", Quickshell.env("HOME") + "/.config/quickshell/thunderbird-calendar.py"];
|
||||
reader.running = true;
|
||||
}
|
||||
|
||||
function forDay(year, month, day) {
|
||||
source.tick;
|
||||
return source.events.filter((event) => {
|
||||
const date = new Date(event.startMs);
|
||||
return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day;
|
||||
});
|
||||
}
|
||||
|
||||
function upcoming(limit) {
|
||||
source.tick;
|
||||
const now = Loc.nowMs();
|
||||
return source.events.filter((event) => event.startMs >= now).slice(0, limit || source.events.length).map((event) => ({
|
||||
"item": source.asAgendaItem(event),
|
||||
"at": new Date(event.startMs)
|
||||
}));
|
||||
}
|
||||
|
||||
function hasOn(year, month, day) {
|
||||
return source.forDay(year, month, day).length > 0;
|
||||
}
|
||||
|
||||
function asAgendaItem(event) {
|
||||
const date = new Date(event.startMs);
|
||||
return {
|
||||
"id": "thunderbird:" + event.id,
|
||||
"name": event.name,
|
||||
"calendar": event.calendar,
|
||||
"source": "thunderbird",
|
||||
"startMs": event.startMs,
|
||||
"endMs": event.endMs,
|
||||
"hour": date.getHours(),
|
||||
"minute": date.getMinutes(),
|
||||
"repeat": ""
|
||||
};
|
||||
}
|
||||
|
||||
Process {
|
||||
id: reader
|
||||
|
||||
command: []
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (this.text.trim() === "")
|
||||
return;
|
||||
try {
|
||||
const result = JSON.parse(this.text);
|
||||
if (!result.error) {
|
||||
source.events = result.events || [];
|
||||
source.error = "";
|
||||
source.updatedAt = Date.now();
|
||||
source.tick++;
|
||||
} else {
|
||||
source.error = result.error;
|
||||
}
|
||||
} catch (e) {
|
||||
source.error = "Could not read Thunderbird calendar cache";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (code) => {
|
||||
if (code !== 0 && source.error === "")
|
||||
source.error = "Could not read Thunderbird calendar cache";
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 300000
|
||||
repeat: true
|
||||
running: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: source.refresh()
|
||||
}
|
||||
}
|
||||
@@ -1,536 +0,0 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.lucidui
|
||||
|
||||
// the month on the left, the picked day's reminders and a way to add one on the right
|
||||
Item {
|
||||
id: page
|
||||
|
||||
property var host: null
|
||||
property var selected: {
|
||||
const n = Loc.now();
|
||||
return { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
}
|
||||
readonly property date selectedDate: new Date(page.selected.year, page.selected.month, page.selected.day)
|
||||
readonly property var dayItems: {
|
||||
Agenda.checkedDay;
|
||||
return Agenda.forDay(page.selected.year, page.selected.month, page.selected.day);
|
||||
}
|
||||
property int newHour: 9
|
||||
property int newMinute: 0
|
||||
// the hour is freshTime's next-hour guess, not one anybody chose
|
||||
property bool hourGuessed: false
|
||||
// am/pm was set by hand, so a typed hour keeps to it
|
||||
property bool halfPicked: false
|
||||
property string newRepeat: ""
|
||||
readonly property bool pm: page.newHour >= 12
|
||||
readonly property int shownHour: Prefs.clock24h ? page.newHour : (page.newHour % 12 === 0 ? 12 : page.newHour % 12)
|
||||
// a one-off set before now would never ring
|
||||
readonly property bool past: {
|
||||
Agenda.minuteKey;
|
||||
return page.newRepeat === "" && page.gone(page.newHour, page.newMinute);
|
||||
}
|
||||
readonly property bool shown: page.visible && (!page.host || page.host.expanded)
|
||||
|
||||
onShownChanged: if (page.shown)
|
||||
page.freshTime()
|
||||
|
||||
function selectToday() {
|
||||
const n = Loc.now();
|
||||
page.selected = { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
month.goToday();
|
||||
page.freshTime();
|
||||
}
|
||||
|
||||
// earlier than the minute we are in, on the picked day
|
||||
function gone(hour, minute) {
|
||||
const n = Loc.now();
|
||||
return new Date(page.selected.year, page.selected.month, page.selected.day, hour, minute) < new Date(n.getFullYear(), n.getMonth(), n.getDate(), n.getHours(), n.getMinutes());
|
||||
}
|
||||
|
||||
function isToday() {
|
||||
return page.gone(0, 0) && !page.gone(23, 59);
|
||||
}
|
||||
|
||||
// every opening of the clock starts on today, unless a reminder is half written
|
||||
function startVisit() {
|
||||
if (nameField.text !== "")
|
||||
return ;
|
||||
|
||||
const n = Loc.now();
|
||||
page.selected = { "year": n.getFullYear(), "month": n.getMonth(), "day": n.getDate() };
|
||||
if (!month.isCurrent)
|
||||
month.goToday();
|
||||
|
||||
page.freshTime();
|
||||
}
|
||||
|
||||
// 9:00, or the next hour once 9:00 has gone today; a time already set for a name stays
|
||||
function freshTime() {
|
||||
if (nameField.text !== "")
|
||||
return ;
|
||||
|
||||
page.newHour = 9;
|
||||
page.newMinute = 0;
|
||||
page.hourGuessed = false;
|
||||
page.halfPicked = false;
|
||||
const next = Loc.now().getHours() + 1;
|
||||
if (page.gone(9, 0) && next < 24 && !page.gone(next, 0)) {
|
||||
page.newHour = next;
|
||||
page.hourGuessed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// a typed 12-hour time means the half still to come: 12:04 at noon is pm
|
||||
function preferUpcoming() {
|
||||
if (Prefs.clock24h || !page.past)
|
||||
return ;
|
||||
|
||||
const flipped = (page.newHour + 12) % 24;
|
||||
if (!page.gone(flipped, page.newMinute))
|
||||
page.newHour = flipped;
|
||||
|
||||
}
|
||||
|
||||
function focusName() {
|
||||
nameField.input.forceActiveFocus();
|
||||
}
|
||||
|
||||
function submit() {
|
||||
const name = nameField.text.trim();
|
||||
if (name === "" || page.past)
|
||||
return ;
|
||||
|
||||
Agenda.add(page.selected.year, page.selected.month, page.selected.day, page.newHour, page.newMinute, name, page.newRepeat);
|
||||
nameField.text = "";
|
||||
page.newRepeat = "";
|
||||
}
|
||||
|
||||
function step(minutes) {
|
||||
page.hourGuessed = false;
|
||||
let t = (page.newHour * 60 + page.newMinute + minutes + 1440) % 1440;
|
||||
page.newHour = Math.floor(t / 60);
|
||||
page.newMinute = t % 60;
|
||||
}
|
||||
|
||||
// typed hours are 0-23, or 1-12 next to the am/pm toggle; on today a
|
||||
// 12-hour one is its next turn, not the half the guessed hour left behind
|
||||
function typeHour(h) {
|
||||
page.hourGuessed = false;
|
||||
if (Prefs.clock24h) {
|
||||
page.newHour = Math.min(23, h);
|
||||
} else if (page.isToday() && !page.halfPicked) {
|
||||
// the minutes are still to come, so only an hour wholly gone is past
|
||||
const h12 = Math.min(12, h) % 12;
|
||||
page.newHour = page.gone(h12, 59) ? h12 + 12 : h12;
|
||||
} else {
|
||||
page.newHour = Math.min(12, h) % 12 + (page.pm ? 12 : 0);
|
||||
page.preferUpcoming();
|
||||
}
|
||||
}
|
||||
|
||||
// minutes typed under the guessed hour mean the next time the clock shows
|
||||
// them: at 12:19, 20 is 12:20 rather than 1:20
|
||||
function typeMinute(m) {
|
||||
page.newMinute = Math.min(59, m);
|
||||
if (page.hourGuessed && page.isToday()) {
|
||||
const h = Loc.now().getHours();
|
||||
if (!page.gone(h, page.newMinute))
|
||||
page.newHour = h;
|
||||
else if (h + 1 < 24)
|
||||
page.newHour = h + 1;
|
||||
}
|
||||
page.preferUpcoming();
|
||||
}
|
||||
|
||||
function setPm(wantPm) {
|
||||
page.hourGuessed = false;
|
||||
page.halfPicked = true;
|
||||
page.newHour = page.newHour % 12 + (wantPm ? 12 : 0);
|
||||
}
|
||||
|
||||
// two digits you type into, each holding its own value until focus leaves
|
||||
component TimeDigit: Item {
|
||||
id: digit
|
||||
|
||||
property int value: 0
|
||||
property int span: 60
|
||||
property Item nextDigit: null
|
||||
readonly property bool typing: field.activeFocus
|
||||
|
||||
signal typed(int v)
|
||||
signal meridiem(bool wantPm)
|
||||
signal accepted()
|
||||
|
||||
function show() {
|
||||
field.text = String(digit.value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function take() {
|
||||
field.forceActiveFocus();
|
||||
field.selectAll();
|
||||
}
|
||||
|
||||
function nudge(by) {
|
||||
digit.typed((digit.value + by + digit.span) % digit.span);
|
||||
digit.show();
|
||||
}
|
||||
|
||||
width: Theme.dp(30)
|
||||
height: Theme.dp(30)
|
||||
onValueChanged: if (!field.activeFocus)
|
||||
digit.show()
|
||||
Component.onCompleted: digit.show()
|
||||
|
||||
TextInput {
|
||||
id: field
|
||||
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
maximumLength: 2
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
selectByMouse: true
|
||||
color: Theme.text
|
||||
selectionColor: Theme.alpha(Theme.primary, 0.35)
|
||||
selectedTextColor: Theme.text
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.typeSize("labelLarge")
|
||||
font.variableAxes: Theme.axes(Theme.typeSize("labelLarge"), 640, 0)
|
||||
font.features: ({
|
||||
"tnum": 1
|
||||
})
|
||||
onTextEdited: {
|
||||
if (field.text !== "")
|
||||
digit.typed(parseInt(field.text, 10));
|
||||
|
||||
if (field.text.length === 2 && digit.nextDigit)
|
||||
digit.nextDigit.take();
|
||||
|
||||
}
|
||||
onActiveFocusChanged: {
|
||||
if (field.activeFocus)
|
||||
Qt.callLater(field.selectAll);
|
||||
else
|
||||
digit.show();
|
||||
}
|
||||
onAccepted: digit.accepted()
|
||||
Keys.onUpPressed: digit.nudge(1)
|
||||
Keys.onDownPressed: digit.nudge(-1)
|
||||
Keys.onTabPressed: digit.nextDigit ? digit.nextDigit.take() : digit.show()
|
||||
Keys.onPressed: (e) => {
|
||||
const c = e.text.toLowerCase();
|
||||
if (c === "a" || c === "p")
|
||||
digit.meridiem(c === "p");
|
||||
|
||||
}
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /[0-9]{0,2}/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - Theme.dp(6)
|
||||
height: Theme.dp(2)
|
||||
radius: 1
|
||||
color: digit.typing ? Theme.primary : Theme.alpha(Theme.subtext, 0.4)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.durFastEffects
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
implicitHeight: Theme.dp(404)
|
||||
|
||||
Connections {
|
||||
function onExpandedChanged() {
|
||||
if (page.host.expanded)
|
||||
page.startVisit();
|
||||
|
||||
}
|
||||
|
||||
target: page.host
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: monthCard
|
||||
|
||||
width: Theme.dp(312)
|
||||
height: parent.height
|
||||
radius: Theme.shapeXl
|
||||
color: Theme.withBlur(Theme.surfaceHigh)
|
||||
|
||||
MonthView {
|
||||
id: month
|
||||
|
||||
anchors.centerIn: parent
|
||||
cell: Theme.dp(40)
|
||||
selected: page.selected
|
||||
onPicked: (y, m, d) => {
|
||||
page.selected = { "year": y, "month": m, "day": d };
|
||||
if (m !== month.monthIndex)
|
||||
month.page(y * 12 + m > month.year * 12 + month.monthIndex ? 1 : -1);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: monthCard.right
|
||||
anchors.leftMargin: Theme.dp(12)
|
||||
anchors.right: parent.right
|
||||
height: parent.height
|
||||
radius: Theme.shapeXl
|
||||
color: Theme.withBlur(Theme.surfaceHigh)
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.dp(18)
|
||||
|
||||
Column {
|
||||
id: dayHead
|
||||
|
||||
width: parent.width
|
||||
spacing: 0
|
||||
|
||||
LText {
|
||||
role: "titleLarge"
|
||||
weight: 560
|
||||
text: page.selectedDate.toLocaleDateString(Qt.locale(), "dddd")
|
||||
}
|
||||
|
||||
LText {
|
||||
role: "bodyMedium"
|
||||
color: Theme.subtext
|
||||
text: page.selectedDate.toLocaleDateString(Qt.locale(), "d MMMM yyyy") + (page.dayItems.length ? " · " + page.dayItems.length + (page.dayItems.length === 1 ? " reminder" : " reminders") : "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: list
|
||||
|
||||
anchors.top: dayHead.bottom
|
||||
anchors.topMargin: Theme.dp(12)
|
||||
anchors.bottom: form.top
|
||||
anchors.bottomMargin: Theme.dp(12)
|
||||
width: parent.width
|
||||
contentHeight: items.implicitHeight
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: items
|
||||
|
||||
width: list.width
|
||||
spacing: Theme.dp(2)
|
||||
|
||||
Repeater {
|
||||
model: page.dayItems
|
||||
|
||||
ListItem {
|
||||
id: row
|
||||
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: items.width
|
||||
first: row.index === 0
|
||||
last: row.index === page.dayItems.length - 1
|
||||
containerColor: Theme.withBlur(Theme.surfaceHighest)
|
||||
clickable: false
|
||||
headline: row.modelData.name
|
||||
supporting: Agenda.timeText(row.modelData) + (row.modelData.repeat ? " · " + Agenda.repeatText(row.modelData) : "") + (row.modelData.source === "thunderbird" ? " · " + row.modelData.calendar : "")
|
||||
icon: row.modelData.source === "thunderbird" ? "event" : (row.modelData.repeat ? "repeat" : "notifications")
|
||||
iconColor: Theme.primary
|
||||
trailing: [
|
||||
IconButton {
|
||||
visible: row.modelData.source !== "thunderbird"
|
||||
icon: "delete"
|
||||
size: "xs"
|
||||
onClicked: Agenda.remove(row.modelData.id)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
visible: page.dayItems.length === 0
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: Theme.dp(24)
|
||||
spacing: Theme.dp(8)
|
||||
|
||||
Icon {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
name: "event_available"
|
||||
size: Theme.dp(32)
|
||||
color: Theme.subtextDim
|
||||
}
|
||||
|
||||
LText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
role: "bodyMedium"
|
||||
color: Theme.subtext
|
||||
text: "A free day"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Column {
|
||||
id: form
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
width: parent.width
|
||||
spacing: Theme.dp(10)
|
||||
|
||||
TextField {
|
||||
id: nameField
|
||||
|
||||
width: parent.width
|
||||
label: "New reminder"
|
||||
placeholder: "What should it say?"
|
||||
supporting: page.past && nameField.text.trim() !== "" ? (page.gone(0, 0) && !page.gone(23, 59) ? Agenda.timeText({ "hour": page.newHour, "minute": page.newMinute }) + " has already gone by today" : "That day has already gone by") : ""
|
||||
containerColor: Theme.withBlur(Theme.surfaceHighest)
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
width: parent.width
|
||||
size: "xs"
|
||||
options: Agenda.repeatOptions
|
||||
current: page.newRepeat
|
||||
onPicked: (k) => {
|
||||
return page.newRepeat = k;
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.dp(8)
|
||||
|
||||
Rectangle {
|
||||
id: timeBox
|
||||
|
||||
width: timeRow.implicitWidth + Theme.dp(20)
|
||||
height: Theme.dp(36)
|
||||
radius: Theme.dp(18)
|
||||
color: Theme.withBlur(Theme.surfaceHighest)
|
||||
|
||||
Row {
|
||||
id: timeRow
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.dp(2)
|
||||
|
||||
TimeDigit {
|
||||
id: hourDigit
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
value: page.shownHour
|
||||
span: Prefs.clock24h ? 24 : 12
|
||||
nextDigit: minuteDigit
|
||||
onTyped: (v) => {
|
||||
return page.typeHour(v);
|
||||
}
|
||||
onMeridiem: (wantPm) => {
|
||||
return page.setPm(wantPm);
|
||||
}
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
LText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
role: "labelLarge"
|
||||
weight: 640
|
||||
color: Theme.subtext
|
||||
text: ":"
|
||||
}
|
||||
|
||||
TimeDigit {
|
||||
id: minuteDigit
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
value: page.newMinute
|
||||
span: 60
|
||||
onTyped: (v) => {
|
||||
return page.typeMinute(v);
|
||||
}
|
||||
onMeridiem: (wantPm) => {
|
||||
return page.setPm(wantPm);
|
||||
}
|
||||
onAccepted: page.submit()
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: !Prefs.clock24h
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Theme.dp(32)
|
||||
height: Theme.dp(30)
|
||||
|
||||
LText {
|
||||
anchors.centerIn: parent
|
||||
role: "labelMedium"
|
||||
weight: 640
|
||||
color: Theme.primary
|
||||
text: page.pm ? "PM" : "AM"
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
radius: Theme.rad(8)
|
||||
onClicked: page.setPm(!page.pm)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the wheel moves a minute at a time
|
||||
WheelHandler {
|
||||
onWheel: (e) => {
|
||||
return page.step(e.angleDelta.y > 0 ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - timeBox.width - addBtn.width - Theme.dp(16)
|
||||
height: 1
|
||||
}
|
||||
|
||||
Button {
|
||||
id: addBtn
|
||||
|
||||
variant: "filled"
|
||||
size: "xs"
|
||||
icon: "add"
|
||||
text: "Add"
|
||||
disabled: nameField.text.trim() === "" || page.past
|
||||
onClicked: page.submit()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
specialCustom = builtins.toJSON [ {
|
||||
key = "daemons";
|
||||
label = "Daemons";
|
||||
glyph = "build";
|
||||
on = true;
|
||||
apps = "";
|
||||
} ];
|
||||
in {
|
||||
lucid.keybindSeed = ./lucid-keybinds.json;
|
||||
lucid.prefsSeed = pkgs.writeText "sweetbread-lucid-prefs.json" (builtins.toJSON (
|
||||
(builtins.fromJSON (builtins.readFile ./lucid-prefs.json)) // {
|
||||
specialCustom = specialCustom;
|
||||
}
|
||||
));
|
||||
lucid.startWallpaper = true;
|
||||
lucid.installWallpaperSupport = true;
|
||||
|
||||
lucid.managedFiles = {
|
||||
"${config.home.homeDirectory}/.config/quickshell/Agenda.qml" = ./Agenda.qml;
|
||||
"${config.home.homeDirectory}/.config/quickshell/CalendarSource.qml" = ./CalendarSource.qml;
|
||||
"${config.home.homeDirectory}/.config/quickshell/lucidbar/ClockCalendar.qml" = ./ClockCalendar.qml;
|
||||
"${config.home.homeDirectory}/.config/quickshell/thunderbird-calendar.py" = ./thunderbird-calendar.py;
|
||||
};
|
||||
|
||||
wayland.windowManager.hyprland.settings.bind = lib.mkForce [];
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"binds": [
|
||||
{ "id": "wallpaper-picker", "keys": "SUPER + W", "desc": "Wallpaper picker", "category": "Lucid", "type": "exec", "cmd": "hyprctl eval 'hl.config({ input = { follow_mouse = 2 } })' && qs ipc call launcher wallpaper" },
|
||||
{ "id": "wallpaper-random", "keys": "SUPER + SHIFT + W", "desc": "Set a random wallpaper", "category": "Desktop", "type": "exec", "cmd": "wallpaper_changer" },
|
||||
{ "id": "calculator-volume", "keys": "XF86Calculator", "desc": "Open volume mixer", "category": "Apps", "type": "exec", "cmd": "ghostty --title=pulsemixer -e pulsemixer" },
|
||||
{ "id": "zoom-reset-scroll-up", "keys": "SUPER + SHIFT + mouse_up", "desc": "Reset cursor zoom", "category": "Accessibility", "type": "lua", "lua": "function() hl.config({ cursor = { zoom_factor = 1 } }) end" },
|
||||
{ "id": "zoom-reset-scroll-down", "keys": "SUPER + SHIFT + mouse_down", "desc": "Reset cursor zoom", "category": "Accessibility", "type": "lua", "lua": "function() hl.config({ cursor = { zoom_factor = 1 } }) end" },
|
||||
{ "id": "zoom-reset-minus", "keys": "SUPER + SHIFT + minus", "desc": "Reset cursor zoom", "category": "Accessibility", "type": "lua", "lua": "function() hl.config({ cursor = { zoom_factor = 1 } }) end" },
|
||||
{ "id": "zoom-reset-kp-subtract", "keys": "SUPER + SHIFT + KP_SUBTRACT", "desc": "Reset cursor zoom", "category": "Accessibility", "type": "lua", "lua": "function() hl.config({ cursor = { zoom_factor = 1 } }) end" },
|
||||
{ "id": "zoom-reset-zero", "keys": "SUPER + SHIFT + 0", "desc": "Reset cursor zoom", "category": "Accessibility", "type": "lua", "lua": "function() hl.config({ cursor = { zoom_factor = 1 } }) end" },
|
||||
{ "id": "zoom-in-equal", "keys": "SUPER + equal", "desc": "Zoom in", "category": "Accessibility", "type": "lua", "lua": "function() local current = hl.get_config('cursor.zoom_factor'); hl.config({ cursor = { zoom_factor = current * 1.1 } }) end", "opts": { "repeating": true } },
|
||||
{ "id": "zoom-out-minus", "keys": "SUPER + minus", "desc": "Zoom out", "category": "Accessibility", "type": "lua", "lua": "function() local current = hl.get_config('cursor.zoom_factor'); hl.config({ cursor = { zoom_factor = math.max(1, current * 0.9) } }) end", "opts": { "repeating": true } },
|
||||
{ "id": "zoom-in-kp-add", "keys": "SUPER + KP_ADD", "desc": "Zoom in", "category": "Accessibility", "type": "lua", "lua": "function() local current = hl.get_config('cursor.zoom_factor'); hl.config({ cursor = { zoom_factor = current * 1.1 } }) end", "opts": { "repeating": true } },
|
||||
{ "id": "zoom-out-kp-subtract", "keys": "SUPER + KP_SUBTRACT", "desc": "Zoom out", "category": "Accessibility", "type": "lua", "lua": "function() local current = hl.get_config('cursor.zoom_factor'); hl.config({ cursor = { zoom_factor = math.max(1, current * 0.9) } }) end", "opts": { "repeating": true } },
|
||||
{ "id": "widgets-panel", "keys": "SUPER + SHIFT + G", "desc": "Toggle widgets panel", "category": "Lucid", "type": "exec", "cmd": "qs ipc call widgets panel" },
|
||||
{ "id": "launcher", "keys": "SUPER + D", "desc": "App launcher", "category": "Lucid", "type": "exec", "cmd": "hyprctl eval 'hl.config({ input = { follow_mouse = 2 } })' && qs ipc call launcher toggle" },
|
||||
{ "id": "vaultwarden-password", "keys": "SUPER + X", "desc": "Type a password from Vaultwarden", "category": "Apps", "type": "exec", "cmd": "qs ipc call -- launcher search '>pass '" },
|
||||
{ "id": "clipse", "keys": "SUPER + V", "desc": "Clipboard manager", "category": "Apps", "type": "exec", "cmd": "ghostty --title=clipse -e clipse" },
|
||||
{ "id": "clipboard", "keys": "SUPER + SHIFT + V", "desc": "Clipboard history", "category": "Lucid", "type": "exec", "cmd": "hyprctl eval 'hl.config({ input = { follow_mouse = 2 } })' && qs ipc call launcher clipboard" },
|
||||
{ "id": "screenshot", "keys": "Print", "desc": "Full screenshot", "category": "Screenshots", "type": "exec", "cmd": "qs ipc call screenshot full" },
|
||||
{ "id": "screenshot-full", "keys": "SUPER + Print", "desc": "Full screenshot", "category": "Screenshots", "type": "exec", "cmd": "qs ipc call screenshot full" },
|
||||
{ "id": "screenshot-region", "keys": "CTRL + Print", "desc": "Region screenshot", "category": "Screenshots", "type": "exec", "cmd": "qs ipc call snap region" },
|
||||
{ "id": "screenshot-window", "keys": "ALT + Print", "desc": "Window screenshot", "category": "Screenshots", "type": "exec", "cmd": "qs ipc call snap window" },
|
||||
{ "id": "terminal", "keys": "SUPER + RETURN", "desc": "Terminal", "category": "Apps", "type": "exec", "cmd": "ghostty" },
|
||||
{ "id": "close-window", "keys": "SUPER + Q", "desc": "Close window", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.close()" },
|
||||
{ "id": "exit-hyprland", "keys": "SUPER + M", "desc": "Exit Hyprland", "category": "Session", "type": "lua", "lua": "hl.dsp.exit()" },
|
||||
{ "id": "file-manager", "keys": "SUPER + E", "desc": "File manager", "category": "Apps", "type": "exec", "cmd": "ghostty -e sh -c yazi" },
|
||||
{ "id": "toggle-float", "keys": "SUPER + F", "desc": "Toggle floating window", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.float({ action = 'toggle' })" },
|
||||
{ "id": "toggle-pseudo", "keys": "SUPER + P", "desc": "Toggle pseudo-tile", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.pseudo({ action = 'toggle' })" },
|
||||
{ "id": "toggle-split", "keys": "SUPER + J", "desc": "Toggle split direction", "category": "Windows", "type": "lua", "lua": "hl.dsp.layout('togglesplit')" },
|
||||
{ "id": "swap-split", "keys": "SUPER + K", "desc": "Swap split direction", "category": "Windows", "type": "lua", "lua": "hl.dsp.layout('swapsplit')" },
|
||||
{ "id": "focus-left", "keys": "SUPER + LEFT", "desc": "Focus left", "category": "Windows", "type": "lua", "lua": "hl.dsp.focus({ direction = 'l' })" },
|
||||
{ "id": "focus-right", "keys": "SUPER + RIGHT", "desc": "Focus right", "category": "Windows", "type": "lua", "lua": "hl.dsp.focus({ direction = 'r' })" },
|
||||
{ "id": "focus-up", "keys": "SUPER + UP", "desc": "Focus up", "category": "Windows", "type": "lua", "lua": "hl.dsp.focus({ direction = 'u' })" },
|
||||
{ "id": "focus-down", "keys": "SUPER + DOWN", "desc": "Focus down", "category": "Windows", "type": "lua", "lua": "hl.dsp.focus({ direction = 'd' })" },
|
||||
{ "id": "swap-left", "keys": "SUPER + SHIFT + LEFT", "desc": "Move window left", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.swap({ direction = 'l' })" },
|
||||
{ "id": "swap-right", "keys": "SUPER + SHIFT + RIGHT", "desc": "Move window right", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.swap({ direction = 'r' })" },
|
||||
{ "id": "swap-up", "keys": "SUPER + SHIFT + UP", "desc": "Move window up", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.swap({ direction = 'u' })" },
|
||||
{ "id": "swap-down", "keys": "SUPER + SHIFT + DOWN", "desc": "Move window down", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.swap({ direction = 'd' })" },
|
||||
{ "id": "resize-left", "keys": "SUPER + CTRL + LEFT", "desc": "Resize window left", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.resize({ x = -60, y = 0, relative = true })" },
|
||||
{ "id": "resize-right", "keys": "SUPER + CTRL + RIGHT", "desc": "Resize window right", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.resize({ x = 60, y = 0, relative = true })" },
|
||||
{ "id": "resize-up", "keys": "SUPER + CTRL + UP", "desc": "Resize window up", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.resize({ x = 0, y = -60, relative = true })" },
|
||||
{ "id": "resize-down", "keys": "SUPER + CTRL + DOWN", "desc": "Resize window down", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.resize({ x = 0, y = 60, relative = true })" },
|
||||
{ "id": "workspace-focus", "keys": "SUPER + {key}", "desc": "Focus workspace {n}", "category": "Workspaces", "type": "lua", "lua": "hl.dsp.focus({ workspace = {n} })", "each": "workspace" },
|
||||
{ "id": "workspace-move", "keys": "SUPER + SHIFT + {key}", "desc": "Move window to workspace {n}", "category": "Workspaces", "type": "lua", "lua": "hl.dsp.window.move({ workspace = {n}, follow = false })", "each": "workspace" },
|
||||
{ "id": "scratchpad", "keys": "SUPER + ALT + P", "desc": "Toggle scratchpad", "category": "Workspaces", "type": "lua", "lua": "specials.scratchpad()" },
|
||||
{ "id": "scratchpad-stash", "keys": "SUPER + ALT + S", "desc": "Stash or restore the focused window", "category": "Workspaces", "type": "lua", "lua": "specials.stash()" },
|
||||
{ "id": "special-music", "keys": "SUPER + ALT + M", "desc": "Toggle Music workspace", "category": "Workspaces", "type": "lua", "lua": "specials.toggle('music')" },
|
||||
{ "id": "special-comms", "keys": "SUPER + ALT + C", "desc": "Toggle Comms workspace", "category": "Workspaces", "type": "lua", "lua": "specials.toggle('comms')" },
|
||||
{ "id": "special-todo", "keys": "SUPER + ALT + T", "desc": "Toggle To-do workspace", "category": "Workspaces", "type": "lua", "lua": "specials.toggle('todo')" },
|
||||
{ "id": "special-sysmon", "keys": "SUPER + ALT + B", "desc": "Toggle system monitor workspace", "category": "Workspaces", "type": "lua", "lua": "specials.toggle('sysmon')" },
|
||||
{ "id": "daemons-workspace", "keys": "SUPER + ALT + D", "desc": "Daemons workspace", "category": "Workspaces", "type": "lua", "lua": "specials.toggle('daemons')" },
|
||||
{ "id": "fullscreen", "keys": "SUPER + SHIFT + F", "desc": "Toggle fullscreen", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.fullscreen({ action = 'toggle', mode = 'fullscreen' })" },
|
||||
{ "id": "workspace-next-scroll", "keys": "SUPER + mouse_down", "desc": "Next workspace", "category": "Workspaces", "type": "lua", "lua": "hl.dsp.focus({ workspace = 'e-1' })" },
|
||||
{ "id": "workspace-previous-scroll", "keys": "SUPER + mouse_up", "desc": "Previous workspace", "category": "Workspaces", "type": "lua", "lua": "hl.dsp.focus({ workspace = 'e+1' })" },
|
||||
{ "id": "keyboard-brightness-up", "keys": "SUPER + F3", "desc": "Keyboard backlight up", "category": "Displays", "type": "exec", "cmd": "brightnessctl -d *::kbd_backlight set +33%" },
|
||||
{ "id": "keyboard-brightness-down", "keys": "SUPER + F2", "desc": "Keyboard backlight down", "category": "Displays", "type": "exec", "cmd": "brightnessctl -d *::kbd_backlight set 33%-" },
|
||||
{ "id": "audio-mute", "keys": "XF86AudioMute", "desc": "Toggle audio mute", "category": "Media", "type": "exec", "cmd": "pamixer -t" },
|
||||
{ "id": "microphone-mute", "keys": "XF86AudioMicMute", "desc": "Toggle microphone mute", "category": "Media", "type": "exec", "cmd": "pamixer --default-source -t" },
|
||||
{ "id": "play-pause", "keys": "XF86AudioPlay", "desc": "Play or pause media", "category": "Media", "type": "exec", "cmd": "playerctl play-pause" },
|
||||
{ "id": "file-manager-media", "keys": "XF86Explorer", "desc": "File manager", "category": "Apps", "type": "exec", "cmd": "ghostty -e sh -c yazi" },
|
||||
{ "id": "email", "keys": "XF86Mail", "desc": "Email", "category": "Apps", "type": "exec", "cmd": "thunderbird" },
|
||||
{ "id": "browser", "keys": "XF86WWW", "desc": "Web browser", "category": "Apps", "type": "exec", "cmd": "google-chrome-stable" },
|
||||
{ "id": "display-brightness-down", "keys": "XF86MonBrightnessDown", "desc": "Brightness down", "category": "Displays", "type": "exec", "cmd": "brightnessctl set 5%-" },
|
||||
{ "id": "display-brightness-up", "keys": "XF86MonBrightnessUp", "desc": "Brightness up", "category": "Displays", "type": "exec", "cmd": "brightnessctl set +5%" },
|
||||
{ "id": "volume-up", "keys": "XF86AudioRaiseVolume", "desc": "Volume up", "category": "Media", "type": "exec", "cmd": "pamixer -i 5", "opts": { "repeating": true } },
|
||||
{ "id": "volume-down", "keys": "XF86AudioLowerVolume", "desc": "Volume down", "category": "Media", "type": "exec", "cmd": "pamixer -d 5", "opts": { "repeating": true } },
|
||||
{ "id": "previous-seek", "keys": "XF86AudioPrev", "desc": "Seek backward", "category": "Media", "type": "exec", "cmd": "playerctl position 5-", "opts": { "click": true } },
|
||||
{ "id": "next-seek", "keys": "XF86AudioNext", "desc": "Seek forward", "category": "Media", "type": "exec", "cmd": "playerctl position 5+", "opts": { "click": true } },
|
||||
{ "id": "previous-track", "keys": "XF86AudioPrev", "desc": "Previous track", "category": "Media", "type": "exec", "cmd": "playerctl previous", "opts": { "long_press": true } },
|
||||
{ "id": "next-track", "keys": "XF86AudioNext", "desc": "Next track", "category": "Media", "type": "exec", "cmd": "playerctl next", "opts": { "long_press": true } },
|
||||
{ "id": "move-window-drag", "keys": "SUPER + mouse:272", "desc": "Move window with mouse", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.drag()", "opts": { "mouse": true } },
|
||||
{ "id": "resize-window-drag", "keys": "SUPER + mouse:273", "desc": "Resize window with mouse", "category": "Windows", "type": "lua", "lua": "hl.dsp.window.resize()", "opts": { "mouse": true } }
|
||||
]
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
{
|
||||
"accentPunch": 1,
|
||||
"audioCombine": "",
|
||||
"audioMeters": true,
|
||||
"audioMoveStreams": true,
|
||||
"autoColorMode": false,
|
||||
"barAutoHide": false,
|
||||
"barEnabled": true,
|
||||
"barFullCorner": 18,
|
||||
"barHeight": 35,
|
||||
"barHoverGrow": 3,
|
||||
"barHoverOpen": true,
|
||||
"barJoinDividers": false,
|
||||
"barJoins": "[]",
|
||||
"barLayout": "{\"left\":[\"start\",\"workspaces\",\"media\",\"tray\"],\"center\":[\"clock\"],\"right\":[\"window\",\"notifications\",\"system\",\"privacy\",\"power\"]}",
|
||||
"barMotionScale": 1.35,
|
||||
"barNotchFlare": 14,
|
||||
"barPopupGap": 10,
|
||||
"barPopupMode": true,
|
||||
"barPopupNotched": false,
|
||||
"barSideMargin": 17,
|
||||
"barSpacing": 8,
|
||||
"barStyle": "full",
|
||||
"barTopMargin": 5,
|
||||
"barZoneGap": 26,
|
||||
"btAgent": true,
|
||||
"btReceive": true,
|
||||
"btReceiveAutoPaired": false,
|
||||
"btReceiveFolder": "",
|
||||
"btScanOnOpen": true,
|
||||
"btShowUnnamed": false,
|
||||
"clipboardEnabled": true,
|
||||
"clock24h": true,
|
||||
"clockDateFormat": "long",
|
||||
"clockShowDate": true,
|
||||
"clockShowSeconds": true,
|
||||
"clockShowTimer": true,
|
||||
"clockShowWeather": true,
|
||||
"clockStyle": "accent",
|
||||
"clockWorldZones": "Europe/Berlin,Europe/Kiev,America/Chicago,Asia/Tokyo",
|
||||
"cornerScale": 1,
|
||||
"desktopIconSize": "medium",
|
||||
"desktopIconSpacing": "normal",
|
||||
"desktopIcons": false,
|
||||
"desktopIconsAutoArrange": false,
|
||||
"desktopIconsCorner": "left",
|
||||
"desktopIconsDrives": true,
|
||||
"desktopIconsHidden": false,
|
||||
"desktopIconsHideExt": false,
|
||||
"desktopIconsHome": false,
|
||||
"desktopIconsOpen": "double",
|
||||
"desktopIconsShown": true,
|
||||
"desktopIconsSort": "name",
|
||||
"desktopIconsSortDesc": false,
|
||||
"desktopIconsSpring": true,
|
||||
"desktopIconsThumbs": true,
|
||||
"desktopIconsTrash": true,
|
||||
"desktopKeys": "{}",
|
||||
"desktopMenu": true,
|
||||
"desktopSelection": true,
|
||||
"dndAllowCritical": true,
|
||||
"dndFullscreen": false,
|
||||
"doNotDisturb": false,
|
||||
"dockAutoHide": true,
|
||||
"dockBottomMargin": 13,
|
||||
"dockDynamicHide": true,
|
||||
"dockEnabled": true,
|
||||
"dockHoverEffect": 1,
|
||||
"dockIconPadding": 0,
|
||||
"dockIconSize": 41,
|
||||
"dockIconTiles": true,
|
||||
"dockMagnify": true,
|
||||
"dockNotchFlare": 14,
|
||||
"dockRadius": 40,
|
||||
"dockShowIndicators": true,
|
||||
"dockShowRunning": true,
|
||||
"dockShowTooltips": true,
|
||||
"dockSpacing": 12,
|
||||
"dockSplitGap": 8,
|
||||
"dockStyle": "notch",
|
||||
"envAdopted": true,
|
||||
"envAppFont": "Pixeloid Sans",
|
||||
"envAppFontSize": 13,
|
||||
"envApplyGtk": true,
|
||||
"envApplyHypr": true,
|
||||
"envApplyQt": true,
|
||||
"envColorScheme": "dark",
|
||||
"envCursorShadow": true,
|
||||
"envCursorSize": 32,
|
||||
"envCursorTheme": "Kurumi",
|
||||
"envDocumentFont": "Pixeloid Sans",
|
||||
"envDocumentFontSize": 13,
|
||||
"envFontSync": false,
|
||||
"envGtkTheme": "Adwaita",
|
||||
"envIconSet": "gravity",
|
||||
"envIconTheme": "Pop-Dark",
|
||||
"envLegacyIcons": false,
|
||||
"envMonoFont": "JetBrains Mono",
|
||||
"envMonoFontSize": 13,
|
||||
"envQtPlatformTheme": "",
|
||||
"envQtStyle": "kvantum",
|
||||
"fontFamily": "Pixeloid Sans",
|
||||
"fontScale": 1,
|
||||
"gameModeOffCmd": "",
|
||||
"gameModeOnCmd": "",
|
||||
"gameModeStatusCmd": "",
|
||||
"glassApps": "vscodium",
|
||||
"glassValues": "vscodium=0.9",
|
||||
"gpsEnabled": false,
|
||||
"hiddenThemes": "",
|
||||
"hyprOptions": "{\"general.layout\":\"scrolling\"}",
|
||||
"idleAdopted": true,
|
||||
"idleAutostart": true,
|
||||
"idleDim": true,
|
||||
"idleDimAfter": 120,
|
||||
"idleDimKeyboard": true,
|
||||
"idleDimLevel": 10,
|
||||
"idleEnabled": false,
|
||||
"idleKeepAwake": false,
|
||||
"idleLock": true,
|
||||
"idleLockAfter": 600,
|
||||
"idleLockBeforeSleep": true,
|
||||
"idleRespectInhibitors": true,
|
||||
"idleScreenOff": true,
|
||||
"idleScreenOffAfter": 900,
|
||||
"idleSuspend": false,
|
||||
"idleSuspendAfter": 1800,
|
||||
"idleSuspendOnAc": false,
|
||||
"idleWakeAfterSleep": true,
|
||||
"idleWhileMedia": true,
|
||||
"jellyAmount": 1,
|
||||
"jellyMotion": true,
|
||||
"kdeConnectEnabled": true,
|
||||
"launcherActionHints": true,
|
||||
"launcherAddressFirst": true,
|
||||
"launcherAppActions": true,
|
||||
"launcherAppDescriptions": true,
|
||||
"launcherCalculator": true,
|
||||
"launcherContentScale": 0.8,
|
||||
"launcherDensity": "compact",
|
||||
"launcherFrequentFirst": true,
|
||||
"launcherHiddenApps": "",
|
||||
"launcherModeBar": true,
|
||||
"launcherPowerButtons": "lock,suspend,reboot,shutdown",
|
||||
"launcherPowerChips": false,
|
||||
"launcherPowerSearch": true,
|
||||
"launcherSearchEngine": "lair",
|
||||
"launcherSearchPosition": "bottom",
|
||||
"launcherSettingsResults": true,
|
||||
"launcherWebRow": true,
|
||||
"launcherWidth": "wide",
|
||||
"launcherWindows": true,
|
||||
"liveDecoder": "cuda",
|
||||
"liveGameMode": "pause",
|
||||
"livePauseUnder": "any",
|
||||
"liveWallpapers": true,
|
||||
"locationLabel": "Nizhny Novgorod, Nizhny Novgorod Oblast, Russia",
|
||||
"locationLat": 56.32867,
|
||||
"locationLon": 44.00205,
|
||||
"locationName": "Nizhniy Novgorod",
|
||||
"locationTz": "Europe/Moscow",
|
||||
"matugenContrast": 0,
|
||||
"matugenScheme": "scheme-tonal-spot",
|
||||
"matugenSourceImage": "",
|
||||
"matugenSourceIndex": 0,
|
||||
"mediaArtist": true,
|
||||
"mediaHideIdle": true,
|
||||
"mediaPanelStyle": "side",
|
||||
"mediaPlayButton": true,
|
||||
"mediaStyle": "disc",
|
||||
"mediaTitleWidth": 300,
|
||||
"mediaWheelVolume": true,
|
||||
"monitorBarScreen": "*",
|
||||
"monitorDockScreen": "",
|
||||
"monitorSetups": "{\"desc:Huawei Technologies Co. Inc. ZQE-CAA 0xC080F622\":{\"x\":1920,\"y\":0,\"mode\":\"3440x1440@120.00\"},\"desc:Samsung Electric Company SAMSUNG 0x01000E00\":{\"x\":0,\"y\":0,\"mode\":\"1920x1080@60.00\"}}",
|
||||
"monitorShellScreen": "desc:Huawei Technologies Co. Inc. ZQE-CAA 0xC080F622",
|
||||
"monitorWorkspaces": "{\"mode\":\"shared\",\"assign\":{}}",
|
||||
"motionScale": 1,
|
||||
"nightLight": false,
|
||||
"nightLightFrom": 1260,
|
||||
"nightLightSchedule": "off",
|
||||
"nightLightTemp": 4000,
|
||||
"nightLightTo": 420,
|
||||
"notifGrouping": true,
|
||||
"notifInlineReply": true,
|
||||
"notifMaxHistory": 50,
|
||||
"notifMutedApps": "",
|
||||
"notifProgress": true,
|
||||
"notifSeenApps": "AyuGram Desktop,Clock,Hyprshot,Lucid test,starship,vesktop,Vivaldi,wallpaper_changer",
|
||||
"notifShowIcons": true,
|
||||
"notifSound": true,
|
||||
"notifSoundName": "pulse",
|
||||
"notifSoundUrgentOnly": false,
|
||||
"notifSoundVolume": 0.6,
|
||||
"notifTimestamps": true,
|
||||
"notificationsStyle": "badge",
|
||||
"osdCapsLock": true,
|
||||
"osdNumLock": true,
|
||||
"pinBadge": "top-right",
|
||||
"pinBorder": "tertiary",
|
||||
"pinPulse": true,
|
||||
"pomodoroAutoFocus": true,
|
||||
"pomodoroAutoStart": true,
|
||||
"pomodoroFocus": 25,
|
||||
"pomodoroGoal": 0,
|
||||
"pomodoroLong": 15,
|
||||
"pomodoroRepeat": true,
|
||||
"pomodoroRounds": 4,
|
||||
"pomodoroShort": 5,
|
||||
"pomodoroSilence": false,
|
||||
"powerModuleActions": "lock,suspend,hibernate,logout,reboot,shutdown",
|
||||
"powerModuleConfirm": true,
|
||||
"powerModulePanelStyle": "list",
|
||||
"powerModuleStyle": "icon",
|
||||
"powerModuleUptime": true,
|
||||
"privacyAlwaysShown": false,
|
||||
"privacyStyle": "marks",
|
||||
"privacyToast": true,
|
||||
"privacyWatch": "mic,camera,screen",
|
||||
"quietFrom": 1320,
|
||||
"quietHours": false,
|
||||
"quietTo": 420,
|
||||
"recordCountdown": 3,
|
||||
"recordEncoder": "auto",
|
||||
"recordFolder": "",
|
||||
"recordFps": 0,
|
||||
"reminderSound": true,
|
||||
"shellGap": 0,
|
||||
"shotFolder": "",
|
||||
"shotLoupe": true,
|
||||
"shotOcrLang": "eng+rus",
|
||||
"shotPreview": "preview",
|
||||
"shotPreviewSeconds": 6,
|
||||
"shotRoundWindows": true,
|
||||
"shotSaveFile": true,
|
||||
"showClock": true,
|
||||
"showKbLayout": true,
|
||||
"showMedia": true,
|
||||
"showNotifications": true,
|
||||
"showPower": true,
|
||||
"showPrivacy": true,
|
||||
"showStart": true,
|
||||
"showSystem": true,
|
||||
"showTray": true,
|
||||
"showWindow": true,
|
||||
"showWorkspaces": true,
|
||||
"soundOnBattery": true,
|
||||
"soundOnBluetooth": true,
|
||||
"soundOnBrightness": true,
|
||||
"soundOnCamera": true,
|
||||
"soundOnCaps": true,
|
||||
"soundOnCapture": true,
|
||||
"soundOnCharger": true,
|
||||
"soundOnMic": true,
|
||||
"soundOnUsb": true,
|
||||
"soundOnVolume": true,
|
||||
"specialBlur": false,
|
||||
"specialComms": true,
|
||||
"specialCommsApps": "auto",
|
||||
"specialCustom": "[]",
|
||||
"specialDim": 0.2,
|
||||
"specialGaps": 0,
|
||||
"specialHideOnSwitch": false,
|
||||
"specialKeepApps": true,
|
||||
"specialMusic": true,
|
||||
"specialMusicApps": "auto",
|
||||
"specialScratchpad": true,
|
||||
"specialSysmon": true,
|
||||
"specialSysmonApps": "auto",
|
||||
"specialTodo": true,
|
||||
"specialTodoApps": "auto",
|
||||
"startModuleStyle": "distro",
|
||||
"startupApps": "[]",
|
||||
"startupEnv": "[]",
|
||||
"startupXdg": false,
|
||||
"storageLowPercent": 10,
|
||||
"storageLowWarn": true,
|
||||
"storageTrashDays": 0,
|
||||
"surfaceDarkness": -1,
|
||||
"surfaceTint": -1,
|
||||
"sysSoundVolume": 0.6,
|
||||
"sysSounds": true,
|
||||
"systemTiles": "dnd,awake,dark,airplane,location,mic,capture,record,picker,keyboard,timer,session,gamemode",
|
||||
"themeColour": "#6750a4",
|
||||
"themeOrder": "",
|
||||
"timeZoneAuto": true,
|
||||
"timerSound": true,
|
||||
"toastBodyLines": 4,
|
||||
"toastCriticalSticky": true,
|
||||
"toastEnabled": true,
|
||||
"toastMaxVisible": 3,
|
||||
"toastOnAudio": true,
|
||||
"toastOnBattery": true,
|
||||
"toastOnBluetooth": true,
|
||||
"toastOnCamera": true,
|
||||
"toastOnDisplays": true,
|
||||
"toastOnGameMode": true,
|
||||
"toastOnLayout": true,
|
||||
"toastOnPower": true,
|
||||
"toastOnUsb": true,
|
||||
"toastOnWifi": true,
|
||||
"toastShowActions": true,
|
||||
"toastShowBody": true,
|
||||
"toastTimeout": 5,
|
||||
"toastUseAppTimeout": true,
|
||||
"trayHidden": "",
|
||||
"trayIconColor": "original",
|
||||
"trayStyle": "collapsed",
|
||||
"uiScale": 1.1,
|
||||
"units": "metric",
|
||||
"updateBeta": false,
|
||||
"updateCheck": true,
|
||||
"urgentBadge": "top-left",
|
||||
"urgentPulse": true,
|
||||
"wallTransAngle": 45,
|
||||
"wallTransBezier": ".54,0,.34,.99",
|
||||
"wallTransCustomPresets": "",
|
||||
"wallTransDuration": 1,
|
||||
"wallTransOrigin": "center",
|
||||
"wallTransPreset": "soft",
|
||||
"wallTransType": "fade",
|
||||
"wallTransWave": "20,20",
|
||||
"wallpaperBentoHero": "medium",
|
||||
"wallpaperBentoPrev": true,
|
||||
"wallpaperFolder": "/home/sweetbread/Wallpapers",
|
||||
"wallpaperPickerKeys": "sides",
|
||||
"wallpaperPickerStyle": "strip",
|
||||
"wallpaperPillsShape": "uniform",
|
||||
"wallpaperStripSides": 2,
|
||||
"wallpaperStripSteps": "normal",
|
||||
"wallpaperTilesAspect": "square",
|
||||
"wallpaperTilesRows": 2,
|
||||
"weekStartMonday": true,
|
||||
"widgetHideFullscreen": true,
|
||||
"widgetLockAll": false,
|
||||
"widgetOnTop": false,
|
||||
"widgetSnap": true,
|
||||
"widgetsEnabled": true,
|
||||
"windowModuleMiddleClose": false,
|
||||
"windowModuleScroll": true,
|
||||
"windowModuleText": "title",
|
||||
"windowModuleWidth": 260,
|
||||
"workspacesByDisplay": true,
|
||||
"workspacesShown": 1,
|
||||
"workspacesStyle": "numbers",
|
||||
"workspacesWheel": true
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
lucid.prefsSeed = lib.mkForce (builtins.toFile "sweetbread-rias-lucid-prefs.json" (builtins.toJSON (
|
||||
(builtins.fromJSON (builtins.readFile ./lucid-prefs.json)) // {
|
||||
specialCustom = "[]";
|
||||
}
|
||||
)));
|
||||
lucid.extraHyprlandConfig = ''
|
||||
require("modules.settings")
|
||||
require("modules.glass")
|
||||
require("modules.startup")
|
||||
'';
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only bridge from Thunderbird's synchronized CalDAV event cache."""
|
||||
|
||||
import configparser
|
||||
import datetime as dt
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HOME = Path.home()
|
||||
DB_NAME = "cache.sqlite"
|
||||
WINDOW_START = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=30)
|
||||
WINDOW_END = dt.datetime.now(dt.timezone.utc) + dt.timedelta(days=400)
|
||||
|
||||
|
||||
def active_profile():
|
||||
ini = configparser.ConfigParser(interpolation=None)
|
||||
ini.read(HOME / ".thunderbird" / "profiles.ini", encoding="utf-8")
|
||||
for section in ini.sections():
|
||||
if not section.startswith("Profile") or ini.get(section, "Default", fallback="0") != "1":
|
||||
continue
|
||||
profile = Path(ini.get(section, "Path"))
|
||||
if ini.get(section, "IsRelative", fallback="1") == "1":
|
||||
profile = HOME / ".thunderbird" / profile
|
||||
return profile
|
||||
raise FileNotFoundError("No default Thunderbird profile")
|
||||
|
||||
|
||||
def calendar_registry(profile):
|
||||
prefs = profile / "prefs.js"
|
||||
pattern = re.compile(r'^user_pref\("calendar\.registry\.([^.]+)\.([^\"]+)",\s*(.*)\);$')
|
||||
registry = {}
|
||||
if not prefs.exists():
|
||||
return registry
|
||||
with prefs.open(encoding="utf-8", errors="replace") as source:
|
||||
for line in source:
|
||||
match = pattern.match(line.strip())
|
||||
if not match:
|
||||
continue
|
||||
calendar_id, key, raw = match.groups()
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
registry.setdefault(calendar_id, {})[key] = value
|
||||
return {
|
||||
calendar_id: data
|
||||
for calendar_id, data in registry.items()
|
||||
if data.get("type") == "caldav" and not data.get("disabled", False)
|
||||
}
|
||||
|
||||
|
||||
def stable_snapshot(database, target_dir):
|
||||
"""Copy DB and WAL together; retry if Thunderbird writes during the copy."""
|
||||
target = target_dir / DB_NAME
|
||||
sidecars = [Path(str(database) + suffix) for suffix in ("", "-wal", "-shm")]
|
||||
for _ in range(4):
|
||||
before = []
|
||||
for path in sidecars:
|
||||
try:
|
||||
stat = path.stat()
|
||||
before.append((stat.st_size, stat.st_mtime_ns))
|
||||
except FileNotFoundError:
|
||||
before.append(None)
|
||||
for path, state in zip(sidecars, before):
|
||||
dest = Path(str(target) + str(path)[len(str(database)):])
|
||||
if state is None:
|
||||
dest.unlink(missing_ok=True)
|
||||
else:
|
||||
shutil.copy2(path, dest)
|
||||
after = []
|
||||
for path in sidecars:
|
||||
try:
|
||||
stat = path.stat()
|
||||
after.append((stat.st_size, stat.st_mtime_ns))
|
||||
except FileNotFoundError:
|
||||
after.append(None)
|
||||
if before == after:
|
||||
return target
|
||||
time.sleep(0.05)
|
||||
raise RuntimeError("Thunderbird calendar cache changed during snapshot")
|
||||
|
||||
|
||||
def parse_until(value):
|
||||
if not value:
|
||||
return None
|
||||
value = value.strip()
|
||||
try:
|
||||
if re.fullmatch(r"\d{8}", value):
|
||||
return dt.datetime.strptime(value, "%Y%m%d").replace(tzinfo=dt.timezone.utc)
|
||||
return dt.datetime.strptime(value.rstrip("Z"), "%Y%m%dT%H%M%S").replace(tzinfo=dt.timezone.utc)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def parse_recurrence(text):
|
||||
rules = []
|
||||
excluded = set()
|
||||
for line in text.replace("\r", "").split("\n"):
|
||||
if line.startswith("RRULE:"):
|
||||
rules.append(dict(part.split("=", 1) for part in line[6:].split(";") if "=" in part))
|
||||
elif line.startswith("EXDATE") and ":" in line:
|
||||
values = line.split(":", 1)[1].split(",")
|
||||
for value in values:
|
||||
parsed = parse_until(value)
|
||||
if parsed:
|
||||
excluded.add(int(parsed.timestamp() * 1000))
|
||||
return rules, excluded
|
||||
|
||||
|
||||
def month_dates(year, month, base_day, rule):
|
||||
import calendar
|
||||
|
||||
month_days = int(calendar.monthrange(year, month)[1])
|
||||
if "BYMONTHDAY" in rule:
|
||||
days = []
|
||||
for raw in rule["BYMONTHDAY"].split(","):
|
||||
value = int(raw)
|
||||
day = value if value > 0 else month_days + value + 1
|
||||
if 1 <= day <= month_days:
|
||||
days.append(day)
|
||||
return sorted(set(days))
|
||||
if "BYDAY" not in rule:
|
||||
return [base_day] if base_day <= month_days else []
|
||||
|
||||
weekdays = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6}
|
||||
result = []
|
||||
for entry in rule["BYDAY"].split(","):
|
||||
match = re.fullmatch(r"([+-]?\d+)?(MO|TU|WE|TH|FR|SA|SU)", entry)
|
||||
if not match:
|
||||
continue
|
||||
ordinal, name = match.groups()
|
||||
weekday = weekdays[name]
|
||||
matching = [day for day in range(1, month_days + 1) if dt.date(year, month, day).weekday() == weekday]
|
||||
if ordinal:
|
||||
index = int(ordinal)
|
||||
if index < 0:
|
||||
index = len(matching) + index + 1
|
||||
if 1 <= index <= len(matching):
|
||||
result.append(matching[index - 1])
|
||||
else:
|
||||
result.extend(matching)
|
||||
return sorted(set(result))
|
||||
|
||||
|
||||
def occurrences(start, rule, excluded):
|
||||
freq = rule.get("FREQ", "").upper()
|
||||
interval = max(1, int(rule.get("INTERVAL", "1")))
|
||||
count_limit = int(rule["COUNT"]) if rule.get("COUNT", "").isdigit() else None
|
||||
until = parse_until(rule.get("UNTIL"))
|
||||
candidates = []
|
||||
base_date = start.date()
|
||||
|
||||
if freq == "DAILY":
|
||||
day = base_date
|
||||
while day <= WINDOW_END.date():
|
||||
delta = (day - base_date).days
|
||||
if delta % interval == 0:
|
||||
candidates.append(dt.datetime.combine(day, start.timetz()))
|
||||
day += dt.timedelta(days=1)
|
||||
elif freq == "WEEKLY":
|
||||
days = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, "FR": 4, "SA": 5, "SU": 6}
|
||||
weekdays = sorted({days[x] for x in rule.get("BYDAY", "").split(",") if x in days}) or [base_date.weekday()]
|
||||
week = base_date - dt.timedelta(days=base_date.weekday())
|
||||
while week <= WINDOW_END.date():
|
||||
weeks = (week - (base_date - dt.timedelta(days=base_date.weekday()))).days // 7
|
||||
if weeks % interval == 0:
|
||||
for weekday in weekdays:
|
||||
day = week + dt.timedelta(days=weekday)
|
||||
if day >= base_date:
|
||||
candidates.append(dt.datetime.combine(day, start.timetz()))
|
||||
week += dt.timedelta(days=7)
|
||||
elif freq == "MONTHLY":
|
||||
month_index = base_date.year * 12 + base_date.month - 1
|
||||
last_index = WINDOW_END.year * 12 + WINDOW_END.month - 1
|
||||
for index in range(month_index, last_index + 1, interval):
|
||||
year, month0 = divmod(index, 12)
|
||||
for day in month_dates(year, month0 + 1, base_date.day, rule):
|
||||
date = dt.date(year, month0 + 1, day)
|
||||
if date >= base_date:
|
||||
candidates.append(dt.datetime.combine(date, start.timetz()))
|
||||
elif freq == "YEARLY":
|
||||
for year in range(base_date.year, WINDOW_END.year + 1, interval):
|
||||
months = [int(x) for x in rule.get("BYMONTH", str(base_date.month)).split(",")]
|
||||
for month in months:
|
||||
for day in month_dates(year, month, base_date.day, rule):
|
||||
date = dt.date(year, month, day)
|
||||
if date >= base_date:
|
||||
candidates.append(dt.datetime.combine(date, start.timetz()))
|
||||
|
||||
candidates.sort()
|
||||
result = []
|
||||
for ordinal, occurrence in enumerate(candidates):
|
||||
if count_limit is not None and ordinal >= count_limit:
|
||||
break
|
||||
if until and occurrence > until:
|
||||
break
|
||||
stamp = int(occurrence.timestamp() * 1000)
|
||||
if WINDOW_START.timestamp() * 1000 <= stamp <= WINDOW_END.timestamp() * 1000 and stamp not in excluded:
|
||||
result.append(stamp)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
profile = active_profile()
|
||||
registry = calendar_registry(profile)
|
||||
database = profile / "calendar-data" / DB_NAME
|
||||
events = []
|
||||
if registry and database.exists():
|
||||
with tempfile.TemporaryDirectory(prefix="lucid-tb-calendar-") as td:
|
||||
snapshot = stable_snapshot(database, Path(td))
|
||||
db = sqlite3.connect(snapshot)
|
||||
db.row_factory = sqlite3.Row
|
||||
recurrence = {}
|
||||
for row in db.execute("SELECT cal_id, item_id, icalString FROM cal_recurrence"):
|
||||
recurrence[(row["cal_id"], row["item_id"])] = parse_recurrence(row["icalString"] or "")
|
||||
|
||||
rows = list(db.execute(
|
||||
"SELECT cal_id, id, title, event_start, event_end, recurrence_id, ical_status "
|
||||
"FROM cal_events WHERE event_start IS NOT NULL"
|
||||
))
|
||||
overrides = set()
|
||||
for row in rows:
|
||||
if row["recurrence_id"] is not None:
|
||||
try:
|
||||
overrides.add((row["cal_id"], row["id"], int(row["recurrence_id"])))
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
|
||||
for row in rows:
|
||||
cal_id = row["cal_id"]
|
||||
if cal_id not in registry or (row["ical_status"] or "").upper() == "CANCELLED":
|
||||
continue
|
||||
try:
|
||||
start_us = int(row["event_start"])
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
start_ms = start_us // 1000
|
||||
duration_ms = max(0, int(row["event_end"] or row["event_start"]) // 1000 - start_ms)
|
||||
title = (row["title"] or "(untitled event)").strip()
|
||||
cal_name = registry[cal_id].get("name") or "CalDAV"
|
||||
rule_data = recurrence.get((cal_id, row["id"]))
|
||||
starts = [start_ms]
|
||||
if rule_data:
|
||||
rules, excluded = rule_data
|
||||
if rules:
|
||||
base = dt.datetime.fromtimestamp(start_ms / 1000, dt.timezone.utc)
|
||||
starts = occurrences(base, rules[0], excluded)
|
||||
for occurrence_ms in starts:
|
||||
if (cal_id, row["id"], occurrence_ms * 1000) in overrides:
|
||||
continue
|
||||
if not (WINDOW_START.timestamp() * 1000 <= occurrence_ms <= WINDOW_END.timestamp() * 1000):
|
||||
continue
|
||||
events.append({
|
||||
"id": f"{cal_id}:{row['id']}:{occurrence_ms}",
|
||||
"name": title,
|
||||
"calendar": cal_name,
|
||||
"startMs": occurrence_ms,
|
||||
"endMs": occurrence_ms + duration_ms,
|
||||
})
|
||||
db.close()
|
||||
|
||||
events.sort(key=lambda event: (event["startMs"], event["name"].casefold()))
|
||||
print(json.dumps({"events": events}, ensure_ascii=False, separators=(",", ":")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# Keep refresh failures out of Lucid's console: current calendar events
|
||||
# remain visible until the next successful sync.
|
||||
print(json.dumps({"events": [], "error": "Thunderbird calendar cache unavailable"}))
|
||||
Reference in new issue
Block a user