Merge branch 'end-4:main' into main

This commit is contained in:
jwihardi
2025-09-27 00:04:30 -04:00
committed by GitHub
96 changed files with 2206 additions and 1372 deletions
+2
View File
@@ -33,6 +33,7 @@ bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle #
bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet
bindd = Super, K, Toggle on-screen keyboard, global, quickshell:oskToggle # Toggle on-screen keyboard bindd = Super, K, Toggle on-screen keyboard, global, quickshell:oskToggle # Toggle on-screen keyboard
bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls
bind = Super, G, global, quickshell:crosshairToggle # Toggle crosshair
bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu
bindd = Super, J, Toggle bar, global, quickshell:barToggle # Toggle bar bindd = Super, J, Toggle bar, global, quickshell:barToggle # Toggle bar
bind = Ctrl+Alt, Delete, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback) bind = Ctrl+Alt, Delete, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] Session menu (fallback)
@@ -49,6 +50,7 @@ bindl = Alt ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidd
bindl = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] bindl = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindld = Super+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden] bindld = Super+Alt,M, Toggle mic, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # [hidden]
bindd = Ctrl+Super, T, Toggle wallpaper selector, global, quickshell:wallpaperSelectorToggle # Wallpaper selector bindd = Ctrl+Super, T, Toggle wallpaper selector, global, quickshell:wallpaperSelectorToggle # Wallpaper selector
bindd = Ctrl+Super+Alt, T, Select random wallpaper, global, quickshell:wallpaperSelectorRandom # Random wallpaper
bindd = Ctrl+Super, T, Change wallpaper, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/colors/switchwall.sh # [hidden] Change wallpaper (fallback) bindd = Ctrl+Super, T, Change wallpaper, exec, qs -c $qsConfig ipc call TEST_ALIVE || ~/.config/quickshell/$qsConfig/scripts/colors/switchwall.sh # [hidden] Change wallpaper (fallback)
bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs -c $qsConfig & # Restart widgets bind = Ctrl+Super, R, exec, killall ags agsv1 gjs ydotool qs quickshell; qs -c $qsConfig & # Restart widgets
+12 -22
View File
@@ -130,32 +130,22 @@ layerrule = blurpopups, quickshell:.*
layerrule = blur, quickshell:.* layerrule = blur, quickshell:.*
layerrule = ignorealpha 0.79, quickshell:.* layerrule = ignorealpha 0.79, quickshell:.*
layerrule = animation slide, quickshell:bar layerrule = animation slide, quickshell:bar
layerrule = animation slide, quickshell:verticalBar
layerrule = animation fade, quickshell:screenCorners
layerrule = animation slide right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide top, quickshell:wallpaperSelector
layerrule = animation slide bottom, quickshell:osk
layerrule = animation slide bottom, quickshell:dock
layerrule = animation slide bottom, quickshell:cheatsheet layerrule = animation slide bottom, quickshell:cheatsheet
layerrule = noanim, quickshell:crosshair
layerrule = animation slide bottom, quickshell:dock
layerrule = animation popin 120%, quickshell:screenCorners
layerrule = noanim, quickshell:lockWindowPusher
layerrule = animation fade, quickshell:notificationPopup
layerrule = noanim, quickshell:overview
layerrule = animation slide bottom, quickshell:osk
layerrule = noanim, quickshell:screenshot
layerrule = blur, quickshell:session layerrule = blur, quickshell:session
layerrule = noanim, quickshell:session layerrule = noanim, quickshell:session
layerrule = ignorealpha 0, quickshell:session layerrule = ignorealpha 0, quickshell:session
layerrule = animation fade, quickshell:notificationPopup layerrule = animation slide right, quickshell:sidebarRight
layerrule = blur, quickshell:backgroundWidgets layerrule = animation slide left, quickshell:sidebarLeft
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets layerrule = animation slide, quickshell:verticalBar
layerrule = noanim, quickshell:screenshot layerrule = animation slide top, quickshell:wallpaperSelector
layerrule = animation popin 120%, quickshell:screenCorners
layerrule = noanim, quickshell:lockWindowPusher
# Launchers need to be FAST # Launchers need to be FAST
layerrule = noanim, quickshell:overview
layerrule = noanim, gtk4-layer-shell layerrule = noanim, gtk4-layer-shell
## outfoxxed's stuff
layerrule = blur, shell:bar
layerrule = ignorezero, shell:bar
layerrule = blur, shell:notifications
layerrule = ignorealpha 0.1, shell:notifications
@@ -23,13 +23,15 @@ while [[ "$#" -gt 0 ]]; do
done done
# Combine the system prompt with the clipboard content # Combine the system prompt with the clipboard content
content=$(wl-paste -p | tr '\n' ' ') content=$(wl-paste -p | tr '\n' ' ' | head -c 2000) # 2000 char limit to prevent overflow
prompt="$SYSTEM_PROMPT $content"
# Properly escape content for JSON using jq
prompt_json=$(jq -n --arg system_prompt "$SYSTEM_PROMPT" --arg content "$content" '$system_prompt + " " + $content')
# Make the API call with the specified or default model # Make the API call with the specified or default model
response=$(curl http://localhost:11434/api/generate -d \ api_payload=$(jq -n --arg model "$model" --argjson prompt "$prompt_json" --argjson stream false \
"{\"model\": \"$model\",\"prompt\": \"$prompt\",\"stream\": false}" \ '{model: $model, prompt: $prompt, stream: $stream}')
| jq -r '.response') response=$(curl -s http://localhost:11434/api/generate -d "$api_payload" | jq -r '.response' 2>/dev/null)
# Check if content is a single line and no longer than 30 characters # Check if content is a single line and no longer than 30 characters
if [[ ${#content} -le 30 && "$content" != *$'\n'* ]]; then if [[ ${#content} -le 30 && "$content" != *$'\n'* ]]; then
@@ -1,24 +0,0 @@
// vim: set ft=glsl:
precision highp float;
varying highp vec2 v_texcoord;
uniform highp sampler2D tex;
#define STRENGTH 0.0027
void main() {
vec2 center = vec2(0.5, 0.5);
vec2 offset = (v_texcoord - center) * STRENGTH;
float rSquared = dot(offset, offset);
float distortion = 1.0 + 1.0 * rSquared;
vec2 distortedOffset = offset * distortion;
vec2 redOffset = vec2(distortedOffset.x, distortedOffset.y);
vec2 blueOffset = vec2(distortedOffset.x, distortedOffset.y);
vec4 redColor = texture2D(tex, v_texcoord + redOffset);
vec4 blueColor = texture2D(tex, v_texcoord + blueOffset);
gl_FragColor = vec4(redColor.r, texture2D(tex, v_texcoord).g, blueColor.b, 1.0);
}
-511
View File
@@ -1,511 +0,0 @@
#version 100
precision highp float;
varying highp vec2 v_texcoord;
varying highp vec3 v_pos;
uniform highp sampler2D tex;
uniform lowp float time;
#define BORDER_COLOR vec4(vec3(0.0, 0.0, 0.0), 1.0) // black border
#define BORDER_RADIUS 1.0 // larger vignette radius
#define BORDER_SIZE 0.01 // small border size
#define CHROMATIC_ABERRATION_STRENGTH 0.00
#define DENOISE_INTENSITY 0.0001 //
#define DISTORTION_AMOUNT 0.00 // moderate distortion amount
#define HDR_BLOOM 0.75 // bloom intensity
#define HDR_BRIGHTNESS 0.011 // brightness
#define HDR_CONTRAST 0.011 // contrast
#define HDR_SATURATION 1.0// saturation
#define LENS_DISTORTION_AMOUNT 0.0
#define NOISE_THRESHOLD 0.0001
#define PHOSPHOR_BLUR_AMOUNT 0.77 // Amount of blur for phosphor glow
#define PHOSPHOR_GLOW_AMOUNT 0.77 // Amount of phosphor glow
#define SAMPLING_RADIUS 0.0001
#define SCANLINE_FREQUENCY 540.0
#define SCANLINE_THICKNESS 0.0507
#define SCANLINE_TIME time * 471.24
#define SHARPNESS 0.25
#define SUPERSAMPLING_SAMPLES 16.0
#define VIGNETTE_RADIUS 0.0 // larger vignette radius
#define PI 3.14159265359
#define TWOPI 6.28318530718
vec2 applyBarrelDistortion(vec2 coord, float amt) {
vec2 p = coord.xy / vec2(1.0);
vec2 v = p * 2.0 - vec2(1.0);
float r = dot(v, v);
float k = 1.0 + pow(r, 2.0) * pow(amt, 2.0);
vec2 result = v * k;
return vec2(0.5, 0.5) + 0.5 * result.xy;
}
vec4 applyColorCorrection(vec4 color) {
color.rgb *= vec3(1.0, 0.79, 0.89);
return vec4(color.rgb, 1.0);
}
vec4 applyBorder(vec2 tc, vec4 color, float borderSize, vec4 borderColor) {
float dist_x = min(tc.x, 1.0 - tc.x);
float dist_y = min(tc.y, 1.0 - tc.y);
float dist = min(dist_x, dist_y) * -1.0;
float border = smoothstep(borderSize, 0.0, dist);
border += smoothstep(borderSize, 0.0, dist);
return mix(color, borderColor, border);
}
vec4 applyFakeHDR(vec4 color, float brightness, float contrast, float saturation, float bloom) {
color.rgb = (color.rgb - vec3(0.5)) * exp2(brightness) + vec3(0.5);
vec3 crtfactor = vec3(1.05, 0.92, 1.0);
color.rgb = pow(color.rgb, crtfactor);
// // NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
// // BT.709
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
// BT.2020
vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
// // Warm NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
float luminance = dot(color.rgb, lumCoeff);
luminance = pow(luminance, 2.2);
color.rgb = mix(vec3(luminance), color.rgb, saturation);
color.rgb = mix(color.rgb, vec3(1.0), pow(max(0.0, luminance - 1.0 + bloom), 4.0));
return color;
}
vec4 applyVignette(vec4 color) {
vec2 center = vec2(0.5, 0.5); // center of screen
float radius = VIGNETTE_RADIUS; // radius of vignette effect
float softness = 1.0; // softness of vignette effect
float intensity = 0.7; // intensity of vignette effect
vec2 offset = v_texcoord - center; // offset from center of screen
float distance = length(offset); // distance from center of screen
float alpha = smoothstep(radius, radius - radius * softness, distance) * intensity; // calculate alpha value for vignette effect
return mix(vec4(0.0, 0.0, 0.0, alpha), color, alpha); // mix black with color using calculated alpha value
}
vec4 applyPhosphorGlow(vec2 tc, vec4 color, sampler2D tex) {
// Calculate average color value of the texture
vec4 texelColor = color;
float averageColor = (texelColor.r + texelColor.g + texelColor.b) / 3.0;
// Determine brightness-dependent color factor
float factor = mix(
mix(0.09,
mix(0.005, 0.0075, (averageColor - 0.1) / 0.1),
step(0.01, averageColor)), 0.0005,
step(0.02, averageColor));
// Apply phosphor glow effect
vec4 sum = vec4(0.0);
vec4 pixels[9];
pixels[0] = texture2D(tex, tc - vec2(0.001, 0.001));
pixels[1] = texture2D(tex, tc - vec2(0.001, 0.0));
pixels[2] = texture2D(tex, tc - vec2(0.001, -0.001));
pixels[3] = texture2D(tex, tc - vec2(0.0, 0.001));
pixels[4] = texture2D(tex, tc);
pixels[5] = texture2D(tex, tc + vec2(0.001, 0.001));
pixels[6] = texture2D(tex, tc + vec2(0.001, 0.0));
pixels[7] = texture2D(tex, tc + vec2(0.001, -0.001));
pixels[8] = texture2D(tex, tc + vec2(0.0, 0.001));
// Perform operations on input pixels in parallel
sum = pixels[0]
+ pixels[1]
+ pixels[2]
+ pixels[3]
+ pixels[4]
+ pixels[5]
+ pixels[6]
+ pixels[7]
+ pixels[8];
sum /= 9.0;
sum += texture2D(tex, tc - vec2(0.01, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.0, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(-0.01, 0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.01, 0.0)) * 0.001;
sum += color * PHOSPHOR_BLUR_AMOUNT;
sum += texture2D(tex, tc - vec2(-0.01, 0.0)) * 0.001;
sum += texture2D(tex, tc - vec2(0.01, -0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(0.0, -0.01)) * 0.001;
sum += texture2D(tex, tc - vec2(-0.01, -0.01)) * 0.001;
sum *= PHOSPHOR_GLOW_AMOUNT;
// Initialize sum_sum_factor to zero
vec4 sum_sum_factor = vec4(0.0);
// Compute sum_j for i = -1
vec4 sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, -1) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
// Compute sum_j for i = 0
sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
// Compute sum_j for i = 1
sum_j = vec4(0.0);
sum_j += texture2D(tex, tc + vec2(-1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 0) * 0.01);
sum_j += texture2D(tex, tc + vec2(-1, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(0, 1) * 0.01);
sum_j += texture2D(tex, tc + vec2(1, 1) * 0.01);
sum_sum_factor += sum_j * vec4(0.011);
color += mix(sum_sum_factor * sum_sum_factor * vec4(factor), sum, 0.5);
return color;
}
vec4 applyAdaptiveSharpen(vec2 tc, vec4 color, sampler2D tex) {
vec4 color_tl = texture2D(tex, tc + vec2(-1.0, -1.0) * 0.5 / 2160.0);
vec4 color_tr = texture2D(tex, tc + vec2(1.0, -1.0) * 0.5 / 2160.0);
vec4 color_bl = texture2D(tex, tc + vec2(-1.0, 1.0) * 0.5 / 2160.0);
vec4 color_br = texture2D(tex, tc + vec2(1.0, 1.0) * 0.5 / 2160.0);
float sharpness = SHARPNESS;
vec3 color_no_alpha = color.rgb;
vec3 color_tl_no_alpha = color_tl.rgb;
vec3 color_tr_no_alpha = color_tr.rgb;
vec3 color_bl_no_alpha = color_bl.rgb;
vec3 color_br_no_alpha = color_br.rgb;
float delta = (dot(color_no_alpha, vec3(0.333333)) + dot(color_tl_no_alpha, vec3(0.333333)) + dot(color_tr_no_alpha, vec3(0.333333)) + dot(color_bl_no_alpha, vec3(0.333333)) + dot(color_br_no_alpha, vec3(0.333333))) * 0.2 - dot(color_no_alpha, vec3(0.333333));
vec3 sharp_color_no_alpha = color_no_alpha + min(vec3(0.0), vec3(delta * sharpness));
vec4 sharp_color = vec4(sharp_color_no_alpha, color.a);
return sharp_color;
}
vec4 applyScanlines(vec2 tc, vec4 color) {
float scanline = (cos(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME) *
sin(tc.y * SCANLINE_FREQUENCY + SCANLINE_TIME)) * SCANLINE_THICKNESS;
float alpha = clamp(1.0 - abs(scanline), 0.0, 1.0);
return vec4(color.rgb * alpha, color.a);
}
vec4 applyChromaticAberration(vec2 uv, vec4 color) {
vec2 center = vec2(0.5, 0.5); // center of the screen
vec2 offset = (uv - center) * CHROMATIC_ABERRATION_STRENGTH; // calculate the offset from the center
// apply lens distortion
float rSquared = dot(offset, offset);
float distortion = 1.0 + LENS_DISTORTION_AMOUNT * rSquared;
vec2 distortedOffset = offset * distortion;
// apply chromatic aberration
vec2 redOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
vec2 blueOffset = vec2(distortedOffset.x * 1.00, distortedOffset.y * 1.00);
vec4 redColor = texture2D(tex, uv + redOffset);
vec4 blueColor = texture2D(tex, uv + blueOffset);
vec4 result = vec4(redColor.r, color.g, blueColor.b, color.a);
return result;
}
vec4 reduceGlare(vec4 color) {
// Calculate the intensity of the color by taking the average of the RGB components
float intensity = (color.r + color.g + color.b) / 3.0;
// Set the maximum intensity that can be considered for glare
float maxIntensity = 0.98;
// Use smoothstep to create a smooth transition from no glare to full glare
// based on the intensity of the color and the maximum intensity
float glareIntensity = smoothstep(maxIntensity - 0.02, maxIntensity, intensity);
// Set the amount of glare to apply to the color
float glareAmount = 0.02;
// Mix the original color with the reduced color that has glare applied to it
vec3 reducedColor = mix(color.rgb, vec3(glareIntensity), glareAmount);
// Return the reduced color with the original alpha value
return vec4(reducedColor, color.a);
}
// Apply a fake HDR effect to the input color.
// Parameters:
// - inputColor: the color to apply the effect to.
// - brightness: the brightness of the image. Should be a value between 0 and 1.
// - contrast: the contrast of the image. Should be a value between 0 and 1.
// - saturation: the saturation of the image. Should be a value between 0 and 2.
// - bloom: the intensity of the bloom effect. Should be a value between 0 and 1.
vec4 applyFakeHDREffect(vec4 inputColor, float brightness, float contrast, float saturation, float bloom) {
const float minBrightness = 0.0;
const float maxBrightness = 1.0;
const float minContrast = 0.0;
const float maxContrast = 1.0;
const float minSaturation = 0.0;
const float maxSaturation = 2.0;
const float minBloom = 0.0;
const float maxBloom = 1.0;
// Check input parameters for validity
if (brightness < minBrightness || brightness > maxBrightness) {
return vec4(0.0, 0.0, 0.0, 1.0); // Return black with alpha of 1.0 to indicate error
}
if (contrast < minContrast || contrast > maxContrast) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
if (saturation < minSaturation || saturation > maxSaturation) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
if (bloom < minBloom || bloom > maxBloom) {
return vec4(0.0, 0.0, 0.0, 1.0);
}
// Apply brightness and contrast
vec3 color = inputColor.rgb;
color = (color - vec3(0.5)) * exp2(brightness * 10.0) + vec3(0.5);
color = mix(vec3(0.5), color, pow(contrast * 4.0 + 1.0, 2.0));
// // NTSC
// vec3 lumCoeff = vec3(0.2125, 0.7154, 0.0721);
// // BT.709
// vec3 lumCoeff = vec3(0.299, 0.587, 0.114);
// // BT.2020
// vec3 lumCoeff = vec3(0.2627, 0.6780, 0.0593);
// Warm NTSC
vec3 lumCoeff = vec3(0.2125, 0.7010, 0.0865);
// Apply saturation
float luminance = dot(color, lumCoeff);
vec3 grey = vec3(luminance);
color = mix(grey, color, saturation);
// Apply bloom effect
float threshold = 1.0 - bloom;
vec3 bloomColor = max(color - threshold, vec3(0.0));
bloomColor = pow(bloomColor, vec3(2.0));
bloomColor = mix(vec3(0.0), bloomColor, pow(min(luminance, threshold), 4.0));
color += bloomColor;
return vec4(color, inputColor.a);
}
vec4 bilateralFilter(sampler2D tex, vec2 uv, vec4 color, float sampleRadius, float noiseThreshold, float intensity) {
vec4 filteredColor = vec4(0.0);
float totalWeight = 0.0;
// Top-left pixel
vec4 sample = texture2D(tex, uv + vec2(-1.0, -1.0));
float dist = length(vec2(-1.0, -1.0));
float colorDist = length(sample - color);
float weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Top pixel
sample = texture2D(tex, uv + vec2(0.0, -1.0));
dist = length(vec2(0.0, -1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Top-right pixel
sample = texture2D(tex, uv + vec2(1.0, -1.0));
dist = length(vec2(1.0, -1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Left pixel
sample = texture2D(tex, uv + vec2(-1.0, 0.0));
dist = length(vec2(-1.0, 0.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Center pixel
sample = texture2D(tex, uv);
dist = 0.0;
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Right pixel
sample = texture2D(tex, uv + vec2(1.0, 0.0));
dist = length(vec2(1.0, 0.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Bottom-left pixel
sample = texture2D(tex, uv + vec2(-1.0, 1.0));
dist = length(vec2(-1.0, 1.0));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
// Bottom pixel
sample = texture2D(tex, uv + vec2(0.0, sampleRadius));
dist = length(vec2(0.0, sampleRadius));
colorDist = length(sample - color);
weight = exp(-0.5 * (dist * dist + colorDist * colorDist * intensity) / (sampleRadius * sampleRadius));
filteredColor += sample * weight;
totalWeight += weight;
filteredColor /= totalWeight;
return mix(color, filteredColor, step(noiseThreshold, length(filteredColor - color)));
}
vec4 supersample(sampler2D tex, vec2 uv, float sampleRadius, float noiseThreshold, float intensity) {
float radiusSq = sampleRadius * sampleRadius;
vec2 poissonDisk;
vec4 color = vec4(0.0);
float r1_0 = sqrt(0.0 / 16.0);
float r2_0 = fract(1.0 / 3.0);
float theta_0 = TWOPI * r2_0;
poissonDisk = vec2(r1_0 * cos(theta_0), r1_0 * sin(theta_0));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_1 = sqrt(1.0 / 16.0);
float r2_1 = fract(2.0 / 3.0);
float theta_1 = TWOPI * r2_1;
poissonDisk = vec2(r1_1 * cos(theta_1), r1_1 * sin(theta_1));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_2 = sqrt(2.0 / 16.0);
float r2_2 = fract(3.0 / 3.0);
float theta_2 = TWOPI * r2_2;
poissonDisk = vec2(r1_2 * cos(theta_2), r1_2 * sin(theta_2));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_3 = sqrt(3.0 / 16.0);
float r2_3 = fract(4.0 / 3.0);
float theta_3 = TWOPI * r2_3;
poissonDisk = vec2(r1_3 * cos(theta_3), r1_3 * sin(theta_3));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_4 = sqrt(4.0 / 16.0);
float r2_4 = fract(5.0 / 3.0);
float theta_4 = TWOPI * r2_4;
poissonDisk = vec2(r1_4 * cos(theta_4), r1_4 * sin(theta_4));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_5 = sqrt(5.0 / 16.0);
float r2_5 = fract(6.0 / 3.0);
float theta_5 = TWOPI * r2_5;
poissonDisk = vec2(r1_5 * cos(theta_5), r1_5 * sin(theta_5));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_6 = sqrt(6.0 / 16.0);
float r2_6 = fract(7.0 / 3.0);
float theta_6 = TWOPI * r2_6;
poissonDisk = vec2(r1_6 * cos(theta_6), r1_6 * sin(theta_6));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_7 = sqrt(7.0 / 16.0);
float r2_7 = fract(8.0 / 3.0);
float theta_7 = TWOPI * r2_7;
poissonDisk = vec2(r1_7 * cos(theta_7), r1_7 * sin(theta_7));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_8 = sqrt(8.0 / 16.0);
float r2_8 = fract(9.0 / 3.0);
float theta_8 = TWOPI * r2_8;
poissonDisk = vec2(r1_8 * cos(theta_8), r1_8 * sin(theta_8));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_9 = sqrt(9.0 / 16.0);
float r2_9 = fract(10.0 / 3.0);
float theta_9 = TWOPI * r2_9;
poissonDisk = vec2(r1_9 * cos(theta_9), r1_9 * sin(theta_9));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_10 = sqrt(10.0 / 16.0);
float r2_10 = fract(11.0 / 3.0);
float theta_10 = TWOPI * r2_10;
poissonDisk = vec2(r1_10 * cos(theta_10), r1_10 * sin(theta_10));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_11 = sqrt(11.0 / 16.0);
float r2_11 = fract(12.0 / 3.0);
float theta_11 = TWOPI * r2_11;
poissonDisk = vec2(r1_11 * cos(theta_11), r1_11 * sin(theta_11));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_12 = sqrt(12.0 / 16.0);
float r2_12 = fract(13.0 / 3.0);
float theta_12 = TWOPI * r2_12;
poissonDisk = vec2(r1_12 * cos(theta_12), r1_12 * sin(theta_12));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_13 = sqrt(13.0 / 16.0);
float r2_13 = fract(14.0 / 3.0);
float theta_13 = TWOPI * r2_13;
poissonDisk = vec2(r1_13 * cos(theta_13), r1_13 * sin(theta_13));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_14 = sqrt(14.0 / 16.0);
float r2_14 = fract(15.0 / 3.0);
float theta_14 = TWOPI * r2_14;
poissonDisk = vec2(r1_14 * cos(theta_14), r1_14 * sin(theta_14));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
float r1_15 = sqrt(15.0 / 16.0);
float r2_15 = fract(16.0 / 3.0);
float theta_15 = TWOPI * r2_15;
poissonDisk = vec2(r1_15 * cos(theta_15), r1_15 * sin(theta_15));
color += texture2D(tex, uv + poissonDisk * sampleRadius);
return bilateralFilter(tex, uv, color, sampleRadius, noiseThreshold, intensity);
}
void main() {
vec2 tc_no_dist = v_texcoord;
vec2 tc = applyBarrelDistortion(tc_no_dist, DISTORTION_AMOUNT);
// [-1, 1]
vec2 tc_no_dist_symmetric = tc_no_dist * 2.0 - 1.0;
// [0,1]
vec2 tc_no_dist_normalized = (tc_no_dist_symmetric + 1.0) / 2.0;
// vec4 color = texture2D(tex, tc);
vec4 color = supersample(tex, tc, SAMPLING_RADIUS, NOISE_THRESHOLD, DENOISE_INTENSITY);
color = applyAdaptiveSharpen(tc, color, tex);
color = applyPhosphorGlow(tc, color, tex);
color = reduceGlare(color);
color = mix(applyFakeHDREffect(color, HDR_BRIGHTNESS, HDR_CONTRAST, HDR_SATURATION, HDR_BLOOM), color, 0.5);
color = applyColorCorrection(color);
color /= SUPERSAMPLING_SAMPLES;
color = mix(applyChromaticAberration(tc, color), color, 0.25);
color = mix(color, applyVignette(color), 0.37);
color = applyBorder(tc_no_dist_normalized, color, 1.0 - BORDER_SIZE * BORDER_RADIUS, BORDER_COLOR);
color = mix(applyBorder(tc, color, BORDER_SIZE, BORDER_COLOR), color, 0.05);
color = applyScanlines(tc, color);
gl_FragColor = color;
gl_FragColor.a = 1.0;
}
-42
View File
@@ -1,42 +0,0 @@
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D tex;
uniform float time;
void warpco(inout vec2 tc) {
tc -= 0.5;
tc *= length(tc) * 2.0;
tc += 0.5;
}
float rand1d(float seed) {
return sin(seed*1454.0);
}
float rand2d(vec2 co)
{
return fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453);
}
vec3 rgb(in vec2 tc, float freq, float amp, inout vec4 centre) {
vec2 off = vec2(1.0/800.0, 0.0) * sin(tc.t * freq + time) * amp;
vec2 off2 = vec2(1.0/800.0, 0.0) * sin(tc.t * freq - time * 1.5) * amp;
centre = texture2D(tex, tc);
return vec3(texture2D(tex, tc-off).r, centre.g, texture2D(tex, tc+off2).b);
}
void main() {
// vec2 px = 1.0 / textureSize(tex, 0).st;
vec2 tc = v_texcoord;
warpco(tc);
tc = mix(v_texcoord, tc, sin(time * 2.0)*0.07);
tc.x += rand2d(floor(tc * 20.0 + floor(time * 2.5))) * 0.01;
tc.x += rand1d(floor(tc.x * 40.0)) * 0.005 * rand1d(time * 0.001);
tc.y += sin(tc.x + time) * 0.02;
vec4 centre;
vec3 bent = rgb(tc, 100.0, 5.0, centre);
vec3 col = mix(centre.rgb, bent, sin(time));
gl_FragColor = vec4(col, centre.a);
// gl_FragColor = vec4(texture2D(tex, v_texcoord));
}
-21
View File
@@ -1,21 +0,0 @@
// vim: set ft=glsl:
// blue light filter shader
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
// red
pixColor[0] *= 0.7;
// green
pixColor[1] *= 0.6;
// blue
pixColor[2] *= 0.5;
gl_FragColor = pixColor;
}
-13
View File
@@ -1,13 +0,0 @@
// vim: set ft=glsl:
// blue light filter shader
// values from https://reshade.me/forum/shader-discussion/3673-blue-light-filter-similar-to-f-lux
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
vec4 pixColor = texture2D(tex, v_texcoord);
pixColor.rgb = 1.0 - pixColor.rgb;
gl_FragColor = pixColor;
}
-41
View File
@@ -1,41 +0,0 @@
// -*- mode:c -*-
precision lowp float;
varying vec2 v_texcoord;
uniform sampler2D tex;
float distanceSquared(vec3 pixColor, vec3 solarizedColor) {
vec3 distanceVector = pixColor - solarizedColor;
return dot(distanceVector, distanceVector);
}
void main() {
vec3 solarized[16];
solarized[0] = vec3(0.,0.169,0.212);
solarized[1] = vec3(0.027,0.212,0.259);
solarized[2] = vec3(0.345,0.431,0.459);
solarized[3] = vec3(0.396,0.482,0.514);
solarized[4] = vec3(0.514,0.58,0.588);
solarized[5] = vec3(0.576,0.631,0.631);
solarized[6] = vec3(0.933,0.91,0.835);
solarized[7] = vec3(0.992,0.965,0.89);
solarized[8] = vec3(0.71,0.537,0.);
solarized[9] = vec3(0.796,0.294,0.086);
solarized[10] = vec3(0.863,0.196,0.184);
solarized[11] = vec3(0.827,0.212,0.51);
solarized[12] = vec3(0.424,0.443,0.769);
solarized[13] = vec3(0.149,0.545,0.824);
solarized[14] = vec3(0.165,0.631,0.596);
solarized[15] = vec3(0.522,0.6,0.);
vec3 pixColor = vec3(texture2D(tex, v_texcoord));
int closest = 0;
float closestDistanceSquared = distanceSquared(pixColor, solarized[0]);
for (int i = 1; i < 15; i++) {
float newDistanceSquared = distanceSquared(pixColor, solarized[i]);
if (newDistanceSquared < closestDistanceSquared) {
closest = i;
closestDistanceSquared = newDistanceSquared;
}
}
gl_FragColor = vec4(solarized[closest], 1.);
}
+1
View File
@@ -10,6 +10,7 @@ pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root
property bool barOpen: true property bool barOpen: true
property bool crosshairOpen: false
property bool sidebarLeftOpen: false property bool sidebarLeftOpen: false
property bool sidebarRightOpen: false property bool sidebarRightOpen: false
property bool mediaControlsOpen: false property bool mediaControlsOpen: false
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions as CF import qs.modules.common.functions as CF
import QtQuick import QtQuick
@@ -13,14 +14,14 @@ import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
Variants { Variants {
id: root id: root
readonly property bool fixedClockPosition: Config.options.background.fixedClockPosition readonly property bool fixedClockPosition: Config.options.background.clock.fixedPosition
readonly property real fixedClockX: Config.options.background.clockX readonly property real fixedClockX: Config.options.background.clock.x
readonly property real fixedClockY: Config.options.background.clockY readonly property real fixedClockY: Config.options.background.clock.y
readonly property real clockSizePadding: 20 readonly property real clockSizePadding: 20
readonly property real screenSizePadding: 50 readonly property real screenSizePadding: 50
readonly property string clockStyle: Config.options.background.clock.style
model: Quickshell.screens model: Quickshell.screens
PanelWindow { PanelWindow {
@@ -29,8 +30,8 @@ Variants {
required property var modelData required property var modelData
// Hide when fullscreen // Hide when fullscreen
property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name) property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name)
property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0] property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0]
visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen
// Workspaces // Workspaces
@@ -39,12 +40,14 @@ Variants {
property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1 property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1
property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10 property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10
// Wallpaper // Wallpaper
property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov")
|| Config.options.background.wallpaperPath.endsWith(".webm")
|| Config.options.background.wallpaperPath.endsWith(".mkv")
|| Config.options.background.wallpaperPath.endsWith(".avi")
|| Config.options.background.wallpaperPath.endsWith(".mov")
property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath
property bool wallpaperSafetyTriggered: {
const enabled = Config.options.workSafety.enable.wallpaper
const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords))
const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords))
return enabled && sensitiveWallpaper && sensitiveNetwork;
}
property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height) property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height)
property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom
property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated
@@ -57,7 +60,7 @@ Variants {
property real clockX: (modelData.width / 2) property real clockX: (modelData.width / 2)
property real clockY: (modelData.height / 2) property real clockY: (modelData.height / 2)
property var textHorizontalAlignment: { property var textHorizontalAlignment: {
if (Config.options.background.blur.enable && Config.options.background.blur.centerClock && GlobalStates.screenLocked) if ((Config.options.lock.centerClock && GlobalStates.screenLocked) || wallpaperSafetyTriggered)
return Text.AlignHCenter; return Text.AlignHCenter;
if (clockX < screen.width / 3) if (clockX < screen.width / 3)
return Text.AlignLeft; return Text.AlignLeft;
@@ -66,10 +69,17 @@ Variants {
return Text.AlignHCenter; return Text.AlignHCenter;
} }
// Colors // Colors
property color dominantColor: Appearance.colors.colPrimary property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable)
property color dominantColor: Appearance.colors.colPrimary // Default, to be changed
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5 property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colText: CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12)) property color colText: {
property bool shouldBlur: (GlobalStates.screenLocked && Config.options.background.blur.enable) if (wallpaperSafetyTriggered)
return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75);
return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12));
}
Behavior on colText {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
// Layer props // Layer props
screen: modelData screen: modelData
@@ -83,42 +93,43 @@ Variants {
left: true left: true
right: true right: true
} }
color: "transparent" color: CF.ColorUtils.transparentize(CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75), (bgRoot.wallpaperIsVideo ? 1 : 0))
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
onWallpaperPathChanged: { onWallpaperPathChanged: {
bgRoot.updateZoomScale() bgRoot.updateZoomScale();
// Clock position gets updated after zoom scale is updated // Clock position gets updated after zoom scale is updated
} }
// Wallpaper zoom scale // Wallpaper zoom scale
function updateZoomScale() { function updateZoomScale() {
getWallpaperSizeProc.path = bgRoot.wallpaperPath getWallpaperSizeProc.path = bgRoot.wallpaperPath;
getWallpaperSizeProc.running = true; getWallpaperSizeProc.running = true;
} }
Process { Process {
id: getWallpaperSizeProc id: getWallpaperSizeProc
property string path: bgRoot.wallpaperPath property string path: bgRoot.wallpaperPath
command: [ "magick", "identify", "-format", "%w %h", path ] command: ["magick", "identify", "-format", "%w %h", path]
stdout: StdioCollector { stdout: StdioCollector {
id: wallpaperSizeOutputCollector id: wallpaperSizeOutputCollector
onStreamFinished: { onStreamFinished: {
const output = wallpaperSizeOutputCollector.text const output = wallpaperSizeOutputCollector.text;
const [width, height] = output.split(" ").map(Number); const [width, height] = output.split(" ").map(Number);
const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height]; const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height];
bgRoot.wallpaperWidth = width bgRoot.wallpaperWidth = width;
bgRoot.wallpaperHeight = height bgRoot.wallpaperHeight = height;
if (width <= screenWidth || height <= screenHeight) { // Undersized/perfectly sized wallpapers if (width <= screenWidth || height <= screenHeight) {
// Undersized/perfectly sized wallpapers
bgRoot.effectiveWallpaperScale = Math.max(screenWidth / width, screenHeight / height); bgRoot.effectiveWallpaperScale = Math.max(screenWidth / width, screenHeight / height);
} else { // Oversized = can be zoomed for parallax, yay } else {
bgRoot.effectiveWallpaperScale = Math.min( // Oversized = can be zoomed for parallax, yay
bgRoot.preferredWallpaperScale, bgRoot.effectiveWallpaperScale = Math.min(bgRoot.preferredWallpaperScale, width / screenWidth, height / screenHeight);
width / screenWidth, height / screenHeight
);
} }
bgRoot.updateClockPosition();
bgRoot.updateClockPosition()
} }
} }
} }
@@ -126,11 +137,11 @@ Variants {
// Clock positioning // Clock positioning
function updateClockPosition() { function updateClockPosition() {
// Somehow all this manual setting is needed to make the proc correctly use the new values // Somehow all this manual setting is needed to make the proc correctly use the new values
leastBusyRegionProc.path = bgRoot.wallpaperPath leastBusyRegionProc.path = bgRoot.wallpaperPath;
leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2 leastBusyRegionProc.contentWidth = clockLoader.implicitWidth + root.clockSizePadding * 2;
leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2 leastBusyRegionProc.contentHeight = clockLoader.implicitHeight + root.clockSizePadding * 2;
leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2 leastBusyRegionProc.horizontalPadding = bgRoot.movableXSpace + root.screenSizePadding * 2;
leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2 leastBusyRegionProc.verticalPadding = bgRoot.movableYSpace + root.screenSizePadding * 2;
leastBusyRegionProc.running = false; leastBusyRegionProc.running = false;
leastBusyRegionProc.running = true; leastBusyRegionProc.running = true;
} }
@@ -141,246 +152,317 @@ Variants {
property int contentHeight: 300 property int contentHeight: 300
property int horizontalPadding: bgRoot.movableXSpace property int horizontalPadding: bgRoot.movableXSpace
property int verticalPadding: bgRoot.movableYSpace property int verticalPadding: bgRoot.movableYSpace
command: [Quickshell.shellPath("scripts/images/least_busy_region.py"), command: [Quickshell.shellPath("scripts/images/least_busy_region.py"), "--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale), "--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale), "--width", contentWidth, "--height", contentHeight, "--horizontal-padding", horizontalPadding, "--vertical-padding", verticalPadding, path
"--screen-width", Math.round(bgRoot.screen.width / bgRoot.effectiveWallpaperScale),
"--screen-height", Math.round(bgRoot.screen.height / bgRoot.effectiveWallpaperScale),
"--width", contentWidth,
"--height", contentHeight,
"--horizontal-padding", horizontalPadding,
"--vertical-padding", verticalPadding,
path,
// "--visual-output", // "--visual-output",
] ,]
stdout: StdioCollector { stdout: StdioCollector {
id: leastBusyRegionOutputCollector id: leastBusyRegionOutputCollector
onStreamFinished: { onStreamFinished: {
const output = leastBusyRegionOutputCollector.text const output = leastBusyRegionOutputCollector.text;
// console.log("[Background] Least busy region output:", output) // console.log("[Background] Least busy region output:", output)
if (output.length === 0) return; if (output.length === 0)
const parsedContent = JSON.parse(output) return;
bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale const parsedContent = JSON.parse(output);
bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale bgRoot.clockX = parsedContent.center_x * bgRoot.effectiveWallpaperScale;
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary bgRoot.clockY = parsedContent.center_y * bgRoot.effectiveWallpaperScale;
bgRoot.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary;
} }
} }
} }
// Wallpaper // Wallpaper
Image { Item {
id: wallpaper anchors.fill: parent
visible: opacity > 0 && !blurLoader.active clip: true
opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
cache: false
asynchronous: true
retainWhileLoading: true
smooth: false
// Range = groups that workspaces span on
property int chunkSize: Config?.options.bar.workspaces.shown ?? 10;
property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize;
property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize;
property int range: upper - lower;
property real valueX: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
if (Config.options.background.parallax.enableSidebar) {
result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen);
}
return result;
}
property real valueY: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
return result;
}
property real effectiveValueX: Math.max(0, Math.min(1, valueX))
property real effectiveValueY: Math.max(0, Math.min(1, valueY))
x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace
y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace
source: bgRoot.wallpaperPath
fillMode: Image.PreserveAspectCrop
Behavior on x {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
sourceSize {
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
}
width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
}
Loader { Image {
id: blurLoader id: wallpaper
active: Config.options.background.blur.enable && (GlobalStates.screenLocked || scaleAnim.running) visible: opacity > 0 && !blurLoader.active
anchors.fill: wallpaper opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0
scale: GlobalStates.screenLocked ? Config.options.background.blur.extraZoom : 1 Behavior on opacity {
Behavior on scale { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
NumberAnimation {
id: scaleAnim
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
} }
cache: false
asynchronous: true
retainWhileLoading: true
smooth: false
// Range = groups that workspaces span on
property int chunkSize: Config?.options.bar.workspaces.shown ?? 10
property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize
property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize
property int range: upper - lower
property real valueX: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
if (Config.options.background.parallax.enableSidebar) {
result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen);
}
return result;
}
property real valueY: {
let result = 0.5;
if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) {
result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range);
}
return result;
}
property real effectiveValueX: Math.max(0, Math.min(1, valueX))
property real effectiveValueY: Math.max(0, Math.min(1, valueY))
x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace
y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace
source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath
fillMode: Image.PreserveAspectCrop
Behavior on x {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: 600
easing.type: Easing.OutCubic
}
}
sourceSize {
width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale
}
width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale
} }
sourceComponent: GaussianBlur {
source: wallpaper
radius: GlobalStates.screenLocked ? Config.options.background.blur.radius : 0
samples: radius * 2 + 1
Rectangle { Loader {
opacity: GlobalStates.screenLocked ? 1 : 0 id: blurLoader
anchors.fill: parent active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running)
color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7) anchors.fill: wallpaper
scale: GlobalStates.screenLocked ? Config.options.lock.blur.extraZoom : 1
Behavior on scale {
NumberAnimation {
id: scaleAnim
duration: 400
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
} }
} sourceComponent: GaussianBlur {
} source: wallpaper
radius: GlobalStates.screenLocked ? Config.options.lock.blur.radius : 0
samples: radius * 2 + 1
// The clock Rectangle {
Loader { opacity: GlobalStates.screenLocked ? 1 : 0
id: clockLoader anchors.fill: parent
active: Config.options.background.showClock color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7)
anchors { }
left: wallpaper.left
top: wallpaper.top
horizontalCenter: undefined
leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
topMargin: {
if (bgRoot.shouldBlur)
return bgRoot.modelData.height / 3
return bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
}
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
} }
} }
states: State {
name: "centered" // The clock
when: bgRoot.shouldBlur Loader {
AnchorChanges { id: clockLoader
target: clockLoader scale: Config.options.background.clock.scale
active: Config.options.background.clock.show
anchors {
left: wallpaper.left
top: wallpaper.top
horizontalCenter: undefined
verticalCenter: undefined
leftMargin: bgRoot.movableXSpace + ((root.fixedClockPosition ? root.fixedClockX : bgRoot.clockX * bgRoot.effectiveWallpaperScale) - implicitWidth / 2)
topMargin: bgRoot.movableYSpace + ((root.fixedClockPosition ? root.fixedClockY : bgRoot.clockY * bgRoot.effectiveWallpaperScale) - implicitHeight / 2)
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
states: State {
name: "centered"
when: (GlobalStates.screenLocked && Config.options.lock.centerClock) || bgRoot.wallpaperSafetyTriggered
AnchorChanges {
target: clockLoader
anchors {
left: undefined
right: undefined
top: undefined
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
}
}
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
sourceComponent: ColumnLayout {
spacing: 8
Loader {
id: digitalClockLoader
visible: root.clockStyle === "digital"
active: visible
sourceComponent: ColumnLayout {
id: clockColumn
spacing: 6
ClockText {
font.pixelSize: 90
text: DateTime.time
}
ClockText {
Layout.topMargin: -5
text: DateTime.date
}
StyledText {
// Somehow gets fucked up if made a ClockText???
visible: Config.options.background.quote.length > 0
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.main
pixelSize: Appearance.font.pixelSize.normal
weight: 350
italic: true
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: Config.options.background.quote
}
}
}
Loader {
id: cookieClockLoader
Layout.alignment: Qt.AlignHCenter
visible: root.clockStyle === "cookie"
active: visible
sourceComponent: CookieClock {}
}
}
Item {
anchors { anchors {
left: undefined top: clockLoader.bottom
horizontalCenter: wallpaper.horizontalCenter topMargin: 8
right: undefined horizontalCenter: (bgRoot.textHorizontalAlignment === Text.AlignHCenter || root.clockStyle === "cookie") ? clockLoader.horizontalCenter : undefined
left: (bgRoot.textHorizontalAlignment === Text.AlignLeft) ? clockLoader.left : undefined
right: (bgRoot.textHorizontalAlignment === Text.AlignRight) ? clockLoader.right : undefined
leftMargin: -26
rightMargin: -26
} }
} implicitWidth: statusTextBg.implicitWidth
} implicitHeight: statusTextBg.implicitHeight
transitions: Transition {
AnchorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
sourceComponent: Item {
id: clock
implicitWidth: clockColumn.implicitWidth
implicitHeight: clockColumn.implicitHeight
ColumnLayout { StyledRectangularShadow {
id: clockColumn target: statusTextBg
anchors.centerIn: parent visible: statusTextBg.visible && root.clockStyle === "cookie"
spacing: 6 opacity: statusTextBg.opacity
}
StyledText { Rectangle {
Layout.fillWidth: true id: statusTextBg
horizontalAlignment: bgRoot.textHorizontalAlignment anchors.centerIn: parent
font { clip: true
family: Appearance.font.family.expressive opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0
pixelSize: 90 visible: opacity > 0
weight: Font.Bold implicitHeight: statusTextRow.implicitHeight + 5 * 2
implicitWidth: statusTextRow.implicitWidth + 5 * 2
radius: Appearance.rounding.small
color: CF.ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === "cookie" ? 0 : 1)
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
} }
color: bgRoot.colText Behavior on implicitHeight {
style: Text.Raised animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
styleColor: Appearance.colors.colShadow
text: DateTime.time
}
StyledText {
Layout.fillWidth: true
Layout.topMargin: -5
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
} }
color: bgRoot.colText Behavior on opacity {
style: Text.Raised animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
styleColor: Appearance.colors.colShadow
text: DateTime.date
animateChange: true
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
} }
color: bgRoot.colText
style: Text.Raised
visible: Config.options.background.quote !== ""
styleColor: Appearance.colors.colShadow
text: Config.options.background.quote
}
}
RowLayout { RowLayout {
anchors { id: statusTextRow
top: clockColumn.bottom anchors.centerIn: parent
left: bgRoot.textHorizontalAlignment === Text.AlignLeft ? clockColumn.left : undefined spacing: 14
right: bgRoot.textHorizontalAlignment === Text.AlignRight ? clockColumn.right : undefined Item {
horizontalCenter: bgRoot.textHorizontalAlignment === Text.AlignHCenter ? clockColumn.horizontalCenter : undefined Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft
topMargin: 5 implicitWidth: 1
leftMargin: -5 }
rightMargin: -5 ClockStatusText {
id: safetyStatusText
shown: bgRoot.wallpaperSafetyTriggered
statusIcon: "hide_image"
statusText: qsTr("Wallpaper safety enforced")
}
ClockStatusText {
id: lockStatusText
shown: GlobalStates.screenLocked && Config.options.lock.showLockedText
statusIcon: "lock"
statusText: qsTr("Locked")
}
Item {
Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight
implicitWidth: 1
}
}
} }
opacity: GlobalStates.screenLocked && (!Config.options.background.blur.enable || Config.options.background.blur.showLockedText) ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignLeft; implicitWidth: 1 }
MaterialSymbol {
text: "lock"
Layout.fillWidth: false
iconSize: Appearance.font.pixelSize.huge
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
StyledText {
Layout.fillWidth: false
text: "Locked"
color: bgRoot.colText
font.pixelSize: Appearance.font.pixelSize.larger
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
Item { Layout.fillWidth: bgRoot.textHorizontalAlignment !== Text.AlignRight; implicitWidth: 1 }
} }
} }
} }
} }
// Components
component ClockText: StyledText {
Layout.fillWidth: true
horizontalAlignment: bgRoot.textHorizontalAlignment
font {
family: Appearance.font.family.expressive
pixelSize: 20
weight: Font.DemiBold
}
color: bgRoot.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
animateChange: true
}
component ClockStatusText: RowLayout {
id: statusTextRow
property alias statusIcon: statusIconWidget.text
property alias statusText: statusTextWidget.text
property bool shown: true
property color textColor: root.clockStyle === "cookie" ? Appearance.colors.colOnSecondaryContainer : bgRoot.colText
opacity: shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Layout.fillWidth: false
MaterialSymbol {
id: statusIconWidget
Layout.fillWidth: false
iconSize: Appearance.font.pixelSize.huge
color: statusTextRow.textColor
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
ClockText {
id: statusTextWidget
Layout.fillWidth: false
color: statusTextRow.textColor
font {
family: Appearance.font.family.main
pixelSize: Appearance.font.pixelSize.large
weight: Font.Normal
}
style: Text.Raised
styleColor: Appearance.colors.colShadow
}
}
} }
@@ -0,0 +1,142 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Layouts
import Quickshell
import Qt5Compat.GraphicalEffects
Item {
id: root
property real implicitSize: 230
property real hourHandLength: 72
property real hourHandWidth: 16
property real minuteHandLength: 95
property real minuteHandWidth: 8
property real centerDotSize: 10
property real hourDotSize: minuteHandWidth
property color colShadow: Appearance.colors.colShadow
property color colBackground: Appearance.colors.colSecondaryContainer
property color colOnBackground: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer, 0.5)
property color colHourHand: Appearance.colors.colPrimary
property color colMinuteHand: Appearance.colors.colSecondary
property color colOnHourHand: Appearance.colors.colOnPrimary
readonly property list<string> clockNumbers: DateTime.time.split(/[: ]/)
readonly property int clockHour: parseInt(clockNumbers[0]) % 12
readonly property int clockMinute: parseInt(clockNumbers[1])
implicitWidth: implicitSize
implicitHeight: implicitSize
DropShadow {
source: cookie
anchors.fill: source
horizontalOffset: 0
verticalOffset: 2
radius: 12
samples: radius * 2 + 1
color: root.colShadow
transparentBorder: true
}
MaterialCookie {
id: cookie
z: 0
implicitSize: root.implicitSize
amplitude: implicitSize / 70
sides: 12
color: root.colBackground
// 12 dots around the cookie
Repeater {
model: 12
Item {
required property int index
rotation: 360 / 12 * index
anchors.fill: parent
Rectangle {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: 10
}
implicitWidth: root.hourDotSize
implicitHeight: implicitWidth
radius: implicitWidth / 2
color: root.colOnBackground
opacity: 0.5
}
}
}
}
Column {
id: timeIndicators
z: 1
anchors.centerIn: cookie
spacing: -16
// Numbers
Repeater {
model: root.clockNumbers
delegate: StyledText {
required property string modelData
anchors.horizontalCenter: parent?.horizontalCenter
font {
pixelSize: modelData.match(/am|pm/i) ? 26 : 68
family: Appearance.font.family.expressive
weight: Font.Bold
}
color: root.colOnBackground
text: modelData.padStart(2, "0")
}
}
}
// Hour hand
Item {
anchors.fill: parent
z: 2
rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: parent.width / 2 - hourHandWidth / 2
width: hourHandLength
height: hourHandWidth
radius: hourHandWidth / 2
color: root.colHourHand
}
}
// Minute hand
Item {
anchors.fill: parent
z: 3
rotation: -90 + (360 / 60) * root.clockMinute
Rectangle {
anchors.verticalCenter: parent.verticalCenter
x: parent.width / 2 - minuteHandWidth / 2
width: minuteHandLength
height: minuteHandWidth
radius: minuteHandWidth / 2
color: root.colMinuteHand
}
}
// Center dot
Rectangle {
z: 4
color: root.colOnHourHand
anchors.centerIn: parent
implicitWidth: centerDotSize
implicitHeight: implicitWidth
radius: implicitWidth / 2
}
}
+2 -4
View File
@@ -1,11 +1,9 @@
import "./weather" import "./weather"
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Services.UPower
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
@@ -93,8 +91,8 @@ Scope {
id: hoverMaskRegion id: hoverMaskRegion
anchors { anchors {
fill: barContent fill: barContent
topMargin: -1 topMargin: -Config.options.bar.autoHide.hoverRegionWidth
bottomMargin: -1 bottomMargin: -Config.options.bar.autoHide.hoverRegionWidth
} }
} }
@@ -289,16 +289,9 @@ Item { // Bar content region
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
} }
Loader { HyprlandXkbIndicator {
active: HyprlandXkb.layoutCodes.length > 1 Layout.alignment: Qt.AlignVCenter
visible: active
Layout.rightMargin: indicatorsRowLayout.realSpacing Layout.rightMargin: indicatorsRowLayout.realSpacing
sourceComponent: StyledText {
text: HyprlandXkb.currentLayoutCode
font.pixelSize: Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
} }
MaterialSymbol { MaterialSymbol {
Layout.rightMargin: indicatorsRowLayout.realSpacing Layout.rightMargin: indicatorsRowLayout.realSpacing
@@ -0,0 +1,27 @@
import QtQuick
import qs.services
import qs.modules.common
import qs.modules.common.widgets
Loader {
id: root
property bool vertical: false
active: HyprlandXkb.layoutCodes.length > 1
visible: active
sourceComponent: Item {
implicitWidth: root.vertical ? null : layoutCodeText.implicitWidth
implicitHeight: root.vertical ? layoutCodeText.implicitHeight : null
StyledText {
id: layoutCodeText
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: HyprlandXkb.currentLayoutCode.split(":").join("\n")
font.pixelSize: text.includes("\n") ? Appearance.font.pixelSize.smallie : Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ Item {
Timer { Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000 interval: Config.options.resources.updateInterval
repeat: true repeat: true
onTriggered: activePlayer.positionChanged() onTriggered: activePlayer.positionChanged()
} }
@@ -24,7 +24,7 @@ Revealer { // Scroll hint
// StyledToolTip { // StyledToolTip {
// extraVisibleCondition: tooltipText.length > 0 // extraVisibleCondition: tooltipText.length > 0
// content: tooltipText // text: tooltipText
// } // }
ColumnLayout { ColumnLayout {
@@ -1,4 +1,5 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
@@ -26,6 +27,10 @@ LazyLoader {
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin implicitWidth: popupBackground.implicitWidth + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
implicitHeight: popupBackground.implicitHeight + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin implicitHeight: popupBackground.implicitHeight + Appearance.sizes.hyprlandGapsOut * 2 + root.popupBackgroundMargin
mask: Region {
item: popupBackground
}
exclusionMode: ExclusionMode.Ignore exclusionMode: ExclusionMode.Ignore
exclusiveZone: 0 exclusiveZone: 0
margins { margins {
@@ -49,15 +54,8 @@ LazyLoader {
WlrLayershell.namespace: "quickshell:popup" WlrLayershell.namespace: "quickshell:popup"
WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.layer: WlrLayer.Overlay
RectangularShadow { StyledRectangularShadow {
property var target: popupBackground target: popupBackground
anchors.fill: target
radius: target.radius
blur: 0.9 * Appearance.sizes.hyprlandGapsOut
offset: Qt.vector2d(0.0, 1.0)
spread: 0.7
color: Appearance.colors.colShadow
cached: true
} }
Rectangle { Rectangle {
+49 -3
View File
@@ -3,6 +3,7 @@ import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
Item { Item {
@@ -14,20 +15,58 @@ Item {
property bool trayOverflowOpen: false property bool trayOverflowOpen: false
property bool showSeparator: true property bool showSeparator: true
property bool showOverflowMenu: true property bool showOverflowMenu: true
property var activeMenu: null
property list<var> itemsInUserList: SystemTray.items.values.filter(i => (Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive)) property list<var> itemsInUserList: SystemTray.items.values.filter(i => (Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive))
property list<var> itemsNotInUserList: SystemTray.items.values.filter(i => (!Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive)) property list<var> itemsNotInUserList: SystemTray.items.values.filter(i => (!Config.options.bar.tray.pinnedItems.includes(i.id) && i.status !== Status.Passive))
property bool invertPins: Config.options.bar.tray.invertPinnedItems property bool invertPins: Config.options.bar.tray.invertPinnedItems
property list<var> pinnedItems: invertPins ? itemsNotInUserList : itemsInUserList property list<var> pinnedItems: invertPins ? itemsNotInUserList : itemsInUserList
property list<var> unpinnedItems: invertPins ? itemsInUserList : itemsNotInUserList property list<var> unpinnedItems: invertPins ? itemsInUserList : itemsNotInUserList
onUnpinnedItemsChanged: if (unpinnedItems.length == 0) onUnpinnedItemsChanged: {
root.trayOverflowOpen = false if (unpinnedItems.length == 0) root.closeOverflowMenu();
}
function grabFocus() {
focusGrab.active = true;
}
function setExtraWindowAndGrabFocus(window) {
root.activeMenu = window;
root.grabFocus();
}
function releaseFocus() {
focusGrab.active = false;
}
function closeOverflowMenu() {
focusGrab.active = false;
}
onTrayOverflowOpenChanged: {
if (root.trayOverflowOpen) {
root.grabFocus();
}
}
HyprlandFocusGrab {
id: focusGrab
active: false
windows: [trayOverflowLayout.QsWindow?.window, root.activeMenu]
onCleared: {
root.trayOverflowOpen = false;
if (root.activeMenu) {
root.activeMenu.close();
root.activeMenu = null;
}
}
}
GridLayout { GridLayout {
id: gridLayout id: gridLayout
columns: root.vertical ? 1 : -1 columns: root.vertical ? 1 : -1
anchors.fill: parent anchors.fill: parent
rowSpacing: 6 rowSpacing: 8
columnSpacing: 15 columnSpacing: 15
RippleButton { RippleButton {
@@ -60,6 +99,7 @@ Item {
} }
StyledPopup { StyledPopup {
id: overflowPopup
hoverTarget: trayOverflowButton hoverTarget: trayOverflowButton
active: root.trayOverflowOpen active: root.trayOverflowOpen
popupBackgroundMargin: 300 // This should be plenty... makes sure tooltips don't get cutoff (easily) popupBackgroundMargin: 300 // This should be plenty... makes sure tooltips don't get cutoff (easily)
@@ -79,6 +119,8 @@ Item {
item: modelData item: modelData
Layout.fillHeight: !root.vertical Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical Layout.fillWidth: root.vertical
onMenuClosed: root.releaseFocus();
onMenuOpened: (qsWindow) => root.setExtraWindowAndGrabFocus(qsWindow);
} }
} }
} }
@@ -95,6 +137,10 @@ Item {
item: modelData item: modelData
Layout.fillHeight: !root.vertical Layout.fillHeight: !root.vertical
Layout.fillWidth: root.vertical Layout.fillWidth: root.vertical
onMenuClosed: root.releaseFocus();
onMenuOpened: (qsWindow) => {
root.setExtraWindowAndGrabFocus(qsWindow);
}
} }
} }
@@ -9,16 +9,17 @@ import Qt5Compat.GraphicalEffects
MouseArea { MouseArea {
id: root id: root
property var bar: root.QsWindow.window
required property SystemTrayItem item required property SystemTrayItem item
property bool targetMenuOpen: false property bool targetMenuOpen: false
hoverEnabled: true
signal menuOpened(qsWindow: var)
signal menuClosed()
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
implicitWidth: 20 implicitWidth: 20
implicitHeight: 20 implicitHeight: 20
onClicked: (event) => { onPressed: (event) => {
switch (event.button) { switch (event.button) {
case Qt.LeftButton: case Qt.LeftButton:
item.activate(); item.activate();
@@ -30,22 +31,36 @@ MouseArea {
event.accepted = true; event.accepted = true;
} }
onEntered: { onEntered: {
tooltip.content = item.tooltipTitle.length > 0 ? item.tooltipTitle tooltip.text = item.tooltipTitle.length > 0 ? item.tooltipTitle
: (item.title.length > 0 ? item.title : item.id); : (item.title.length > 0 ? item.title : item.id);
if (item.tooltipDescription.length > 0) tooltip.content += " • " + item.tooltipDescription; if (item.tooltipDescription.length > 0) tooltip.text += " • " + item.tooltipDescription;
if (Config.options.bar.tray.showItemId) tooltip.content += "\n[" + item.id + "]"; if (Config.options.bar.tray.showItemId) tooltip.text += "\n[" + item.id + "]";
} }
QsMenuAnchor { Loader {
id: menu id: menu
function open() {
menu: root.item.menu menu.active = true;
anchor.window: bar }
anchor.rect.x: root.x + (Config.options.bar.vertical ? 0 : bar?.width) active: false
anchor.rect.y: root.y + (Config.options.bar.vertical ? bar?.height : 0) sourceComponent: SysTrayMenu {
anchor.rect.height: root.height Component.onCompleted: this.open();
anchor.rect.width: root.width trayItemMenuHandle: root.item.menu
anchor.edges: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right) anchor {
window: root.QsWindow.window
rect.x: root.x + (Config.options.bar.vertical ? 0 : QsWindow.window?.width)
rect.y: root.y + (Config.options.bar.vertical ? QsWindow.window?.height : 0)
rect.height: root.height
rect.width: root.width
edges: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right)
gravity: Config.options.bar.bottom ? (Edges.Top | Edges.Left) : (Edges.Bottom | Edges.Right)
}
onMenuOpened: (window) => root.menuOpened(window);
onMenuClosed: {
root.menuClosed();
menu.active = false;
}
}
} }
IconImage { IconImage {
@@ -76,10 +91,11 @@ MouseArea {
} }
} }
StyledToolTip { PopupToolTip {
id: tooltip id: tooltip
extraVisibleCondition: root.containsMouse extraVisibleCondition: root.containsMouse
alternativeVisibleCondition: extraVisibleCondition alternativeVisibleCondition: extraVisibleCondition
anchorEdges: (!Config.options.bar.bottom && !Config.options.bar.vertical) ? Edges.Bottom : Edges.Top
} }
} }
@@ -0,0 +1,218 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
PopupWindow {
id: root
required property QsMenuHandle trayItemMenuHandle
property real popupBackgroundMargin: 0
signal menuClosed
signal menuOpened(qsWindow: var) // Correct type is QsWindow, but QML does not like that
color: "transparent"
property real padding: Appearance.sizes.elevationMargin
implicitHeight: {
let result = 0;
for (let child of stackView.children) {
result = Math.max(child.implicitHeight, result);
}
return result + popupBackground.padding * 2 + root.padding * 2;
}
implicitWidth: {
let result = 0;
for (let child of stackView.children) {
result = Math.max(child.implicitWidth, result);
}
return result + popupBackground.padding * 2 + root.padding * 2;
}
function open() {
root.visible = true;
root.menuOpened(root);
}
function close() {
root.visible = false;
while (stackView.depth > 1)
stackView.pop();
root.menuClosed();
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.BackButton | Qt.RightButton
onClicked: event => {
if ((event.button === Qt.BackButton || event.button === Qt.RightButton) && stackView.depth > 1)
stackView.pop();
}
StyledRectangularShadow {
target: popupBackground
opacity: popupBackground.opacity
}
Rectangle {
id: popupBackground
readonly property real padding: 4
anchors {
left: parent.left
right: parent.right
verticalCenter: Config.options.bar.vertical ? parent.verticalCenter : undefined
top: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? undefined : parent.top
bottom: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? parent.bottom : undefined
margins: root.padding
}
color: Appearance.colors.colLayer0
radius: Appearance.rounding.windowRounding
border.width: 1
border.color: Appearance.colors.colLayer0Border
clip: true
opacity: 0
Component.onCompleted: opacity = 1
implicitWidth: stackView.implicitWidth + popupBackground.padding * 2
implicitHeight: stackView.implicitHeight + popupBackground.padding * 2
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
Behavior on implicitWidth {
animation: Appearance.animation.elementResize.numberAnimation.createObject(this)
}
StackView {
id: stackView
anchors {
fill: parent
margins: popupBackground.padding
}
pushEnter: NoAnim {}
pushExit: NoAnim {}
popEnter: NoAnim {}
popExit: NoAnim {}
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
initialItem: SubMenu {
handle: root.trayItemMenuHandle
}
}
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: ColumnLayout {
id: submenu
required property QsMenuHandle handle
property bool isSubMenu: false
property bool shown: false
opacity: shown ? 1 : 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
menu: submenu.handle
}
spacing: 0
Loader {
Layout.fillWidth: true
visible: submenu.isSubMenu
active: visible
sourceComponent: RippleButton {
id: backButton
buttonRadius: popupBackground.radius - popupBackground.padding
horizontalPadding: 12
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: 36
onClicked: stackView.pop()
contentItem: RowLayout {
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: backButton.horizontalPadding
rightMargin: backButton.horizontalPadding
}
spacing: 8
MaterialSymbol {
iconSize: 20
text: "chevron_left"
}
StyledText {
Layout.fillWidth: true
text: Translation.tr("Back")
}
}
}
}
Repeater {
id: menuEntriesRepeater
property bool iconColumnNeeded: {
for (let i = 0; i < menuOpener.children.values.length; i++) {
if (menuOpener.children.values[i].icon.length > 0)
return true;
}
return false;
}
property bool specialInteractionColumnNeeded: {
for (let i = 0; i < menuOpener.children.values.length; i++) {
if (menuOpener.children.values[i].buttonType !== QsMenuButtonType.None)
return true;
}
return false;
}
model: menuOpener.children
delegate: SysTrayMenuEntry {
required property QsMenuEntry modelData
forceIconColumn: menuEntriesRepeater.iconColumnNeeded
forceSpecialInteractionColumn: menuEntriesRepeater.specialInteractionColumnNeeded
menuEntry: modelData
buttonRadius: popupBackground.radius - popupBackground.padding
onDismiss: root.close()
onOpenSubmenu: handle => {
stackView.push(subMenuComponent.createObject(null, {
handle: handle,
isSubMenu: true
}));
}
}
}
}
Component {
id: subMenuComponent
SubMenu {}
}
}
@@ -0,0 +1,126 @@
pragma ComponentBehavior: Bound
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
RippleButton {
id: root
required property QsMenuEntry menuEntry
property bool forceIconColumn: false
property bool forceSpecialInteractionColumn: false
readonly property bool hasIcon: menuEntry.icon.length > 0
readonly property bool hasSpecialInteraction: menuEntry.buttonType !== QsMenuButtonType.None
signal dismiss()
signal openSubmenu(handle: QsMenuHandle)
colBackground: menuEntry.isSeparator ? Appearance.m3colors.m3outlineVariant : ColorUtils.transparentize(Appearance.colors.colLayer0)
enabled: !menuEntry.isSeparator
opacity: 1
horizontalPadding: 12
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: menuEntry.isSeparator ? 1 : 36
Layout.topMargin: menuEntry.isSeparator ? 4 : 0
Layout.bottomMargin: menuEntry.isSeparator ? 4 : 0
Layout.fillWidth: true
Component.onCompleted: {
if (menuEntry.isSeparator) {
root.buttonColor = root.colBackground;
}
}
releaseAction: () => {
if (menuEntry.hasChildren) {
root.openSubmenu(root.menuEntry);
return;
}
menuEntry.triggered();
root.dismiss();
}
altAction: (event) => { // Not hog right-click
event.accepted = false;
}
contentItem: RowLayout {
id: contentItem
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
}
spacing: 8
visible: !root.menuEntry.isSeparator
// Interaction: checkbox or radio button
Item {
visible: root.hasSpecialInteraction || root.forceSpecialInteractionColumn
implicitWidth: 20
implicitHeight: 20
Loader {
anchors.fill: parent
active: root.menuEntry.buttonType === QsMenuButtonType.RadioButton
sourceComponent: StyledRadioButton {
enabled: false
padding: 0
checked: root.menuEntry.checkState === Qt.Checked
}
}
Loader {
anchors.fill: parent
active: root.menuEntry.buttonType === QsMenuButtonType.CheckBox && root.menuEntry.checkState !== Qt.Unchecked
sourceComponent: MaterialSymbol {
text: root.menuEntry.checkState === Qt.PartiallyChecked ? "check_indeterminate_small" : "check"
iconSize: 20
}
}
}
// Button icon
Item {
visible: root.hasIcon || root.forceIconColumn
implicitWidth: 20
implicitHeight: 20
Loader {
anchors.centerIn: parent
active: root.menuEntry.icon.length > 0
sourceComponent: IconImage {
asynchronous: true
source: root.menuEntry.icon
implicitSize: 20
mipmap: true
}
}
}
StyledText {
id: label
text: root.menuEntry.text
font.pixelSize: Appearance.font.pixelSize.smallie
Layout.fillWidth: true
}
Loader {
active: root.menuEntry.hasChildren
sourceComponent: MaterialSymbol {
text: "chevron_right"
iconSize: 20
}
}
}
}
@@ -204,7 +204,7 @@ Item {
rowSpacing: 0 rowSpacing: 0
anchors.fill: parent anchors.fill: parent
implicitHeight: vertical ? Appearance.sizes.barWidth : Appearance.sizes.barHeight implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
Repeater { Repeater {
@@ -212,6 +212,7 @@ Singleton {
property QtObject pixelSize: QtObject { property QtObject pixelSize: QtObject {
property int smallest: 10 property int smallest: 10
property int smaller: 12 property int smaller: 12
property int smallie: 13
property int small: 15 property int small: 15
property int normal: 16 property int normal: 16
property int large: 17 property int large: 17
@@ -254,14 +255,8 @@ Singleton {
easing.bezierCurve: root.animation.elementMove.bezierCurve easing.bezierCurve: root.animation.elementMove.bezierCurve
} }
} }
property Component colorAnimation: Component {
ColorAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
} }
property QtObject elementMoveEnter: QtObject { property QtObject elementMoveEnter: QtObject {
property int duration: 400 property int duration: 400
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
@@ -275,6 +270,7 @@ Singleton {
} }
} }
} }
property QtObject elementMoveExit: QtObject { property QtObject elementMoveExit: QtObject {
property int duration: 200 property int duration: 200
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
@@ -288,6 +284,7 @@ Singleton {
} }
} }
} }
property QtObject elementMoveFast: QtObject { property QtObject elementMoveFast: QtObject {
property int duration: animationCurves.expressiveEffectsDuration property int duration: animationCurves.expressiveEffectsDuration
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
@@ -304,6 +301,21 @@ Singleton {
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}} }}
} }
property QtObject elementResize: QtObject {
property int duration: 400
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasized
property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementResize.duration
easing.type: root.animation.elementResize.type
easing.bezierCurve: root.animation.elementResize.bezierCurve
}
}
}
property QtObject clickBounce: QtObject { property QtObject clickBounce: QtObject {
property int duration: 200 property int duration: 200
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
@@ -315,11 +327,13 @@ Singleton {
easing.bezierCurve: root.animation.clickBounce.bezierCurve easing.bezierCurve: root.animation.clickBounce.bezierCurve
}} }}
} }
property QtObject scroll: QtObject { property QtObject scroll: QtObject {
property int duration: 200 property int duration: 200
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.standardDecel property list<real> bezierCurve: animationCurves.standardDecel
} }
property QtObject menuDecel: QtObject { property QtObject menuDecel: QtObject {
property int duration: 350 property int duration: 350
property int type: Easing.OutExpo property int type: Easing.OutExpo
+42 -13
View File
@@ -122,12 +122,18 @@ Singleton {
} }
property JsonObject background: JsonObject { property JsonObject background: JsonObject {
property bool fixedClockPosition: false property JsonObject clock: JsonObject {
property real clockX: -500 property bool fixedPosition: false
property real clockY: -500 property real x: -500
property bool showClock: true property real y: -500
property bool show: true
property string style: "cookie" // Options: "cookie", "digital"
property real scale: 1
}
property string wallpaperPath: "" property string wallpaperPath: ""
property string thumbnailPath: "" property string thumbnailPath: ""
property string quote: ""
property bool hideWhenFullscreen: true
property JsonObject parallax: JsonObject { property JsonObject parallax: JsonObject {
property bool vertical: false property bool vertical: false
property bool autoVertical: false property bool autoVertical: false
@@ -135,20 +141,12 @@ Singleton {
property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size
property bool enableSidebar: true property bool enableSidebar: true
} }
property JsonObject blur: JsonObject {
property bool enable: true
property int radius: 100
property bool centerClock: true
property bool showLockedText: true
property real extraZoom: 1.1
}
property string quote: ""
property bool hideWhenFullscreen: true
} }
property JsonObject bar: JsonObject { property JsonObject bar: JsonObject {
property JsonObject autoHide: JsonObject { property JsonObject autoHide: JsonObject {
property bool enable: false property bool enable: false
property int hoverRegionWidth: 2
property bool pushWindows: false property bool pushWindows: false
property JsonObject showWhenPressingSuper: JsonObject { property JsonObject showWhenPressingSuper: JsonObject {
property bool enable: true property bool enable: true
@@ -214,6 +212,11 @@ Singleton {
property bool autoKillTrays: false property bool autoKillTrays: false
} }
property JsonObject crosshair: JsonObject {
// Valorant crosshair format. Use https://www.vcrdb.net/builder
property string code: "0;P;d;1;0l;10;0o;2;1b;0"
}
property JsonObject dock: JsonObject { property JsonObject dock: JsonObject {
property bool enable: false property bool enable: false
property bool monochromeIcons: true property bool monochromeIcons: true
@@ -256,6 +259,16 @@ Singleton {
} }
} }
property JsonObject lock: JsonObject {
property JsonObject blur: JsonObject {
property bool enable: false
property real radius: 100
property real extraZoom: 1.1
}
property bool centerClock: true
property bool showLockedText: true
}
property JsonObject media: JsonObject { property JsonObject media: JsonObject {
// Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration) // Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration)
property bool filterDuplicatePlayers: true property bool filterDuplicatePlayers: true
@@ -265,6 +278,10 @@ Singleton {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
} }
property JsonObject notifications: JsonObject {
property int timeout: 7000
}
property JsonObject osd: JsonObject { property JsonObject osd: JsonObject {
property int timeout: 1000 property int timeout: 1000
} }
@@ -355,6 +372,18 @@ Singleton {
property JsonObject screenshotTool: JsonObject { property JsonObject screenshotTool: JsonObject {
property bool showContentRegions: true property bool showContentRegions: true
} }
property JsonObject workSafety: JsonObject {
property JsonObject enable: JsonObject {
property bool wallpaper: true
property bool clipboard: true
}
property JsonObject triggerCondition: JsonObject {
property list<string> networkNameKeywords: ["airport", "cafe", "college", "company", "eduroam", "free", "guest", "public", "school", "university"]
property list<string> fileKeywords: ["anime", "ecchi", "hentai", "yande.re", "konachan", "breast", "nipples", "pussy", "nsfw", "spoiler", "girl"]
property list<string> linkKeywords: ["hentai", "porn", "sukebei", "hitomi.la", "rule34", "gelbooru", "fanbox", "dlsite"]
}
}
} }
} }
} }
@@ -10,7 +10,9 @@ Singleton {
* @returns {string} * @returns {string}
*/ */
function trimFileProtocol(str) { function trimFileProtocol(str) {
return str.startsWith("file://") ? str.slice(7) : str; let s = str;
if (typeof s !== "string") s = str.toString(); // Convert to string if it's an url or whatever
return s.startsWith("file://") ? s.slice(7) : s;
} }
/** /**
@@ -8,7 +8,7 @@ Singleton {
* Formats a string according to the args that are passed inc * Formats a string according to the args that are passed inc
* @param { string } str * @param { string } str
* @param {...any} args * @param {...any} args
* @returns * @returns { string }
*/ */
function format(str, ...args) { function format(str, ...args) {
return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match); return str.replace(/{(\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match);
@@ -35,10 +35,10 @@ Singleton {
} }
/** /**
* Escapes single quotes in shell commands * Escapes single quotes in shell commands
* @param { string } str * @param { string } str
* @returns { string } * @returns { string }
*/ */
function shellSingleQuoteEscape(str) { function shellSingleQuoteEscape(str) {
return String(str) return String(str)
// .replace(/\\/g, '\\\\') // .replace(/\\/g, '\\\\')
@@ -48,6 +48,7 @@ Singleton {
/** /**
* Splits markdown blocks into three different types: text, think, and code. * Splits markdown blocks into three different types: text, think, and code.
* @param { string } markdown * @param { string } markdown
* @returns {Array<{type: "text" | "think" | "code", content: string, lang?: string, completed?: boolean}>}
*/ */
function splitMarkdownBlocks(markdown) { function splitMarkdownBlocks(markdown) {
const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g; const regex = /```(\w+)?\n([\s\S]*?)```|<think>([\s\S]*?)<\/think>/g;
@@ -182,6 +183,11 @@ Singleton {
return lines.join("\n"); return lines.join("\n");
} }
/**
* Cleans up a music title by removing bracketed and special characters.
* @param { string } title
* @returns { string }
*/
function cleanMusicTitle(title) { function cleanMusicTitle(title) {
if (!title) if (!title)
return ""; return "";
@@ -198,6 +204,11 @@ Singleton {
return title.trim(); return title.trim();
} }
/**
* Converts seconds to a friendly time string (e.g. 1:23 or 1:02:03).
* @param { number } seconds
* @returns { string }
*/
function friendlyTimeForSeconds(seconds) { function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0) if (isNaN(seconds) || seconds < 0)
return "0:00"; return "0:00";
@@ -212,13 +223,38 @@ Singleton {
} }
} }
/**
* Escapes HTML special characters in a string.
* @param { string } str
* @returns { string }
*/
function escapeHtml(str) { function escapeHtml(str) {
if (typeof str !== 'string') if (typeof str !== 'string')
return str; return str;
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;'); return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
} }
/**
* Cleans a cliphist entry by removing leading digits and tab.
* @param { string } str
* @returns { string }
*/
function cleanCliphistEntry(str: string): string { function cleanCliphistEntry(str: string): string {
return str.replace(/^\d+\t/, ""); return str.replace(/^\d+\t/, "");
} }
/**
* Checks if any substring in the list is contained in the string.
* @param { string } str
* @param { string[] } substrings
* @returns { boolean }
*/
function stringListContainsSubstring(str, substrings) {
for (let i = 0; i < substrings.length; ++i) {
if (str.includes(substrings[i])) {
return true;
}
}
return false;
}
} }
@@ -0,0 +1,27 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
/**
* Material color scheme adapted to a given color. It's incomplete but enough for what we need...
*/
QtObject {
id: root
required property color color
readonly property bool colorIsDark: color.hslLightness < 0.5
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, root.color, (colorIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, root.color, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, root.color, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, root.color), root.color, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, root.color), root.color, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, root.color), root.color, 0.3)
property color colSecondary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colSecondary, root.color), root.color, 0.5)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, root.color, 0.15)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, root.color, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, root.color, 0.5)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, root.color), root.color, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, root.color, 0.5)
}
@@ -113,7 +113,7 @@ Rectangle {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Edit directory") text: Translation.tr("Edit directory")
} }
} }
} }
@@ -12,6 +12,8 @@ Rectangle {
property string entry property string entry
property real maxWidth property real maxWidth
property real maxHeight property real maxHeight
property bool blur: false
property string blurText: "Image hidden"
property string imageDecodePath: Directories.cliphistDecode property string imageDecodePath: Directories.cliphistDecode
property string imageDecodeFileName: `${entryNumber}` property string imageDecodeFileName: `${entryNumber}`
@@ -19,26 +21,25 @@ Rectangle {
property string source property string source
property int entryNumber: { property int entryNumber: {
if (!root.entry) return 0 if (!root.entry)
const match = root.entry.match(/^(\d+)\t/) return 0;
return match ? parseInt(match[1]) : 0 const match = root.entry.match(/^(\d+)\t/);
return match ? parseInt(match[1]) : 0;
} }
property int imageWidth: { property int imageWidth: {
if (!root.entry) return 0 if (!root.entry)
const match = root.entry.match(/(\d+)x(\d+)/) return 0;
return match ? parseInt(match[1]) : 0 const match = root.entry.match(/(\d+)x(\d+)/);
return match ? parseInt(match[1]) : 0;
} }
property int imageHeight: { property int imageHeight: {
if (!root.entry) return 0 if (!root.entry)
const match = root.entry.match(/(\d+)x(\d+)/) return 0;
return match ? parseInt(match[2]) : 0 const match = root.entry.match(/(\d+)x(\d+)/);
return match ? parseInt(match[2]) : 0;
} }
property real scale: { property real scale: {
return Math.min( return Math.min(root.maxWidth / imageWidth, root.maxHeight / imageHeight, 1);
root.maxWidth / imageWidth,
root.maxHeight / imageHeight,
1
)
} }
color: Appearance.colors.colLayer1 color: Appearance.colors.colLayer1
@@ -47,26 +48,33 @@ Rectangle {
implicitWidth: imageWidth * scale implicitWidth: imageWidth * scale
Component.onCompleted: { Component.onCompleted: {
decodeImageProcess.running = true decodeImageProcess.running = true;
} }
Process { Process {
id: decodeImageProcess id: decodeImageProcess
command: ["bash", "-c", command: ["bash", "-c", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]
`[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`
]
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (exitCode === 0) { if (exitCode === 0) {
root.source = imageDecodeFilePath root.source = imageDecodeFilePath;
} else { } else {
console.error("[CliphistImage] Failed to decode image for entry:", root.entry) console.error("[CliphistImage] Failed to decode image for entry:", root.entry);
root.source = "" root.source = "";
} }
} }
} }
Component.onDestruction: { Component.onDestruction: {
Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]) Quickshell.execDetached(["bash", "-c", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]);
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: image.width
height: image.height
radius: root.radius
}
} }
Image { Image {
@@ -82,15 +90,42 @@ Rectangle {
height: root.imageHeight * root.scale height: root.imageHeight * root.scale
sourceSize.width: width sourceSize.width: width
sourceSize.height: height sourceSize.height: height
}
layer.enabled: true Loader {
layer.effect: OpacityMask { id: blurLoader
maskSource: Rectangle { active: root.blur
width: image.width anchors.fill: image
height: image.height sourceComponent: GaussianBlur {
radius: root.radius source: image
radius: 35
samples: radius * 2 + 1
Rectangle {
anchors.fill: parent
color: ColorUtils.transparentize(Appearance.colors.colLayer0, 0.5)
Column {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
MaterialSymbol {
visible: width <= image.width
anchors.horizontalCenter: parent.horizontalCenter
text: "visibility_off"
font.pixelSize: 28
}
StyledText {
visible: width <= image.width
anchors.horizontalCenter: parent.horizontalCenter
text: root.blurText
color: Appearance.colors.colOnSurface
font.pixelSize: Appearance.font.pixelSize.smallie
}
}
} }
} }
} }
} }
@@ -24,7 +24,6 @@ RowLayout {
id: labelWidget id: labelWidget
Layout.fillWidth: true Layout.fillWidth: true
text: root.text text: root.text
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnSecondaryContainer color: Appearance.colors.colOnSecondaryContainer
} }
} }
@@ -7,8 +7,11 @@ import QtQuick.Controls
RippleButton { RippleButton {
id: root id: root
property string buttonIcon property string buttonIcon
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: contentItem.implicitHeight + 8 * 2 implicitHeight: contentItem.implicitHeight + 8 * 2
font.pixelSize: Appearance.font.pixelSize.small
onClicked: checked = !checked onClicked: checked = !checked
contentItem: RowLayout { contentItem: RowLayout {
@@ -21,7 +24,7 @@ RippleButton {
id: labelWidget id: labelWidget
Layout.fillWidth: true Layout.fillWidth: true
text: root.text text: root.text
font.pixelSize: Appearance.font.pixelSize.small font: root.font
color: Appearance.colors.colOnSecondaryContainer color: Appearance.colors.colOnSecondaryContainer
} }
StyledSwitch { StyledSwitch {
@@ -32,7 +32,7 @@ ColumnLayout {
StyledToolTip { StyledToolTip {
extraVisibleCondition: false extraVisibleCondition: false
alternativeVisibleCondition: infoMouseArea.containsMouse alternativeVisibleCondition: infoMouseArea.containsMouse
content: root.tooltip text: root.tooltip
} }
} }
} }
@@ -0,0 +1,50 @@
import QtQuick
import QtQuick.Shapes
import Quickshell
Item {
id: root
property int sides: 12
property int implicitSize: 100
property real amplitude: implicitSize / 50
property int renderPoints: 360
property color color: "#605790"
property alias strokeWidth: shapePath.strokeWidth
implicitWidth: implicitSize
implicitHeight: implicitSize
Shape {
id: shape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
ShapePath {
id: shapePath
strokeWidth: 0
fillColor: root.color
pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting
PathPolyline {
property var pointsList: {
var points = []
var cx = shape.width / 2 // center x
var cy = shape.height / 2 // center y
var steps = root.renderPoints
var radius = root.implicitSize / 2 - root.amplitude
for (var i = 0; i <= steps; i++) {
var angle = (i / steps) * 2 * Math.PI
var wave = Math.sin(angle * root.sides + Math.PI/2) * root.amplitude
var x = Math.cos(angle) * (radius + wave) + cx
var y = Math.sin(angle) * (radius + wave) + cy
points.push(Qt.point(x, y))
}
return points
}
path: pointsList
}
}
}
}
@@ -6,6 +6,7 @@ StyledText {
property real iconSize: Appearance?.font.pixelSize.small ?? 16 property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0 property real fill: 0
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
renderType: fill !== 0 ? Text.CurveRendering : Text.NativeRendering
font { font {
hintingPreference: Font.PreferFullHinting hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
@@ -215,7 +215,7 @@ MouseArea { // Notification group area
altAction: () => { root.toggleExpanded() } altAction: () => { root.toggleExpanded() }
StyledToolTip { StyledToolTip {
content: Translation.tr("Tip: right-clicking a group\nalso expands it") text: Translation.tr("Tip: right-clicking a group\nalso expands it")
} }
} }
} }
@@ -4,6 +4,7 @@ import qs.services
import qs.modules.common.functions import qs.modules.common.functions
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell import Quickshell
import Quickshell.Hyprland import Quickshell.Hyprland
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
@@ -15,6 +16,7 @@ Item { // Notification item area
property bool onlyNotification: false property bool onlyNotification: false
property real fontSize: Appearance.font.pixelSize.small property real fontSize: Appearance.font.pixelSize.small
property real padding: onlyNotification ? 0 : 8 property real padding: onlyNotification ? 0 : 8
property real summaryElideRatio: 0.85
property real dragConfirmThreshold: 70 // Drag further to discard notification property real dragConfirmThreshold: 70 // Drag further to discard notification
property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations
@@ -57,6 +59,12 @@ Item { // Notification item area
destroyAnimation.running = true; destroyAnimation.running = true;
} }
TextMetrics {
id: summaryTextMetrics
font.pixelSize: root.fontSize
text: root.notificationObject.summary || ""
}
SequentialAnimation { // Drag finish animation SequentialAnimation { // Drag finish animation
id: destroyAnimation id: destroyAnimation
running: false running: false
@@ -163,9 +171,9 @@ Item { // Notification item area
visible: !root.onlyNotification || !root.expanded visible: !root.onlyNotification || !root.expanded
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: summaryText.implicitHeight implicitHeight: summaryText.implicitHeight
// Layout.fillWidth: true
StyledText { StyledText {
id: summaryText id: summaryText
Layout.fillWidth: summaryTextMetrics.width >= summaryRow.width * root.summaryElideRatio
visible: !root.onlyNotification visible: !root.onlyNotification
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
color: Appearance.colors.colOnLayer3 color: Appearance.colors.colOnLayer3
@@ -209,7 +217,7 @@ Item { // Notification item area
textFormat: Text.RichText textFormat: Text.RichText
text: { text: {
return `<style>img{max-width:${300 /* binding to notificationBodyText.width would cause a binding loop */}px;}</style>` + return `<style>img{max-width:${300 /* binding to notificationBodyText.width would cause a binding loop */}px;}</style>` +
`${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}` `${processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\n/g, "<br/>")}`
} }
onLinkActivated: (link) => { onLinkActivated: (link) => {
@@ -220,91 +228,110 @@ Item { // Notification item area
PointingHandLinkHover {} PointingHandLinkHover {}
} }
StyledFlickable { // Notification actions Item {
id: actionsFlickable
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: actionRowLayout.implicitHeight implicitWidth: actionsFlickable.implicitWidth
contentWidth: actionRowLayout.implicitWidth implicitHeight: actionsFlickable.implicitHeight
clip: !onlyNotification
Behavior on opacity { layer.enabled: true
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) layer.effect: OpacityMask {
} maskSource: Rectangle {
Behavior on height { width: actionsFlickable.width
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) height: actionsFlickable.height
} radius: Appearance.rounding.small
Behavior on implicitHeight { }
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
RowLayout { ScrollEdgeFade {
id: actionRowLayout target: actionsFlickable
Layout.alignment: Qt.AlignBottom vertical: false
}
NotificationActionButton { StyledFlickable { // Notification actions
Layout.fillWidth: true id: actionsFlickable
buttonText: Translation.tr("Close") anchors.fill: parent
urgency: notificationObject.urgency implicitHeight: actionRowLayout.implicitHeight
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : contentWidth: actionRowLayout.implicitWidth
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: { Behavior on opacity {
root.destroyWithAnimation() animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
} }
Behavior on height {
contentItem: MaterialSymbol { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
iconSize: Appearance.font.pixelSize.large }
horizontalAlignment: Text.AlignHCenter Behavior on implicitHeight {
color: (notificationObject.urgency == NotificationUrgency.Critical) ? animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "close"
}
} }
Repeater { RowLayout {
id: actionRepeater id: actionRowLayout
model: notificationObject.actions Layout.alignment: Qt.AlignBottom
NotificationActionButton { NotificationActionButton {
Layout.fillWidth: true Layout.fillWidth: true
buttonText: modelData.text buttonText: Translation.tr("Close")
urgency: notificationObject.urgency urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: { onClicked: {
Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier); root.destroyWithAnimation()
} }
}
}
NotificationActionButton { contentItem: MaterialSymbol {
Layout.fillWidth: true iconSize: Appearance.font.pixelSize.large
urgency: notificationObject.urgency horizontalAlignment: Text.AlignHCenter
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : color: (notificationObject.urgency == NotificationUrgency.Critical) ?
(contentItem.implicitWidth + leftPadding + rightPadding) Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "close"
onClicked: {
Quickshell.clipboardText = notificationObject.body
copyIcon.text = "inventory"
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyIcon.text = "content_copy"
} }
} }
contentItem: MaterialSymbol { Repeater {
id: copyIcon id: actionRepeater
iconSize: Appearance.font.pixelSize.large model: notificationObject.actions
horizontalAlignment: Text.AlignHCenter NotificationActionButton {
color: (notificationObject.urgency == NotificationUrgency.Critical) ? Layout.fillWidth: true
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface buttonText: modelData.text
text: "content_copy" urgency: notificationObject.urgency
onClicked: {
Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier);
}
}
} }
NotificationActionButton {
Layout.fillWidth: true
urgency: notificationObject.urgency
implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) :
(contentItem.implicitWidth + leftPadding + rightPadding)
onClicked: {
Quickshell.clipboardText = notificationObject.body
copyIcon.text = "inventory"
copyIconTimer.restart()
}
Timer {
id: copyIconTimer
interval: 1500
repeat: false
onTriggered: {
copyIcon.text = "content_copy"
}
}
contentItem: MaterialSymbol {
id: copyIcon
iconSize: Appearance.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface
text: "content_copy"
}
}
} }
} }
} }
} }
@@ -0,0 +1,52 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
property string text: ""
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
property real horizontalPadding: 10
property real verticalPadding: 5
property var anchorEdges: Edges.Top
property var anchorGravity: anchorEdges
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
Loader {
id: tooltipLoader
anchors.fill: parent
active: internalVisibleCondition
sourceComponent: PopupWindow {
visible: true
anchor {
window: root.QsWindow.window
item: root.parent
edges: root.anchorEdges
gravity: root.anchorGravity
}
mask: Region {
item: null
}
color: "transparent"
implicitWidth: contentItem.implicitWidth + root.horizontalPadding * 2
implicitHeight: contentItem.implicitHeight + root.verticalPadding * 2
StyledToolTipContent {
id: contentItem
anchors.centerIn: parent
text: root.text
shown: false
Component.onCompleted: shown = true
horizontalPadding: root.horizontalPadding
verticalPadding: root.verticalPadding
}
}
}
}
@@ -63,7 +63,7 @@ Button {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (event) => { onPressed: (event) => {
if(event.button === Qt.RightButton) { if(event.button === Qt.RightButton) {
if (root.altAction) root.altAction(); if (root.altAction) root.altAction(event);
return; return;
} }
if(event.button === Qt.MiddleButton) { if(event.button === Qt.MiddleButton) {
@@ -0,0 +1,59 @@
import QtQuick
import qs.modules.common
import qs.modules.common.functions
Item {
id: root
z: 99
required property Item target
property real fadeSize: Appearance.m3colors.darkmode ? 40 : 20
property color color: ColorUtils.transparentize(Appearance.colors.colShadow, Appearance.m3colors.darkmode ? 0 : 0.7)
property bool vertical: true
anchors.fill: target
EndGradient {
anchors {
top: parent.top
left: parent.left
right: vertical ? parent.right : undefined
bottom: vertical ? undefined : parent.bottom
}
shown: !(root.vertical ? root.target.atYBeginning : root.target.atXBeginning)
}
EndGradient {
anchors {
bottom: parent.bottom
right: parent.right
left: vertical ? parent.left : undefined
top: vertical ? undefined : parent.top
}
shown: !(root.vertical ? root.target.atYEnd : root.target.atXEnd)
rotation: 180
}
component EndGradient: Rectangle {
required property bool shown
height: vertical ? root.fadeSize : parent.height
width: vertical ? parent.width : root.fadeSize
opacity: shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
gradient: Gradient {
orientation: root.vertical ? Gradient.Vertical : Gradient.Horizontal
GradientStop {
position: 0.0
color: root.color
}
GradientStop {
position: 1.0
color: ColorUtils.transparentize(root.color)
}
}
}
}
@@ -50,4 +50,5 @@ Flickable {
root.scrollTargetY = root.contentY; root.scrollTargetY = root.contentY;
} }
} }
} }
@@ -10,7 +10,8 @@ import Quickshell.Services.Pipewire
RadioButton { RadioButton {
id: root id: root
implicitHeight: contentItem.implicitHeight + 4 * 2 padding: 4
implicitHeight: contentItem.implicitHeight + padding * 2
property string description property string description
property color activeColor: Appearance?.colors.colPrimary ?? "#685496" property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F" property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? "#45464F"
@@ -5,7 +5,7 @@ import qs.modules.common
RectangularShadow { RectangularShadow {
required property var target required property var target
anchors.fill: target anchors.fill: target
radius: target.radius radius: 20
blur: 0.9 * Appearance.sizes.elevationMargin blur: 0.9 * Appearance.sizes.elevationMargin
offset: Qt.vector2d(0.0, 1.0) offset: Qt.vector2d(0.0, 1.0)
spread: 1 spread: 1
@@ -149,7 +149,7 @@ Slider {
StyledToolTip { StyledToolTip {
extraVisibleCondition: root.pressed extraVisibleCondition: root.pressed
content: root.tooltipContent text: root.tooltipContent
} }
} }
} }
@@ -6,55 +6,20 @@ import QtQuick.Layouts
ToolTip { ToolTip {
id: root id: root
property string content
property bool extraVisibleCondition: true property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false property bool alternativeVisibleCondition: false
property bool internalVisibleCondition: { readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
const ans = (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
return ans
}
verticalPadding: 5 verticalPadding: 5
horizontalPadding: 10 horizontalPadding: 10
opacity: internalVisibleCondition ? 1 : 0
visible: opacity > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
background: null background: null
visible: internalVisibleCondition
contentItem: Item { contentItem: StyledToolTipContent {
id: contentItemBackground id: contentItem
implicitWidth: tooltipTextObject.width + 2 * root.horizontalPadding text: root.text
implicitHeight: tooltipTextObject.height + 2 * root.verticalPadding shown: root.internalVisibleCondition
horizontalPadding: root.horizontalPadding
Rectangle { verticalPadding: root.verticalPadding
id: backgroundRectangle
anchors.bottom: contentItemBackground.bottom
anchors.horizontalCenter: contentItemBackground.horizontalCenter
color: Appearance?.colors.colTooltip ?? "#3C4043"
radius: Appearance?.rounding.verysmall ?? 7
width: internalVisibleCondition ? (tooltipTextObject.width + 2 * padding) : 0
height: internalVisibleCondition ? (tooltipTextObject.height + 2 * padding) : 0
clip: true
Behavior on width {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
StyledText {
id: tooltipTextObject
anchors.centerIn: parent
text: content
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
wrapMode: Text.Wrap
}
}
} }
} }
@@ -0,0 +1,52 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: root
required property string text
property bool shown: false
property real horizontalPadding: 10
property real verticalPadding: 5
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
property bool isVisible: backgroundRectangle.implicitHeight > 0
Rectangle {
id: backgroundRectangle
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
color: Appearance?.colors.colTooltip ?? "#3C4043"
radius: Appearance?.rounding.verysmall ?? 7
opacity: shown ? 1 : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
clip: true
Behavior on implicitWidth {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
StyledText {
id: tooltipTextObject
anchors.centerIn: parent
text: root.text
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
font.hintingPreference: Font.PreferNoHinting // Prevent shaky text
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
wrapMode: Text.Wrap
}
}
}
@@ -9,7 +9,7 @@ function findSuitableMaterialSymbol(summary = "") {
const keywordsToTypes = { const keywordsToTypes = {
'reboot': 'restart_alt', 'reboot': 'restart_alt',
'recording': 'screen_record', 'record': 'screen_record',
'battery': 'power', 'battery': 'power',
'power': 'power', 'power': 'power',
'screenshot': 'screenshot_monitor', 'screenshot': 'screenshot_monitor',
@@ -21,7 +21,7 @@ function findSuitableMaterialSymbol(summary = "") {
'update': 'update', 'update': 'update',
'ai response': 'neurology', 'ai response': 'neurology',
'control': 'settings', 'control': 'settings',
'upscale': 'compare', 'upsca': 'compare',
'install': 'deployed_code_update', 'install': 'deployed_code_update',
'startswith:file': 'folder_copy', // Declarative startsWith check 'startswith:file': 'folder_copy', // Declarative startsWith check
}; };
@@ -0,0 +1,57 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import qs.services
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
Loader {
id: crosshairLoader
active: GlobalStates.crosshairOpen
sourceComponent: PanelWindow {
id: crosshairWindow
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:crosshair"
WlrLayershell.layer: WlrLayer.Overlay
visible: true
color: "transparent"
mask: Region { // Crosshair should not block mouse input
item: null
}
implicitWidth: crosshairContent.implicitWidth
implicitHeight: crosshairContent.implicitHeight
CrosshairContent {
id: crosshairContent
anchors.centerIn: parent
}
}
}
IpcHandler {
target: "sidebarRight"
function toggle(): void {
GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen;
}
}
GlobalShortcut {
name: "crosshairToggle"
description: "Toggles crosshair on press"
onPressed: {
GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen;
}
}
}
@@ -0,0 +1,197 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
import qs.modules.common.functions
Item {
id: root
// Keys to props
// f, 0f, 1f, m are irrelevant as they're firing error stuff
// 0 is irrelevant because it's some profile stuff
property var propertyMap: ({
"c": "color",
"u": "colorCode",
"h": "outline",
"o": "outlineOpacity",
"t": "outlineThickness",
"d": "centerDot",
"a": "centerDotOpacity",
"z": "centerDotSize",
"0a": "innerLineOpacity",
"0l": "innerLineLength",
"0v": "innerLineVerticalLength",
"0g": "innerLineUnbindAxesLengths",
"0t": "innerLineThickness",
"0o": "innerLineOffset",
"1b": "outerLines",
"1a": "outerLineOpacity",
"1l": "outerLineLength",
"1v": "outerLineVerticalLength",
"1g": "outerLineUnbindAxesLengths",
"1t": "outerLineThickness",
"1o": "outerLineOffset",
})
property var colorMap: ({
0: "#FFFFFF",
1: "#00FF00",
2: "#7FFF00",
3: "#DFFF00",
4: "#FFFF00",
5: "#00FFFF",
6: "#FF00FF",
7: "#FF0000"
})
// Raw props
property int color: 0
property string colorCode: "#FFFFFF"
property bool outline: true
property real outlineOpacity: 0.5
property int outlineThickness: 1
property bool centerDot: false
property real centerDotOpacity: 1
property int centerDotSize: 2
property bool innerLines: true
property real innerLineOpacity: 0.8
property int innerLineLength: 6
property int innerLineVerticalLength: innerLineLength
property bool innerLineUnbindAxesLengths: false
property int innerLineThickness: 2
property int innerLineOffset: 3
property bool outerLines: true
property real outerLineOpacity: 0.35
property int outerLineLength: 2
property int outerLineVerticalLength: outerLineLength
property bool outerLineUnbindAxesLengths: false
property int outerLineThickness: 2
property int outerLineOffset: 10
property string defaultCode: "c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10"
function loadFromCode(code: string): void {
let args = code.split(";");
for (let i = 0; i < args.length; i+= 2) {
let key = args[i];
let value = args[i+1];
let targetKey = root.propertyMap[key];
let targetType = typeof root[targetKey];
if (targetKey === undefined) continue;
if (targetType === "number") {
value = parseFloat(value);
} else if (targetType === "boolean") {
value = (value === "1");
}
if (targetKey === "colorCode") {
value = "#" + value.slice(0, 6);
}
root[targetKey] = value;
}
if (!root.innerLineUnbindAxesLengths) {
root.innerLineVerticalLength = root.innerLineLength;
}
if (!root.outerLineUnbindAxesLengths) {
root.outerLineVerticalLength = root.outerLineLength;
}
}
// Update values from code
property var code: Config.options.crosshair.code
Component.onCompleted: reloadFromCode();
onCodeChanged: reloadFromCode();
function reloadFromCode() {
root.loadFromCode(root.defaultCode);
root.loadFromCode(root.code);
}
// Aggregated props
property color crosshairColor: {
if (colorMap[color] !== undefined) return root.colorMap[color];
if (color === 8) return colorCode;
return "#FFFFFF";
}
property int borderWidth: outline ? outlineThickness : 0
property color borderColor: ColorUtils.transparentize("black", 1 - root.outlineOpacity)
property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity)
property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity)
property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset
property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset
property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2
property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2
property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2
implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction
implicitHeight: implicitWidth
// width: implicitWidth
// height: implicitHeight
Rectangle {
id: centerDot
visible: root.centerDot
anchors.centerIn: parent
color: root.crosshairColor
opacity: root.centerDotOpacity
width: centerDotTotalSize
height: width
border.width: root.borderWidth
border.color: root.borderColor
}
Repeater {
id: innerLines
model: 4
Item {
id: innerHair
z: index % 2 // Vertical lines above horizontal lines
required property int index
property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0
property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength)
visible: root.innerLines && hairLength > 0
anchors.fill: parent
rotation: index * 90
Rectangle {
x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection
y: parent.height / 2 - height / 2
color: root.innerLineColor
width: innerHair.hairLength + root.borderWidth * 2
height: root.innerLineThickness + root.borderWidth * 2
border.width: root.borderWidth
border.color: root.borderColor
}
}
}
Repeater {
id: outerLines
model: 4
Item {
id: outerHair
z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines
required property int index
property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0
property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength)
visible: root.outerLines && hairLength > 0
anchors.fill: parent
rotation: index * 90
Rectangle {
x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection
y: parent.height / 2 - height / 2
color: root.outerLineColor
width: hairLength + root.borderWidth * 2
height: root.outerLineThickness + root.borderWidth * 2
border.width: root.borderWidth
border.color: root.borderColor
}
}
}
}
@@ -1,4 +1,5 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.services import qs.services
import qs.modules.common.functions import qs.modules.common.functions
@@ -46,9 +47,9 @@ Item { // Player instance
} }
} }
Timer { // Force update for prevision Timer { // Force update for revision
running: playerController.player?.playbackState == MprisPlaybackState.Playing running: playerController.player?.playbackState == MprisPlaybackState.Playing
interval: 1000 interval: Config.options.resources.updateInterval
repeat: true repeat: true
onTriggered: { onTriggered: {
playerController.player.positionChanged() playerController.player.positionChanged()
@@ -82,22 +83,8 @@ Item { // Player instance
rescaleSize: 1 // Rescale to 1x1 pixel for faster processing rescaleSize: 1 // Rescale to 1x1 pixel for faster processing
} }
property bool backgroundIsDark: artDominantColor.hslLightness < 0.5 property QtObject blendedColors: AdaptedMaterialScheme {
property QtObject blendedColors: QtObject { color: artDominantColor
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, (backgroundIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, artDominantColor), artDominantColor, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.15)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.5)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.5)
} }
StyledRectangularShadow { StyledRectangularShadow {
@@ -32,7 +32,7 @@ Item {
((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)
property real workspaceNumberMargin: 80 property real workspaceNumberMargin: 80
property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale property real workspaceNumberSize: 250 * monitor.scale
property int workspaceZ: 0 property int workspaceZ: 0
property int windowZ: 1 property int windowZ: 1
property int windowDraggingZ: 99999 property int windowDraggingZ: 99999
@@ -97,8 +97,11 @@ Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: workspaceValue text: workspaceValue
font.pixelSize: root.workspaceNumberSize * root.scale font {
font.weight: Font.DemiBold pixelSize: root.workspaceNumberSize * root.scale
weight: Font.DemiBold
family: Appearance.font.family.expressive
}
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
@@ -146,7 +149,7 @@ Item {
values: { values: {
// console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2)) // console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))
return ToplevelManager.toplevels.values.filter((toplevel) => { return ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel.address}` const address = `0x${toplevel.HyprlandToplevel?.address}`
var win = windowByAddress[address] var win = windowByAddress[address]
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
return inWorkspaceGroup; return inWorkspaceGroup;
@@ -235,7 +238,7 @@ Item {
StyledToolTip { StyledToolTip {
extraVisibleCondition: false extraVisibleCondition: false
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` text: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}`
} }
} }
} }
@@ -16,14 +16,16 @@ RippleButton {
property string query property string query
property bool entryShown: entry?.shown ?? true property bool entryShown: entry?.shown ?? true
property string itemType: entry?.type ?? Translation.tr("App") property string itemType: entry?.type ?? Translation.tr("App")
property string itemName: entry?.name property string itemName: entry?.name ?? ""
property string itemIcon: entry?.icon ?? "" property string itemIcon: entry?.icon ?? ""
property var itemExecute: entry?.execute property var itemExecute: entry?.execute
property string fontType: entry?.fontType ?? "main" property string fontType: entry?.fontType ?? "main"
property string itemClickActionName: entry?.clickActionName property string itemClickActionName: entry?.clickActionName ?? "Open"
property string bigText: entry?.bigText ?? "" property string bigText: entry?.bigText ?? ""
property string materialSymbol: entry?.materialSymbol ?? "" property string materialSymbol: entry?.materialSymbol ?? ""
property string cliphistRawString: entry?.cliphistRawString ?? "" property string cliphistRawString: entry?.cliphistRawString ?? ""
property bool blurImage: entry?.blurImage ?? false
property string blurImageText: entry?.blurImageText ?? "Image hidden"
visible: root.entryShown visible: root.entryShown
property int horizontalMargin: 10 property int horizontalMargin: 10
@@ -208,6 +210,8 @@ RippleButton {
entry: root.cliphistRawString entry: root.cliphistRawString
maxWidth: contentColumn.width maxWidth: contentColumn.width
maxHeight: 140 maxHeight: 140
blur: root.blurImage
blurText: root.blurImageText
} }
} }
} }
@@ -233,8 +237,8 @@ RippleButton {
delegate: RippleButton { delegate: RippleButton {
id: actionButton id: actionButton
required property var modelData required property var modelData
property string iconName: modelData.icon property string iconName: modelData.icon ?? ""
property string materialIconName: modelData.materialIcon property string materialIconName: modelData.materialIcon ?? ""
implicitHeight: 34 implicitHeight: 34
implicitWidth: 34 implicitWidth: 34
@@ -246,7 +250,7 @@ RippleButton {
anchors.centerIn: parent anchors.centerIn: parent
Loader { Loader {
anchors.centerIn: parent anchors.centerIn: parent
active: !(actionButton.iconName && actionButton.iconName !== "") || actionButton.materialIconName active: !(actionButton.iconName !== "") || actionButton.materialIconName
sourceComponent: MaterialSymbol { sourceComponent: MaterialSymbol {
text: actionButton.materialIconName || "video_settings" text: actionButton.materialIconName || "video_settings"
font.pixelSize: Appearance.font.pixelSize.hugeass font.pixelSize: Appearance.font.pixelSize.hugeass
@@ -255,7 +259,7 @@ RippleButton {
} }
Loader { Loader {
anchors.centerIn: parent anchors.centerIn: parent
active: !actionButton.materialIconName && actionButton.iconName && actionButton.iconName !== "" active: actionButton.materialIconName.length == 0 && actionButton.iconName && actionButton.iconName !== ""
sourceComponent: IconImage { sourceComponent: IconImage {
source: Quickshell.iconPath(actionButton.iconName) source: Quickshell.iconPath(actionButton.iconName)
implicitSize: 20 implicitSize: 20
@@ -266,7 +270,7 @@ RippleButton {
onClicked: modelData.execute() onClicked: modelData.execute()
StyledToolTip { StyledToolTip {
content: modelData.name text: modelData.name
} }
} }
} }
@@ -20,20 +20,10 @@ Item { // Wrapper
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
property string mathResult: "" property string mathResult: ""
property bool clipboardWorkSafetyActive: {
function disableExpandAnimation() { const enabled = Config.options.workSafety.enable.clipboard;
searchWidthBehavior.enabled = false; const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords))
} return enabled && sensitiveNetwork;
function cancelSearch() {
searchInput.selectAll();
root.searchingText = "";
searchWidthBehavior.enabled = true;
}
function setSearchingText(text) {
searchInput.text = text;
root.searchingText = text;
} }
property var searchActions: [ property var searchActions: [
@@ -97,6 +87,27 @@ Item { // Wrapper
appResults.currentIndex = 0; appResults.currentIndex = 0;
} }
function disableExpandAnimation() {
searchWidthBehavior.enabled = false;
}
function cancelSearch() {
searchInput.selectAll();
root.searchingText = "";
searchWidthBehavior.enabled = true;
}
function setSearchingText(text) {
searchInput.text = text;
root.searchingText = text;
}
function containsUnsafeLink(entry) {
if (entry == undefined) return false;
const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords;
return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords);
}
Timer { Timer {
id: nonAppResultsTimer id: nonAppResultsTimer
interval: Config.options.search.nonAppResultDelay interval: Config.options.search.nonAppResultDelay
@@ -311,7 +322,12 @@ Item { // Wrapper
if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) { if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) {
// Clipboard // Clipboard
const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length); const searchString = root.searchingText.slice(Config.options.search.prefix.clipboard.length);
return Cliphist.fuzzyQuery(searchString).map(entry => { return Cliphist.fuzzyQuery(searchString).map((entry, index, array) => {
const mightBlurImage = Cliphist.entryIsImage(entry) && root.clipboardWorkSafetyActive;
let shouldBlurImage = mightBlurImage;
if (mightBlurImage) {
shouldBlurImage = shouldBlurImage && (containsUnsafeLink(array[index - 1]) || containsUnsafeLink(array[index + 1]));
}
return { return {
cliphistRawString: entry, cliphistRawString: entry,
name: StringUtils.cleanCliphistEntry(entry), name: StringUtils.cleanCliphistEntry(entry),
@@ -335,7 +351,9 @@ Item { // Wrapper
Cliphist.deleteEntry(entry); Cliphist.deleteEntry(entry);
} }
} }
] ],
blurImage: shouldBlurImage,
blurImageText: Translation.tr("Work safety")
}; };
}).filter(Boolean); }).filter(Boolean);
} }
@@ -52,7 +52,7 @@ RippleButton {
} }
StyledToolTip { StyledToolTip {
content: buttonText text: buttonText
} }
} }
@@ -27,7 +27,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.enableQtApps = checked; Config.options.appearance.wallpaperTheming.enableQtApps = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Shell & utilities theming must also be enabled") text: Translation.tr("Shell & utilities theming must also be enabled")
} }
} }
ConfigSwitch { ConfigSwitch {
@@ -38,7 +38,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.enableTerminal = checked; Config.options.appearance.wallpaperTheming.enableTerminal = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Shell & utilities theming must also be enabled") text: Translation.tr("Shell & utilities theming must also be enabled")
} }
} }
ConfigRow { ConfigRow {
@@ -51,7 +51,7 @@ ContentPage {
Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode= checked; Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode= checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Ignored if terminal theming is not enabled") text: Translation.tr("Ignored if terminal theming is not enabled")
} }
} }
} }
@@ -21,7 +21,7 @@ ContentPage {
Config.options.audio.protection.enable = checked; Config.options.audio.protection.enable = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Prevents abrupt increments and restricts volume limit") text: Translation.tr("Prevents abrupt increments and restricts volume limit")
} }
} }
ConfigRow { ConfigRow {
@@ -85,7 +85,7 @@ ContentPage {
Config.options.battery.automaticSuspend = checked; Config.options.battery.automaticSuspend = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Automatically suspends the system when battery is low") text: Translation.tr("Automatically suspends the system when battery is low")
} }
} }
ConfigSpinBox { ConfigSpinBox {
@@ -14,9 +14,42 @@ ContentPage {
ConfigSwitch { ConfigSwitch {
text: Translation.tr("Show clock") text: Translation.tr("Show clock")
checked: Config.options.background.showClock checked: Config.options.background.clock.show
onCheckedChanged: { onCheckedChanged: {
Config.options.background.showClock = checked; Config.options.background.clock.show = checked;
}
}
ConfigSpinBox {
text: Translation.tr("Scale (%)")
value: Config.options.background.clock.scale * 100
from: 1
to: 200
stepSize: 2
onValueChanged: {
Config.options.background.clock.scale = value / 100;
}
}
ContentSubsection {
title: Translation.tr("Clock style")
ConfigSelectionArray {
currentValue: Config.options.background.clock.style
onSelected: newValue => {
Config.options.background.clock.style = newValue;
}
options: [
{
displayName: Translation.tr("Simple digital"),
icon: "timer_10",
value: "digital"
},
{
displayName: Translation.tr("Material cookie"),
icon: "cookie",
value: "cookie"
},
]
} }
} }
@@ -61,6 +94,37 @@ ContentPage {
} }
} }
ContentSection {
icon: "point_scan"
title: Translation.tr("Crosshair")
MaterialTextArea {
Layout.fillWidth: true
placeholderText: Translation.tr("Crosshair code (in Valorant's format)")
text: Config.options.crosshair.code
wrapMode: TextEdit.Wrap
onTextChanged: {
Config.options.crosshair.code = text;
}
}
RowLayout {
Item { Layout.fillWidth: true }
RippleButtonWithIcon {
id: editorButton
buttonRadius: Appearance.rounding.full
materialIcon: "open_in_new"
mainText: Translation.tr("Open editor")
onClicked: {
Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`);
}
StyledToolTip {
text: "www.vcrdb.net"
}
}
}
}
ContentSection { ContentSection {
icon: "call_to_action" icon: "call_to_action"
title: Translation.tr("Dock") title: Translation.tr("Dock")
@@ -99,6 +163,68 @@ ContentPage {
} }
} }
ContentSection {
icon: "lock"
title: Translation.tr("Lock screen")
ContentSubsection {
title: Translation.tr("Blurred style")
ConfigSwitch {
text: Translation.tr('Enable blur')
checked: Config.options.lock.blur.enable
onCheckedChanged: {
Config.options.lock.blur.enable = checked;
}
}
ConfigSpinBox {
text: Translation.tr("Blur: Extra zoom (%)")
value: Config.options.lock.blur.extraZoom * 100
from: 1
to: 150
stepSize: 2
onValueChanged: {
Config.options.lock.blur.extraZoom = value / 100;
}
}
ConfigSwitch {
text: Translation.tr('Center clock')
checked: Config.options.lock.centerClock
onCheckedChanged: {
Config.options.lock.centerClock = checked;
}
}
ConfigSwitch {
text: Translation.tr('Show "Locked" text')
checked: Config.options.lock.showLockedText
onCheckedChanged: {
Config.options.lock.showLockedText = checked;
}
}
}
}
ContentSection {
icon: "notifications"
title: Translation.tr("Notifications")
ConfigSpinBox {
text: Translation.tr("Timeout duration (if not defined by notification) (ms)")
value: Config.options.notifications.timeout
from: 1000
to: 60000
stepSize: 1000
onValueChanged: {
Config.options.notifications.timeout = value;
}
}
}
ContentSection { ContentSection {
icon: "side_navigation" icon: "side_navigation"
title: Translation.tr("Sidebars") title: Translation.tr("Sidebars")
@@ -110,7 +236,7 @@ ContentPage {
Config.options.sidebar.keepRightSidebarLoaded = checked; Config.options.sidebar.keepRightSidebarLoaded = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help") text: Translation.tr("When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a custom kernel like linux-cachyos might help")
} }
} }
@@ -134,7 +260,7 @@ ContentPage {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("When this is off you'll have to click") text: Translation.tr("When this is off you'll have to click")
} }
} }
} }
@@ -148,7 +274,7 @@ ContentPage {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Place the corners to trigger at the bottom") text: Translation.tr("Place the corners to trigger at the bottom")
} }
} }
ConfigSwitch { ConfigSwitch {
@@ -159,7 +285,7 @@ ContentPage {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Brightness and volume") text: Translation.tr("Brightness and volume")
} }
} }
} }
@@ -268,7 +394,7 @@ ContentPage {
Config.options.screenshotTool.showContentRegions = checked; Config.options.screenshotTool.showContentRegions = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.") text: Translation.tr("Such regions could be images or parts of the screen that have some containment.\nMight not always be accurate.\nThis is done with an image processing algorithm run locally and no AI is used.")
} }
} }
} }
@@ -76,6 +76,7 @@ ContentPage {
sourceSize.height: parent.implicitHeight sourceSize.height: parent.implicitHeight
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
source: Config.options.background.wallpaperPath source: Config.options.background.wallpaperPath
cache: false
layer.enabled: true layer.enabled: true
layer.effect: OpacityMask { layer.effect: OpacityMask {
maskSource: Rectangle { maskSource: Rectangle {
@@ -93,20 +94,20 @@ ContentPage {
visible: Config.options.policies.weeb === 1 visible: Config.options.policies.weeb === 1
Layout.fillWidth: true Layout.fillWidth: true
buttonRadius: Appearance.rounding.small buttonRadius: Appearance.rounding.small
materialIcon: "wallpaper" materialIcon: "ifl"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan") mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: { onClicked: {
konachanWallProc.running = true; konachanWallProc.running = true;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers") text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
} }
} }
RippleButtonWithIcon { RippleButtonWithIcon {
Layout.fillWidth: true Layout.fillWidth: true
materialIcon: "wallpaper" materialIcon: "wallpaper"
StyledToolTip { StyledToolTip {
content: Translation.tr("Pick wallpaper image on your system") text: Translation.tr("Pick wallpaper image on your system")
} }
onClicked: { onClicked: {
Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`); Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`);
@@ -161,7 +162,7 @@ ContentPage {
Config.options.appearance.transparency.enable = checked; Config.options.appearance.transparency.enable = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Might look ass. Unsupported.") text: Translation.tr("Might look ass. Unsupported.")
} }
} }
} }
@@ -69,7 +69,7 @@ ContentPage {
Config.options.search.sloppy = checked; Config.options.search.sloppy = checked;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)") text: Translation.tr("Could be better if you make a ton of typos,\nbut results can be weird and might not work with acronyms\n(e.g. \"GIMP\" might not give you the paint program)")
} }
} }
@@ -209,6 +209,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
else { else {
Ai.sendUserMessage(inputText); Ai.sendUserMessage(inputText);
} }
// Always scroll to bottom when user sends a message
messageListView.positionViewAtEnd()
} }
Process { Process {
@@ -257,7 +260,7 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
} }
StyledToolTip { StyledToolTip {
content: statusItem.description text: statusItem.description
extraVisibleCondition: false extraVisibleCondition: false
alternativeVisibleCondition: statusItem.containsMouse alternativeVisibleCondition: statusItem.containsMouse
} }
@@ -305,6 +308,20 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
Item { // Messages Item { // Messages
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
ScrollEdgeFade {
target: messageListView
vertical: true
}
StyledListView { // Message list StyledListView { // Message list
id: messageListView id: messageListView
anchors.fill: parent anchors.fill: parent
@@ -315,15 +332,14 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4
property int lastResponseLength: 0 property int lastResponseLength: 0
property bool shouldAutoScroll: true
clip: true onContentYChanged: shouldAutoScroll = atYEnd
layer.enabled: true onContentHeightChanged: {
layer.effect: OpacityMask { if (shouldAutoScroll) positionViewAtEnd();
maskSource: Rectangle { }
width: swipeView.width onCountChanged: { // Auto-scroll when new messages are added
height: swipeView.height if (shouldAutoScroll) positionViewAtEnd();
radius: Appearance.rounding.small
}
} }
add: null // Prevent function calls from being janky add: null // Prevent function calls from being janky
@@ -141,6 +141,21 @@ Item {
Item { Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
ScrollEdgeFade {
target: booruResponseListView
vertical: true
}
StyledListView { // Booru responses StyledListView { // Booru responses
id: booruResponseListView id: booruResponseListView
anchors.fill: parent anchors.fill: parent
@@ -151,16 +166,6 @@ Item {
property int lastResponseLength: 0 property int lastResponseLength: 0
clip: true
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: swipeView.width
height: swipeView.height
radius: Appearance.rounding.small
}
}
model: ScriptModel { model: ScriptModel {
values: { values: {
if(root.responses.length > booruResponseListView.lastResponseLength) { if(root.responses.length > booruResponseListView.lastResponseLength) {
@@ -41,7 +41,7 @@ Item { // Model indicator
id: toolTip id: toolTip
extraVisibleCondition: false extraVisibleCondition: false
alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered
content: root.tooltipText text: root.tooltipText
} }
} }
} }
@@ -101,7 +101,7 @@ Item {
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
Flickable { StyledFlickable {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
contentHeight: contentColumn.implicitHeight contentHeight: contentColumn.implicitHeight
@@ -164,7 +164,7 @@ Rectangle {
text: "visibility_off" text: "visibility_off"
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Not visible to model") text: Translation.tr("Not visible to model")
} }
} }
@@ -191,7 +191,7 @@ Rectangle {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Copy") text: Translation.tr("Copy")
} }
} }
AiMessageControlButton { AiMessageControlButton {
@@ -206,7 +206,7 @@ Rectangle {
} }
} }
StyledToolTip { StyledToolTip {
content: root.editing ? Translation.tr("Save") : Translation.tr("Edit") text: root.editing ? Translation.tr("Save") : Translation.tr("Edit")
} }
} }
AiMessageControlButton { AiMessageControlButton {
@@ -217,7 +217,7 @@ Rectangle {
root.renderMarkdown = !root.renderMarkdown root.renderMarkdown = !root.renderMarkdown
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("View Markdown source") text: Translation.tr("View Markdown source")
} }
} }
AiMessageControlButton { AiMessageControlButton {
@@ -227,7 +227,7 @@ Rectangle {
Ai.removeMessage(root.messageIndex) Ai.removeMessage(root.messageIndex)
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Delete") text: Translation.tr("Delete")
} }
} }
} }
@@ -84,7 +84,7 @@ ColumnLayout {
} }
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Copy code") text: Translation.tr("Copy code")
} }
} }
AiMessageControlButton { AiMessageControlButton {
@@ -114,7 +114,7 @@ ColumnLayout {
} }
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Save to Downloads") text: Translation.tr("Save to Downloads")
} }
} }
} }
@@ -41,7 +41,7 @@ Button {
} }
StyledToolTip { StyledToolTip {
content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}` text: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}`
} }
padding: 0 padding: 0
@@ -105,7 +105,6 @@ Rectangle {
return true return true
} }
implicitHeight: tagRowLayout.implicitHeight implicitHeight: tagRowLayout.implicitHeight
// height: tagRowLayout.implicitHeight
contentWidth: tagRowLayout.implicitWidth contentWidth: tagRowLayout.implicitWidth
clip: true clip: true
@@ -118,9 +117,6 @@ Rectangle {
} }
} }
Behavior on height {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on implicitHeight { Behavior on implicitHeight {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
} }
@@ -1,12 +1,7 @@
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
@@ -88,7 +88,7 @@ Item {
Quickshell.reload(true); Quickshell.reload(true);
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Reload Hyprland & Quickshell") text: Translation.tr("Reload Hyprland & Quickshell")
} }
} }
QuickToggleButton { QuickToggleButton {
@@ -99,7 +99,7 @@ Item {
Quickshell.execDetached(["qs", "-p", root.settingsQmlPath]); Quickshell.execDetached(["qs", "-p", root.settingsQmlPath]);
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Settings") text: Translation.tr("Settings")
} }
} }
QuickToggleButton { QuickToggleButton {
@@ -109,7 +109,7 @@ Item {
GlobalStates.sessionOpen = true; GlobalStates.sessionOpen = true;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Session") text: Translation.tr("Session")
} }
} }
} }
@@ -30,7 +30,7 @@ RippleButton {
} }
StyledToolTip { StyledToolTip {
content: tooltipText text: tooltipText
extraVisibleCondition: tooltipText.length > 0 extraVisibleCondition: tooltipText.length > 0
} }
} }
@@ -21,7 +21,7 @@ QuickToggleButton {
GlobalStates.sidebarRightOpen = false GlobalStates.sidebarRightOpen = false
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("%1 | Right-click to configure").arg( text: Translation.tr("%1 | Right-click to configure").arg(
(BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth")) (BluetoothStatus.firstActiveDevice?.name ?? Translation.tr("Bluetooth"))
+ (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "") + (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : "")
) )
@@ -87,6 +87,6 @@ QuickToggleButton {
} }
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Cloudflare WARP (1.1.1.1)") text: Translation.tr("Cloudflare WARP (1.1.1.1)")
} }
} }
@@ -21,11 +21,11 @@ QuickToggleButton {
} }
altAction: () => { altAction: () => {
Quickshell.execDetached(["easyeffects"]) Quickshell.execDetached(["bash", "-c", "flatpak run com.github.wwmm.easyeffects || easyeffects"])
GlobalStates.sidebarRightOpen = false GlobalStates.sidebarRightOpen = false
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("EasyEffects | Right-click to configure") text: Translation.tr("EasyEffects | Right-click to configure")
} }
} }
@@ -26,6 +26,6 @@ QuickToggleButton {
} }
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Game mode") text: Translation.tr("Game mode")
} }
} }
@@ -10,7 +10,7 @@ QuickToggleButton {
Idle.toggleInhibit() Idle.toggleInhibit()
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Keep system awake") text: Translation.tr("Keep system awake")
} }
} }
@@ -18,6 +18,6 @@ QuickToggleButton {
GlobalStates.sidebarRightOpen = false GlobalStates.sidebarRightOpen = false
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("%1 | Right-click to configure").arg(Network.networkName) text: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
} }
} }
@@ -23,6 +23,6 @@ QuickToggleButton {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Night Light | Right-click to toggle Auto mode") text: Translation.tr("Night Light | Right-click to toggle Auto mode")
} }
} }
@@ -26,7 +26,7 @@ RippleButton {
} }
StyledToolTip { StyledToolTip {
content: tooltipText text: tooltipText
extraVisibleCondition: tooltipText.length > 0 extraVisibleCondition: tooltipText.length > 0
} }
} }
@@ -82,8 +82,8 @@ Scope {
id: hoverMaskRegion id: hoverMaskRegion
anchors { anchors {
fill: barContent fill: barContent
leftMargin: -1 leftMargin: -Config.options.bar.autoHide.hoverRegionWidth
rightMargin: -1 rightMargin: -Config.options.bar.autoHide.hoverRegionWidth
} }
} }
@@ -269,16 +269,10 @@ Item { // Bar content region
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
} }
Loader { Bar.HyprlandXkbIndicator {
active: HyprlandXkb.layoutCodes.length > 1 vertical: true
visible: active Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: indicatorsColumnLayout.realSpacing Layout.bottomMargin: indicatorsColumnLayout.realSpacing
sourceComponent: StyledText {
text: HyprlandXkb.currentLayoutCode
font.pixelSize: Appearance.font.pixelSize.small
color: rightSidebarButton.colText
animateChange: true
}
} }
MaterialSymbol { MaterialSymbol {
Layout.bottomMargin: indicatorsColumnLayout.realSpacing Layout.bottomMargin: indicatorsColumnLayout.realSpacing
@@ -22,7 +22,7 @@ MouseArea {
Timer { Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000 interval: Config.options.resources.updateInterval
repeat: true repeat: true
onTriggered: activePlayer.positionChanged() onTriggered: activePlayer.positionChanged()
} }
@@ -71,11 +71,25 @@ Scope {
function toggle(): void { function toggle(): void {
root.toggleWallpaperSelector(); root.toggleWallpaperSelector();
} }
function random(): void {
Wallpapers.randomFromCurrentFolder();
}
} }
GlobalShortcut { GlobalShortcut {
name: "wallpaperSelectorToggle" name: "wallpaperSelectorToggle"
description: "Toggle wallpaper selector" description: "Toggle wallpaper selector"
onPressed: root.toggleWallpaperSelector(); onPressed: {
root.toggleWallpaperSelector();
}
}
GlobalShortcut {
name: "wallpaperSelectorRandom"
description: "Select random wallpaper in current folder"
onPressed: {
Wallpapers.randomFromCurrentFolder();
}
} }
} }
@@ -40,6 +40,13 @@ MouseArea {
} }
} }
function selectWallpaperPath(filePath) {
if (filePath && filePath.length > 0) {
Wallpapers.select(filePath, root.useDarkMode);
filterField.text = "";
}
}
acceptedButtons: Qt.BackButton | Qt.ForwardButton acceptedButtons: Qt.BackButton | Qt.ForwardButton
onPressed: event => { onPressed: event => {
if (event.button === Qt.BackButton) { if (event.button === Qt.BackButton) {
@@ -164,7 +171,7 @@ MouseArea {
{ icon: "movie", name: "Videos", path: Directories.videos }, { icon: "movie", name: "Videos", path: Directories.videos },
{ icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" }, { icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" },
{ icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` }, { icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` },
{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` }, ...(Config.options.policies.weeb === 1 ? [{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` }] : []),
] ]
delegate: RippleButton { delegate: RippleButton {
id: quickDirButton id: quickDirButton
@@ -185,6 +192,7 @@ MouseArea {
color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1 color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
text: quickDirButton.modelData.icon text: quickDirButton.modelData.icon
fill: quickDirButton.toggled ? 1 : 0
} }
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
@@ -267,8 +275,7 @@ MouseArea {
function activateCurrent() { function activateCurrent() {
const filePath = grid.model.get(currentIndex, "filePath") const filePath = grid.model.get(currentIndex, "filePath")
Wallpapers.select(filePath, root.useDarkMode); root.selectWallpaperPath(filePath);
filterField.text = "";
} }
model: Wallpapers.folderModel model: Wallpapers.folderModel
@@ -287,8 +294,7 @@ MouseArea {
} }
onActivated: { onActivated: {
Wallpapers.select(fileModelData.filePath, root.useDarkMode); root.selectWallpaperPath(fileModelData.filePath);
filterField.text = "";
} }
} }
@@ -322,11 +328,29 @@ MouseArea {
Config.options.wallpaperSelector.useSystemFileDialog = true Config.options.wallpaperSelector.useSystemFileDialog = true
} }
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "open_in_new" text: "open_in_new"
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior") text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior")
}
}
ToolbarButton {
implicitWidth: height
onClicked: {
Wallpapers.randomFromCurrentFolder();
}
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "ifl"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
text: Translation.tr("Pick random from this folder")
} }
} }
@@ -334,11 +358,13 @@ MouseArea {
implicitWidth: height implicitWidth: height
onClicked: root.useDarkMode = !root.useDarkMode onClicked: root.useDarkMode = !root.useDarkMode
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: root.useDarkMode ? "dark_mode" : "light_mode" text: root.useDarkMode ? "dark_mode" : "light_mode"
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Click to toggle light/dark mode (applied when wallpaper is chosen)") text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)")
} }
} }
@@ -378,11 +404,18 @@ MouseArea {
} }
ToolbarButton { ToolbarButton {
implicitWidth: height
onClicked: { onClicked: {
GlobalStates.wallpaperSelectorOpen = false; GlobalStates.wallpaperSelectorOpen = false;
} }
contentItem: StyledText { contentItem: MaterialSymbol {
text: "Cancel" anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
text: "cancel_presentation"
iconSize: Appearance.font.pixelSize.larger
}
StyledToolTip {
text: Translation.tr("Cancel wallpaper selection")
} }
} }
} }
+17 -6
View File
@@ -103,16 +103,27 @@ Singleton {
} }
} }
function setBrightness(value: real): void { // We need a delay for DDC monitors because they can be quite slow and might act weird with rapid changes
value = Math.max(0.01, Math.min(1, value)); property var setTimer: Timer {
const rounded = Math.round(value * monitor.rawMaxBrightness); id: setTimer
if (Math.round(brightness * monitor.rawMaxBrightness) === rounded) interval: monitor.isDdc ? 300 : 0
return; onTriggered: {
brightness = value; syncBrightness();
}
}
function syncBrightness() {
const rounded = Math.round(monitor.brightness * monitor.rawMaxBrightness);
setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "s", rounded, "--quiet"]; setProc.command = isDdc ? ["ddcutil", "-b", busNum, "setvcp", "10", rounded] : ["brightnessctl", "s", rounded, "--quiet"];
setProc.startDetached(); setProc.startDetached();
} }
function setBrightness(value: real): void {
value = Math.max(0.01, Math.min(1, value));
monitor.brightness = value;
setTimer.restart();
}
Component.onCompleted: { Component.onCompleted: {
initialize(); initialize();
} }
@@ -25,12 +25,12 @@ Singleton {
function disable() { function disable() {
root.active = false root.active = false
Quickshell.execDetached(["pkill", "easyeffects"]) Quickshell.execDetached(["bash", "-c", "pkill easyeffects || flatpak pkill com.github.wwmm.easyeffects"])
} }
function enable() { function enable() {
root.active = true root.active = true
Quickshell.execDetached(["easyeffects", "--gapplication-service"]) Quickshell.execDetached(["bash", "-c", "easyeffects --gapplication-service || flatpak run com.github.wwmm.easyeffects --gapplication-service"])
} }
function toggle() { function toggle() {
@@ -44,7 +44,7 @@ Singleton {
Process { Process {
id: fetchAvailabilityProc id: fetchAvailabilityProc
running: true running: true
command: ["bash", "-c", "command -v easyeffects"] command: ["bash", "-c", "command -v easyeffects || flatpak info com.github.wwmm.easyeffects > /dev/null 2>&1"]
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
root.available = exitCode === 0 root.available = exitCode === 0
} }
@@ -53,7 +53,7 @@ Singleton {
Process { Process {
id: fetchActiveStateProc id: fetchActiveStateProc
running: true running: true
command: ["pidof", "easyeffects"] command: ["bash", "-c", "pidof easyeffects || flatpak ps | grep com.github.wwmm.easyeffects > /dev/null 2>&1"]
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
root.active = exitCode === 0 root.active = exitCode === 0
} }
+16 -5
View File
@@ -46,13 +46,24 @@ Singleton {
if (!line.trim() || line.trim().startsWith('!')) if (!line.trim() || line.trim().startsWith('!'))
return false; return false;
// Match: key + whitespace + description // Match layout: (whitespace + ) key + whitespace + description
const match = line.match(/^\s*(\S+)\s+(.+)$/); const matchLayout = line.match(/^\s*(\S+)\s+(.+)$/);
if (match && match[2] === targetDescription) { if (matchLayout && matchLayout[2] === targetDescription) {
root.cachedLayoutCodes[match[2]] = match[1]; root.cachedLayoutCodes[matchLayout[2]] = matchLayout[1];
root.currentLayoutCode = match[1]; root.currentLayoutCode = matchLayout[1];
return true; return true;
} }
// Match variant: (whitespace + ) variant + whitespace + key + whitespace + description
const matchVariant = line.match(/^\s*(\S+)\s+(\S+)\s+(.+)$/);
if (matchVariant && matchVariant[3] === targetDescription) {
const complexLayout = matchVariant[2] + matchVariant[1];
root.cachedLayoutCodes[matchVariant[3]] = complexLayout;
root.currentLayoutCode = complexLayout;
return true;
}
return false;
}); });
// console.log("[HyprlandXkb] Found line:", foundLine); // console.log("[HyprlandXkb] Found line:", foundLine);
// console.log("[HyprlandXkb] Layout:", root.currentLayoutName, "| Code:", root.currentLayoutCode); // console.log("[HyprlandXkb] Layout:", root.currentLayoutName, "| Code:", root.currentLayoutCode);
@@ -60,7 +60,7 @@ Singleton {
component NotifTimer: Timer { component NotifTimer: Timer {
required property int notificationId required property int notificationId
interval: 5000 interval: 7000
running: true running: true
onTriggered: () => { onTriggered: () => {
root.timeoutNotification(notificationId); root.timeoutNotification(notificationId);
@@ -168,7 +168,7 @@ Singleton {
if (notification.expireTimeout != 0) { if (notification.expireTimeout != 0) {
newNotifObject.timer = notifTimerComponent.createObject(root, { newNotifObject.timer = notifTimerComponent.createObject(root, {
"notificationId": newNotifObject.notificationId, "notificationId": newNotifObject.notificationId,
"interval": notification.expireTimeout < 0 ? 5000 : notification.expireTimeout, "interval": notification.expireTimeout < 0 ? (Config?.options.notifications.timeout ?? 7000) : notification.expireTimeout,
}); });
} }
} }
+13 -2
View File
@@ -16,7 +16,7 @@ Singleton {
id: root id: root
property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen.py` property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen.py`
property string generateThumbnailsMagicScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh` property string generateThumbnailsMagickScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh`
property alias directory: folderModel.folder property alias directory: folderModel.folder
readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString()) readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString())
property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`) property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`)
@@ -33,6 +33,8 @@ Singleton {
signal thumbnailGenerated(directory: string) signal thumbnailGenerated(directory: string)
signal thumbnailGeneratedFile(filePath: string) signal thumbnailGeneratedFile(filePath: string)
function load () {} // For forcing initialization
// Executions // Executions
Process { Process {
id: applyProc id: applyProc
@@ -77,6 +79,14 @@ Singleton {
selectProc.select(filePath, darkMode); selectProc.select(filePath, darkMode);
} }
function randomFromCurrentFolder(darkMode = Appearance.m3colors.darkmode) {
if (folderModel.count === 0) return;
const randomIndex = Math.floor(Math.random() * folderModel.count);
const filePath = folderModel.get(randomIndex, "filePath");
print("Randomly selected wallpaper:", filePath);
root.select(filePath, darkMode);
}
Process { Process {
id: validateDirProc id: validateDirProc
property string nicePath: "" property string nicePath: ""
@@ -136,12 +146,13 @@ Singleton {
// Thumbnail generation // Thumbnail generation
function generateThumbnail(size: string) { function generateThumbnail(size: string) {
// console.log("[Wallpapers] Updating thumbnails")
if (!["normal", "large", "x-large", "xx-large"].includes(size)) throw new Error("Invalid thumbnail size"); if (!["normal", "large", "x-large", "xx-large"].includes(size)) throw new Error("Invalid thumbnail size");
thumbgenProc.directory = root.directory thumbgenProc.directory = root.directory
thumbgenProc.running = false thumbgenProc.running = false
thumbgenProc.command = [ thumbgenProc.command = [
"bash", "-c", "bash", "-c",
`${thumbgenScriptPath} --size ${size} --machine_progress -d ${root.directory} || ${generateThumbnailsMagicScriptPath} --size ${size} -d ${root.directory}`, `${thumbgenScriptPath} --size ${size} --machine_progress -d ${FileUtils.trimFileProtocol(root.directory)} || ${generateThumbnailsMagickScriptPath} --size ${size} -d ${root.directory}`,
] ]
root.thumbnailGenerationProgress = 0 root.thumbnailGenerationProgress = 0
thumbgenProc.running = true thumbgenProc.running = true
+3 -3
View File
@@ -71,8 +71,8 @@ ApplicationWindow {
MaterialThemeLoader.reapplyTheme() MaterialThemeLoader.reapplyTheme()
} }
minimumWidth: 600 minimumWidth: 750
minimumHeight: 400 minimumHeight: 500
width: 1100 width: 1100
height: 750 height: 750
color: Appearance.m3colors.m3background color: Appearance.m3colors.m3background
@@ -177,7 +177,7 @@ ApplicationWindow {
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json") text: Translation.tr("Open the shell config file.\nIf the button doesn't work or doesn't open in your favorite editor,\nyou can manually open ~/.config/illogical-impulse/config.json")
} }
} }
+4
View File
@@ -11,6 +11,7 @@ import "./modules/common/"
import "./modules/background/" import "./modules/background/"
import "./modules/bar/" import "./modules/bar/"
import "./modules/cheatsheet/" import "./modules/cheatsheet/"
import "./modules/crosshair/"
import "./modules/dock/" import "./modules/dock/"
import "./modules/lock/" import "./modules/lock/"
import "./modules/mediaControls/" import "./modules/mediaControls/"
@@ -36,6 +37,7 @@ ShellRoot {
property bool enableBar: true property bool enableBar: true
property bool enableBackground: true property bool enableBackground: true
property bool enableCheatsheet: true property bool enableCheatsheet: true
property bool enableCrosshair: true
property bool enableDock: true property bool enableDock: true
property bool enableLock: true property bool enableLock: true
property bool enableMediaControls: true property bool enableMediaControls: true
@@ -59,11 +61,13 @@ ShellRoot {
FirstRunExperience.load() FirstRunExperience.load()
ConflictKiller.load() ConflictKiller.load()
Cliphist.refresh() Cliphist.refresh()
Wallpapers.load()
} }
LazyLoader { active: enableBar && Config.ready && !Config.options.bar.vertical; component: Bar {} } LazyLoader { active: enableBar && Config.ready && !Config.options.bar.vertical; component: Bar {} }
LazyLoader { active: enableBackground; component: Background {} } LazyLoader { active: enableBackground; component: Background {} }
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
LazyLoader { active: enableCrosshair; component: Crosshair {} }
LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} } LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} }
LazyLoader { active: enableLock; component: Lock {} } LazyLoader { active: enableLock; component: Lock {} }
LazyLoader { active: enableMediaControls; component: MediaControls {} } LazyLoader { active: enableMediaControls; component: MediaControls {} }
+7 -3
View File
@@ -109,6 +109,10 @@ ApplicationWindow {
text: "close" text: "close"
iconSize: 20 iconSize: 20
} }
StyledToolTip {
text: Translation.tr("Tip: Close a window with Super+Q")
}
} }
} }
} }
@@ -239,20 +243,20 @@ ApplicationWindow {
visible: Config.options.policies.weeb === 1 visible: Config.options.policies.weeb === 1
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
buttonRadius: Appearance.rounding.small buttonRadius: Appearance.rounding.small
materialIcon: "wallpaper" materialIcon: "ifl"
mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan") mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan")
onClicked: { onClicked: {
console.log(konachanWallProc.command.join(" ")); console.log(konachanWallProc.command.join(" "));
konachanWallProc.running = true; konachanWallProc.running = true;
} }
StyledToolTip { StyledToolTip {
content: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers") text: Translation.tr("Random SFW Anime wallpaper from Konachan\nImage is saved to ~/Pictures/Wallpapers")
} }
} }
RippleButtonWithIcon { RippleButtonWithIcon {
materialIcon: "wallpaper" materialIcon: "wallpaper"
StyledToolTip { StyledToolTip {
content: Translation.tr("Pick wallpaper image on your system") text: Translation.tr("Pick wallpaper image on your system")
} }
onClicked: { onClicked: {
Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]); Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]);
+1 -1
View File
@@ -23,7 +23,7 @@
;; Define function to read SCSS variables ;; Define function to read SCSS variables
(defun material-get-color-from-scss (var-name) (defun material-get-color-from-scss (var-name)
"Extract color value for VAR-NAME from material_colors.scss file." "Extract color value for VAR-NAME from material_colors.scss file."
(let* ((scss-file (expand-file-name "~/.cache/ags/user/generated/material_colors.scss")) (let* ((scss-file (expand-file-name "~/.local/state/quickshell/user/generated/material_colors.scss"))
(scss-content (with-temp-buffer (scss-content (with-temp-buffer
(insert-file-contents scss-file) (insert-file-contents scss-file)
(buffer-string))) (buffer-string)))
+64 -59
View File
@@ -47,8 +47,8 @@
| [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (manages and renders windows) | | [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (manages and renders windows) |
| [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, used for the status bar, sidebars, etc. | | [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, used for the status bar, sidebars, etc. |
- THERE IS NO WAYBAR
- For the full list of dependencies, see the [arch-packages folder](https://github.com/end-4/dots-hyprland/tree/main/arch-packages) - For the full list of dependencies, see the [arch-packages folder](https://github.com/end-4/dots-hyprland/tree/main/arch-packages)
- THERE IS NO WAYBAR STOP FUCKING CALLING EVERY BAR WAYBAR
</details> </details>
<details> <details>
@@ -62,20 +62,14 @@
<h3></h3> <h3></h3>
</div> </div>
<table style="border-collapse: collapse;"> <div align="center">
<tr> <img src=".github/assets/illogical-impulse.svg" alt="illogical-impulse logo" style="float:left; width:400;">
<td width="25%"> </div>
<img src=".github/assets/illogical-impulse.svg" alt="illogical-impulse logo" style="float:left; width:100%;">
</td>
<td width="75%">
<i>latest and only style that I actively use. Other past styles are still there for your viewing pleasure and not actual use, but code is still available, see below.</i>
</td>
</tr>
</table>
### illogical-impulse<sup>Quickshell</sup> ### illogical-impulse<sup>Quickshell</sup>
This is the latest and only supported style. Other stuff are still there mostly for viewing pleasure and not actual use, but code is still available, see below.
Widget system: Quickshell | Support: Yes Widget system: Quickshell | Support: Yes
[Showcase video](https://www.youtube.com/watch?v=RPwovTInagE) [Showcase video](https://www.youtube.com/watch?v=RPwovTInagE)
@@ -86,52 +80,7 @@ Widget system: Quickshell | Support: Yes
| Window management | Weeb power | | Window management | Weeb power |
| <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e77a7c96-1905-4126-a2a0-434f818825a2" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/c8544e99-8881-477f-b83a-d6e35c0184a1" /> | | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/e77a7c96-1905-4126-a2a0-434f818825a2" /> | <img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/c8544e99-8881-477f-b83a-d6e35c0184a1" /> |
### illogical-impulse<sup>AGS</sup> <sub>(Deprecated)</sub> ### Other styles: Available at the end of the readme.
Widget system: AGS | Support: No
| AI | Common widgets |
|:---|:---------------|
| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |
### Very old stuff
- Not likely to work, but the source is still available in the [`archive`](https://github.com/end-4/dots-hyprland/tree/archive) branch. Extremely spaghetti.
- Click image for a presentation video
#### m3ww
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/85ch8x">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/09533e64-b6d7-47eb-a840-ee90c6776adf" alt="Material Eww!">
</a>
#### NovelKnock
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/7vo61k">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/42903d03-bf6f-49d4-be7f-dd77e6cb389d" alt="Desktop Preview">
</a>
#### Hybrid
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/4oogot">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/190deb1e-f6f5-46ce-8cf0-9b39944c079d" alt="click the circles!">
</a>
#### Windoes
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/5qx614">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/b15317b1-f295-49f5-b90c-fb6328b8d886" alt="Desktop Preview">
</a>
<div align="center"> <div align="center">
<h2>• thank you •</h2> <h2>• thank you •</h2>
@@ -162,4 +111,60 @@ Widget system: AGS | Support: No
</div> </div>
- Inspiration: osu!lazer, Windows 11, Material Design 3, AvdanOS (concept) - Inspiration: osu!lazer, Windows 11, Material Design 3, AvdanOS (concept)
- Copying: The license allows you to. Personally I have absolutely no problem with others redistributing/recreating my work. There's no "stealing" (unless you do weird stuff and violate the license). <sub>(this note is here because some people actually asked)</sub> - Copying: The license allows you to. Personally I have absolutely no problem with others redistributing/recreating my work. There's no "stealing" (maybe unless you loudly do weird stuff and violate the license) <sub>(some people actually had to ask smh)</sub>
---
---
<div align="center">
<h2>• old, UNSUPPORTED stuff •</h2>
<h3></h3>
</div>
- Source for illogical-impulse AGS available in the `ii-ags` branch, others in the `archive` branch.
- The list goes from newest to the oldest, and the code quality is worse the older a style is
- No bug fix or official support will be provided.
### illogical-impulse<sup>AGS</sup>
Widget system: AGS | Support: No
| AI | Common widgets |
|:---|:---------------|
| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |
#### m3ww
Widget system: EWW | Support: No, dead
<a href="https://streamable.com/85ch8x">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/09533e64-b6d7-47eb-a840-ee90c6776adf" alt="Material Eww!">
</a>
#### NovelKnock
Widget system: EWW | Support: No
<a href="https://streamable.com/7vo61k">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/42903d03-bf6f-49d4-be7f-dd77e6cb389d" alt="Desktop Preview">
</a>
#### Hybrid
Widget system: EWW | Support: No
<a href="https://streamable.com/4oogot">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/190deb1e-f6f5-46ce-8cf0-9b39944c079d" alt="click the circles!">
</a>
#### Windoes
Widget system: EWW | Support: No
<a href="https://streamable.com/5qx614">
<img src="https://github.com/end-4/dots-hyprland/assets/97237370/b15317b1-f295-49f5-b90c-fb6328b8d886" alt="Desktop Preview">
</a>