Merge branch 'ii-qs' into ii-qs-patch-1

This commit is contained in:
_xB
2025-06-12 09:23:30 +03:00
committed by GitHub
94 changed files with 2060 additions and 701 deletions
+3 -1
View File
@@ -17,7 +17,9 @@ if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt
cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt
end
alias pamcan=pacman
alias pamcan pacman
alias ls 'eza --icons'
# function fish_prompt
# set_color cyan; echo (pwd)
+3 -3
View File
@@ -7,17 +7,17 @@ general {
}
listener {
timeout = 180 # 3mins
timeout = 300 # 5mins
on-timeout = loginctl lock-session
}
listener {
timeout = 240 # 4mins
timeout = 600 # 10mins
on-timeout = hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on
}
listener {
timeout = 540 # 9mins
timeout = 900 # 15mins
on-timeout = $suspend_cmd
}
+1 -1
View File
@@ -1,6 +1,6 @@
# Bar, wallpaper
exec-once = swww-daemon --format xrgb --no-cache
exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1
exec-once = sleep 0.5; swww img "$(cat ~/.local/state/quickshell/user/generated/wallpaper/path.txt)" --transition-step 100 --transition-fps 120 --transition-type grow --transition-angle 30 --transition-duration 1
exec-once = /usr/lib/geoclue-2.0/demos/agent & gammastep
exec-once = qs &
+6 -4
View File
@@ -59,15 +59,17 @@ decoration {
contrast = 1
popups = true
popups_ignorealpha = 0.6
input_methods = true
input_methods_ignorealpha = 0.8
}
shadow {
enabled = true
ignore_window = true
range = 70
offset = 0 4
render_power = 2
color = rgba(00000020)
range = 30
offset = 0 2
render_power = 4
color = rgba(00000010)
}
# Dim
+1 -1
View File
@@ -208,7 +208,7 @@ bind = Super, W, exec, zen-browser # [hidden]
bind = Super+Shift, W, exec, wps # WPS Office
bind = Super, X, exec, kate # Kate (text editor)
bind = Ctrl+Super, V, exec, pavucontrol-qt # Pavucontrol Qt (volume mixer)
bind = Super, I, exec, XDG_CURRENT_DESKTOP=gnome gnome-control-center # GNOME Control center (settings app)
bind = Super, I, exec, systemsettings # Plasma system settings
bind = Ctrl+Shift, Escape, exec, plasma-systemmonitor --page-name Processes # Plasma system monitor
# Cursed stuff
+5
View File
@@ -19,6 +19,8 @@ windowrulev2 = center, class:^(org.pulseaudio.pavucontrol)$
windowrulev2 = float, class:^(nm-connection-editor)$
windowrulev2 = size 45%, class:^(nm-connection-editor)$
windowrulev2 = center, class:^(nm-connection-editor)$
windowrulev2 = float, class:.*plasmawindowed.*
windowrulev2 = float, class:kcm_.*
# No appearance
# kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all.
@@ -116,9 +118,12 @@ layerrule = animation fade, quickshell:screenCorners
layerrule = animation slide right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide bottom, quickshell:osk
layerrule = animation slide bottom, quickshell:dock
layerrule = blur, quickshell:session
layerrule = noanim, quickshell:session
layerrule = animation fade, quickshell:notificationPopup
layerrule = blur, quickshell:backgroundWidgets
layerrule = ignorealpha 0.05, quickshell:backgroundWidgets
# layerrule = blurpopups, quickshell:.*
# layerrule = blur, quickshell:.*
+1 -1
View File
@@ -45,4 +45,4 @@ post_hook = '~/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh'
[templates.wallpaper]
input_path = '~/.config/matugen/templates/wallpaper.txt'
output_path = '~/.local/state/quickshell/user/generated/wallpaper.txt'
output_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt'
+338
View File
@@ -0,0 +1,338 @@
#!/usr/bin/env python3
# Disclaimer: This script was ai-generated and went through minimal revision.
import os
os.environ["OPENCV_LOG_LEVEL"] = "SILENT"
import cv2
import numpy as np
import argparse
import json
def center_crop(img, target_w, target_h):
h, w = img.shape[:2]
if w == target_w and h == target_h:
return img
x1 = max(0, (w - target_w) // 2)
y1 = max(0, (h - target_h) // 2)
x2 = x1 + target_w
y2 = y1 + target_h
return img[y1:y2, x1:x2]
def find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", padding=50):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img is None:
raise FileNotFoundError(f"Image not found: {image_path}")
orig_h, orig_w = img.shape
scale = 1.0
if screen_width is not None and screen_height is not None:
scale_w = screen_width / orig_w
scale_h = screen_height / orig_h
if screen_mode == "fill":
scale = max(scale_w, scale_h)
else:
scale = min(scale_w, scale_h)
new_w = int(orig_w * scale)
new_h = int(orig_h * scale)
if verbose:
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
img = center_crop(img, screen_width, screen_height)
if verbose:
print(f"Cropped image to {screen_width}x{screen_height}")
else:
if verbose:
print(f"Using original image size: {orig_w}x{orig_h}")
arr = img.astype(np.float64)
h, w = arr.shape
# Use OpenCV's integral for fast computation
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
def region_sum(ii, x1, y1, x2, y2):
total = ii[y2, x2]
if x1 > 0:
total -= ii[y2, x1-1]
if y1 > 0:
total -= ii[y1-1, x2]
if x1 > 0 and y1 > 0:
total += ii[y1-1, x1-1]
return total
min_var = None
min_coords = (0, 0)
area = region_width * region_height
x_start = padding
y_start = padding
x_end = w - region_width - padding + 1
y_end = h - region_height - padding + 1
for y in range(y_start, max(y_end, y_start+1), stride):
for x in range(x_start, max(x_end, x_start+1), stride):
x1, y1 = x, y
x2, y2 = x + region_width - 1, y + region_height - 1
s = region_sum(integral, x1, y1, x2, y2)
s2 = region_sum(integral_sq, x1, y1, x2, y2)
mean = s / area
var = (s2 / area) - (mean ** 2)
if (min_var is None) or (var < min_var):
min_var = var
min_coords = (x, y)
return min_coords, min_var
def find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode="fill", threshold=100.0, aspect_ratio=1.0, padding=50):
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if img is None:
raise FileNotFoundError(f"Image not found: {image_path}")
orig_h, orig_w = img.shape
scale = 1.0
if screen_width is not None and screen_height is not None:
scale_w = screen_width / orig_w
scale_h = screen_height / orig_h
if screen_mode == "fill":
scale = max(scale_w, scale_h)
else:
scale = min(scale_w, scale_h)
new_w = int(orig_w * scale)
new_h = int(orig_h * scale)
if verbose:
print(f"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})")
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
img = center_crop(img, screen_width, screen_height)
if verbose:
print(f"Cropped image to {screen_width}x{screen_height}")
else:
if verbose:
print(f"Using original image size: {orig_w}x{orig_h}")
arr = img.astype(np.float64)
h, w = arr.shape
# Use OpenCV's integral for fast computation
integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]
integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]
def region_sum(ii, x1, y1, x2, y2):
total = ii[y2, x2]
if x1 > 0:
total -= ii[y2, x1-1]
if y1 > 0:
total -= ii[y1-1, x2]
if x1 > 0 and y1 > 0:
total += ii[y1-1, x1-1]
return total
min_size = 10
max_size = min(h, int(w / aspect_ratio)) if aspect_ratio >= 1.0 else min(int(h * aspect_ratio), w)
best = None
best_size = min_size
while min_size <= max_size:
mid = (min_size + max_size) // 2
if aspect_ratio >= 1.0:
region_h = mid
region_w = int(mid * aspect_ratio)
else:
region_w = mid
region_h = int(mid / aspect_ratio)
if region_w > w or region_h > h:
max_size = mid - 1
continue
found = False
x_start = padding
y_start = padding
x_end = w - region_w - padding + 1
y_end = h - region_h - padding + 1
for y in range(y_start, max(y_end, y_start+1), stride):
for x in range(x_start, max(x_end, x_start+1), stride):
x1, y1 = x, y
x2, y2 = x + region_w - 1, y + region_h - 1
s = region_sum(integral, x1, y1, x2, y2)
s2 = region_sum(integral_sq, x1, y1, x2, y2)
area = region_w * region_h
mean = s / area
var = (s2 / area) - (mean ** 2)
if var <= threshold:
found = True
best = (x, y, region_w, region_h, var)
break
if found:
break
if found:
best_size = mid
min_size = mid + 1
else:
max_size = mid - 1
if best:
x, y, region_w, region_h, var = best
center_x = x + region_w // 2
center_y = y + region_h // 2
return (center_x, center_y), (region_w, region_h), var
else:
return None, (0, 0), None
def draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Image not found: {image_path}")
orig_h, orig_w = img.shape[:2]
if screen_width is not None and screen_height is not None:
scale_w = screen_width / orig_w
scale_h = screen_height / orig_h
if screen_mode == "fill":
scale = max(scale_w, scale_h)
else:
scale = min(scale_w, scale_h)
new_w = int(orig_w * scale)
new_h = int(orig_h * scale)
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
img = center_crop(img, screen_width, screen_height)
x, y = coords
cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3)
cv2.imwrite(output_path, img)
print(f"Saved output image with rectangle at {output_path}")
def draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode="fill"):
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Image not found: {image_path}")
orig_h, orig_w = img.shape[:2]
if screen_width is not None and screen_height is not None:
scale_w = screen_width / orig_w
scale_h = screen_height / orig_h
if screen_mode == "fill":
scale = max(scale_w, scale_h)
else:
scale = min(scale_w, scale_h)
new_w = int(orig_w * scale)
new_h = int(orig_h * scale)
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
img = center_crop(img, screen_width, screen_height)
cx, cy = center
region_w, region_h = size
x1 = cx - region_w // 2
y1 = cy - region_h // 2
x2 = cx + region_w // 2 - 1
y2 = cy + region_h // 2 - 1
cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3)
cv2.imwrite(output_path, img)
print(f"Saved output image with largest region at {output_path}")
def get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode="fill"):
img = cv2.imread(image_path)
if img is None:
raise FileNotFoundError(f"Image not found: {image_path}")
orig_h, orig_w = img.shape[:2]
if screen_width is not None and screen_height is not None:
scale_w = screen_width / orig_w
scale_h = screen_height / orig_h
if screen_mode == "fill":
scale = max(scale_w, scale_h)
else:
scale = min(scale_w, scale_h)
new_w = int(orig_w * scale)
new_h = int(orig_h * scale)
img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)
img = center_crop(img, screen_width, screen_height)
# Ensure region is within bounds
x = max(0, x)
y = max(0, y)
w = max(1, min(w, img.shape[1] - x))
h = max(1, min(h, img.shape[0] - y))
region = img[y:y+h, x:x+w]
if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0:
return [0, 0, 0]
region = region.reshape((-1, 3))
# Filter out black pixels (optional, improves accuracy for some images)
non_black = region[np.any(region > 10, axis=1)]
if non_black.shape[0] == 0:
non_black = region
region = np.float32(non_black)
if region.shape[0] < 3:
return [int(x) for x in np.mean(region, axis=0)]
# K-means to find dominant color
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
K = min(3, region.shape[0])
_, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
counts = np.bincount(labels.flatten())
dominant = centers[np.argmax(counts)]
return [int(x) for x in dominant]
def main():
parser = argparse.ArgumentParser(description="Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.")
parser.add_argument("image_path", help="Path to the input image")
parser.add_argument("--width", type=int, default=300, help="Region width")
parser.add_argument("--height", type=int, default=200, help="Region height")
parser.add_argument("-v", "--visual-output", action="store_true", help="Output image with rectangle")
parser.add_argument("--screen-width", type=int, default=1920, help="Screen width for wallpaper scaling")
parser.add_argument("--screen-height", type=int, default=1080, help="Screen height for wallpaper scaling")
parser.add_argument("--stride", type=int, default=4, help="Step size for sliding window (higher is faster, less precise)")
parser.add_argument("--screen-mode", choices=["fill", "fit"], default="fill", help="Wallpaper scaling mode: 'fill' (default) or 'fit'")
parser.add_argument("--verbose", action="store_true", help="Print verbose output")
parser.add_argument("-l", "--largest-region", action="store_true", help="Find the largest region under the variance threshold and output its center")
parser.add_argument("-t", "--variance-threshold", type=float, default=1000.0, help="Variance threshold for largest region mode")
parser.add_argument("--aspect-ratio", type=float, default=1.78, help="Aspect ratio (width/height) for largest region mode")
parser.add_argument("--padding", type=int, default=50, help="Minimum distance from region to image edge (default: 50)")
args = parser.parse_args()
if args.largest_region:
center, size, var = find_largest_region(
args.image_path,
screen_width=args.screen_width,
screen_height=args.screen_height,
verbose=args.verbose,
stride=args.stride,
screen_mode=args.screen_mode,
threshold=args.variance_threshold,
aspect_ratio=args.aspect_ratio,
padding=args.padding
)
if center:
if args.visual_output:
draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
# Extract dominant color
cx, cy = center
region_w, region_h = size
x1 = cx - region_w // 2
y1 = cy - region_h // 2
dominant_color = get_dominant_color(
args.image_path, x1, y1, region_w, region_h,
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
)
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
print(json.dumps({
"center_x": center[0],
"center_y": center[1],
"width": size[0],
"height": size[1],
"variance": var,
"dominant_color": dominant_color_hex
}))
else:
print(json.dumps({"error": "No region found under the threshold."}))
return
coords, variance = find_least_busy_region(
args.image_path,
region_width=args.width,
region_height=args.height,
screen_width=args.screen_width,
screen_height=args.screen_height,
verbose=args.verbose,
stride=args.stride,
screen_mode=args.screen_mode,
padding=args.padding
)
if args.visual_output:
draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)
# Output JSON with center point
center_x = coords[0] + args.width // 2
center_y = coords[1] + args.height // 2
dominant_color = get_dominant_color(
args.image_path, coords[0], coords[1], args.width, args.height,
screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode
)
dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)
print(json.dumps({
"center_x": center_x,
"center_y": center_y,
"width": args.width,
"height": args.height,
"variance": variance,
"dominant_color": dominant_color_hex
}))
if __name__ == "__main__":
main()
@@ -0,0 +1,135 @@
import "root:/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.UPower
Scope {
id: root
property string filePath: `${Directories.state}/user/generated/wallpaper/least_busy_region.json`
property real centerX: 0
property real centerY: 0
property color dominantColor: Appearance.colors.colPrimary
property bool dominantColorIsDark: dominantColor.hslLightness < 0.5
property color colBackground: ColorUtils.transparentize(ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colSecondaryContainer), 1)
property color colText: ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (root.dominantColorIsDark ? 0.8 : 0.12))
function updateWidgetPosition(fileContent) {
// console.log("[BackgroundWidgets] Updating widget position with content:", fileContent)
const parsedContent = JSON.parse(fileContent)
root.centerX = parsedContent.center_x
root.centerY = parsedContent.center_y
root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary
}
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
running: false
onTriggered: {
root.updateWidgetPosition(leastBusyRegionFileView.text())
}
}
FileView {
id: leastBusyRegionFileView
path: Qt.resolvedUrl(root.filePath)
watchChanges: true
onFileChanged: {
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = leastBusyRegionFileView.text()
root.updateWidgetPosition(fileContent)
}
}
Variants { // For each monitor
model: Quickshell.screens
Loader {
required property var modelData
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)
active: !ToplevelManager.activeToplevel?.activated
sourceComponent: PanelWindow { // Window
id: windowRoot
screen: modelData
property var textHorizontalAlignment: root.centerX / monitor.scale < windowRoot.width / 3 ? Text.AlignLeft :
(root.centerX / monitor.scale > windowRoot.width * 2 / 3 ? Text.AlignRight : Text.AlignHCenter)
WlrLayershell.layer: WlrLayer.Bottom
WlrLayershell.namespace: "quickshell:backgroundWidgets"
anchors {
top: true
bottom:true
left: true
right: true
}
color: "transparent"
HyprlandWindow.visibleMask: Region {
item: widgetBackground
}
Rectangle {
id: widgetBackground
property real verticalPadding: 20
property real horizontalPadding: 30
radius: 40
color: root.colBackground
implicitHeight: columnLayout.implicitHeight + verticalPadding * 2
implicitWidth: columnLayout.implicitWidth + horizontalPadding * 2
anchors {
left: parent.left
top: parent.top
leftMargin: (root.centerX / monitor.scale - implicitWidth / 2)
topMargin: (root.centerY / monitor.scale - implicitHeight / 2)
Behavior on leftMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
Behavior on topMargin {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
}
ColumnLayout {
id: columnLayout
anchors.centerIn: parent
spacing: -5
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 95
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.time
}
StyledText {
Layout.fillWidth: true
horizontalAlignment: windowRoot.textHorizontalAlignment
font.pixelSize: 25
color: root.colText
style: Text.Raised
styleColor: Appearance.colors.colShadow
text: DateTime.date
}
}
}
}
}
}
}
@@ -12,7 +12,6 @@ Item {
height: parent.height
width: colLayout.width
ColumnLayout {
id: colLayout
+15 -5
View File
@@ -19,6 +19,13 @@ Scope {
readonly property int osdHideMouseMoveThreshold: 20
property bool showBarBackground: ConfigOptions.bar.showBackground
component VerticalBarSeparator: Rectangle {
Layout.topMargin: barHeight / 3
Layout.bottomMargin: barHeight / 3
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.colors.colOutlineVariant
// Check screensList from config, If no screens are specified, show on all screens
property var filteredScreens: {
@@ -150,7 +157,7 @@ Scope {
colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarLeftOpen
@@ -177,7 +184,7 @@ Scope {
}
ActiveWindow {
visible: barRoot.useShortenedForm === 0
visible: barRoot.useShortenedForm === 0 && width > 0 && height > 0
Layout.rightMargin: Appearance.rounding.screenRounding
Layout.fillWidth: true
bar: barRoot
@@ -189,7 +196,7 @@ Scope {
RowLayout { // Middle section
id: middleSection
anchors.centerIn: parent
spacing: 8
spacing: ConfigOptions?.bar.borderless ? 4 : 8
RowLayout {
id: leftCenterGroup
@@ -210,9 +217,10 @@ Scope {
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout {
id: middleCenterGroup
Layout.fillWidth: true
Layout.fillHeight: true
Workspaces {
@@ -231,6 +239,8 @@ Scope {
}
VerticalBarSeparator {visible: ConfigOptions?.bar.borderless}
RowLayout {
id: rightCenterGroup
Layout.preferredWidth: leftCenterGroup.width
@@ -344,7 +354,7 @@ Scope {
colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
colBackgroundToggled: Appearance.m3colors.m3secondaryContainer
colBackgroundToggled: Appearance.colors.colSecondaryContainer
colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover
colRippleToggled: Appearance.colors.colSecondaryContainerActive
toggled: GlobalStates.sidebarRightOpen
@@ -48,7 +48,7 @@ Rectangle {
lineWidth: 2
value: percentage
size: 26
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.m3colors.m3secondaryContainer
secondaryColor: (isLow && !isCharging) ? batteryLowBackground : Appearance.colors.colSecondaryContainer
primaryColor: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
fill: (isLow && !isCharging)
+2 -2
View File
@@ -62,13 +62,13 @@ Item {
lineWidth: 2
value: activePlayer?.position / activePlayer?.length
size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
anchors.centerIn: parent
fill: 1
text: activePlayer?.isPlaying ? "pause" : "play_arrow"
text: activePlayer?.isPlaying ? "pause" : "music_note"
iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer
}
+1 -1
View File
@@ -23,7 +23,7 @@ Item {
lineWidth: 2
value: percentage
size: 26
secondaryColor: Appearance.m3colors.m3secondaryContainer
secondaryColor: Appearance.colors.colSecondaryContainer
primaryColor: Appearance.m3colors.m3onSecondaryContainer
MaterialSymbol {
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -98,15 +99,17 @@ Item {
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: Appearance.rounding.full
property var radiusLeft: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index)) ? 0 : Appearance.rounding.full
property var radiusRight: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2)) ? 0 : Appearance.rounding.full
property var leftOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+2))
property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full
property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full
topLeftRadius: radiusLeft
bottomLeftRadius: radiusLeft
topRightRadius: radiusRight
bottomRightRadius: radiusRight
color: Appearance.colors.colLayer2
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity {
@@ -144,13 +147,13 @@ Item {
Behavior on activeWorkspaceMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on idx1 {
Behavior on idx1 { // Leading anim
NumberAnimation {
duration: 100
easing.type: Easing.OutSine
}
}
Behavior on idx2 {
Behavior on idx2 { // Following anim
NumberAnimation {
duration: 300
easing.type: Easing.OutSine
@@ -203,7 +206,7 @@ Item {
elide: Text.ElideRight
color: (monitor.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.colors.colOnLayer1 :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity {
@@ -15,11 +15,11 @@ Singleton {
property QtObject sizes
property string syntaxHighlightingTheme
// [!] Enabling transparency can affect readability when using light theme.
// Extremely conservative transparency values for consistency and readability
property real transparency: 0
property real contentTransparency: 0
// property real transparency: 0.15
// property real contentTransparency: 0.5
// property real transparency: m3colors.darkmode ? 0.05 : 0
// property real contentTransparency: m3colors.darkmode ? 0.18 : 0
m3colors: QtObject {
property bool darkmode: false
@@ -106,10 +106,10 @@ Singleton {
property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))
property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency);
property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency);
property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency)
property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.7), root.contentTransparency)
property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency)
@@ -122,21 +122,30 @@ Singleton {
property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency)
property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency);
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)
property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)
property color colPrimaryContainer: m3colors.m3primaryContainer
property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Hover, 0.7)
property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colLayer1Active, 0.6)
property color colSecondary: m3colors.m3secondary
property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colSecondaryContainer: ColorUtils.transparentize(m3colors.m3secondaryContainer, root.contentTransparency)
property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6)
property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colSurfaceContainerLow: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency)
property color colSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency)
property color colSurfaceContainerHigh: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency)
property color colSurfaceContainerHighest: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency)
property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color
property color colTooltip: m3colors.darkmode ? ColorUtils.mix(m3colors.m3background, "#3C4043", 0.5) : "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses #3C4043
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.85)
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutlineVariant: m3colors.m3outlineVariant
}
rounding: QtObject {
@@ -176,6 +185,7 @@ Singleton {
animationCurves: QtObject {
readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms
readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms
readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]
readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]
@@ -287,7 +297,7 @@ Singleton {
property real searchWidthCollapsed: 260
property real searchWidth: 450
property real hyprlandGapsOut: 5
property real elevationMargin: 8
property real elevationMargin: 10
property real fabShadowRadius: 5
property real fabHoveredShadowRadius: 7
}
@@ -9,14 +9,23 @@ Singleton {
}
property QtObject appearance: QtObject {
property int fakeScreenRounding: 1 // 0: None | 1: Always | 2: When not fullscreen
property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen
}
property QtObject audio: QtObject { // Values in %
property QtObject protection: QtObject { // Prevent sudden bangs
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90 // Realistically should already provide some protection when it's 99...
}
}
property QtObject apps: QtObject {
property string bluetooth: "better-control --bluetooth"
property string bluetooth: "kcmshell6 kcm_bluetooth"
property string imageViewer: "loupe"
property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi"
property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center"
property string network: "plasmawindowed org.kde.plasma.networkmanagement"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string settings: "systemsettings"
property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions
}
@@ -29,7 +38,7 @@ Singleton {
property QtObject bar: QtObject {
property bool bottom: false // Instead of top
property bool borderless: true
property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true
property QtObject resources: QtObject {
@@ -48,15 +57,24 @@ Singleton {
}
property QtObject dock: QtObject {
property bool enable: false
property real height: 60
property real hoverRegionHeight: 3
property bool pinnedOnStartup: false
property bool hoverToReveal: false // When false, only reveals on empty workspace
property list<string> pinnedApps: [ // IDs of pinned entries
"org.kde.dolphin",
"kitty",
]
}
property QtObject language: QtObject {
property QtObject translator: QtObject {
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
property string targetLanguage: "auto" // Run `trans -list-all` for available languages
property string sourceLanguage: "auto"
}
}
property QtObject networking: QtObject {
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"
}
@@ -21,7 +21,7 @@ Singleton {
property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/latex`)
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
@@ -32,6 +32,7 @@ Singleton {
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.config}/quickshell/scripts/switchwall.sh`)
// Cleanup on init
Component.onCompleted: {
Hyprland.dispatch(`exec mkdir -p '${shellConfig}'`)
Hyprland.dispatch(`exec mkdir -p '${favicons}'`)
Hyprland.dispatch(`exec rm -rf '${coverArt}'; mkdir -p '${coverArt}'`)
Hyprland.dispatch(`exec rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`)
@@ -39,6 +39,30 @@ function colorWithSaturationOf(color1, color2) {
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).
*
* @param {string} color - The base color (any Qt.color-compatible string).
* @param {number} lightness - The lightness value to use (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
/**
* Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take lightness from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
@@ -66,7 +90,7 @@ function adaptToAccent(color1, color2) {
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage) {
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
@@ -11,7 +11,7 @@ import QtQuick.Layouts
*/
Rectangle {
id: root
default property alias content: rowLayout.data
default property alias data: rowLayout.data
property real spacing: 5
property real padding: 0
property int clickIndex: rowLayout.clickIndex
@@ -14,8 +14,8 @@ Item {
property int lineWidth: 2
property real value: 0
property color primaryColor: Appearance.m3colors.m3onSecondaryContainer
property color secondaryColor: Appearance.m3colors.m3secondaryContainer
property real gapAngle: Math.PI / 10
property color secondaryColor: Appearance.colors.colSecondaryContainer
property real gapAngle: Math.PI / 9
property bool fill: false
property int fillOverflow: 2
property int animationDuration: 1000
@@ -15,7 +15,7 @@ Rectangle {
property real extraBottomBorderWidth: 2
property color borderColor: Appearance.colors.colOnLayer0
property real borderRadius: 5
property color keyColor: Appearance.m3colors.m3surfaceContainerLow
property color keyColor: Appearance.colors.colSurfaceContainerLow
implicitWidth: keyFace.implicitWidth + borderWidth * 2
implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth
radius: borderRadius
@@ -6,23 +6,26 @@ Text {
id: root
property real iconSize: Appearance?.font.pixelSize.small ?? 16
property real fill: 0
renderType: Text.NativeRendering
font.hintingPreference: Font.PreferFullHinting
property real truncatedFill: Math.round(fill * 100) / 100 // Reduce memory consumption spikes from constant font remapping
renderType: Text.CurveRendering
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
pixelSize: iconSize
}
verticalAlignment: Text.AlignVCenter
font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded"
font.pixelSize: iconSize
color: Appearance.m3colors.m3onBackground
Behavior on fill {
NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
}
// Behavior on fill {
// NumberAnimation {
// duration: Appearance?.animation.elementMoveFast.duration ?? 200
// easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
// easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
// }
// }
font.variableAxes: {
"FILL": fill,
"FILL": truncatedFill,
// "wght": font.weight,
// "GRAD": 0,
"opsz": iconSize,
@@ -30,7 +30,7 @@ Button {
Layout.alignment: Qt.AlignHCenter
radius: Appearance.rounding.full
color: toggled ?
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) :
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer) :
(button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color {
@@ -15,7 +15,7 @@ RippleButton {
leftPadding: 15
rightPadding: 15
buttonRadius: Appearance.rounding.small
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3secondaryContainer : Appearance.m3colors.m3surfaceContainerHighest
colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover
colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive
@@ -26,7 +26,7 @@ Rectangle { // App icon
implicitWidth: size
implicitHeight: size
radius: Appearance.rounding.full
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
Loader {
id: materialSymbolLoader
active: root.appIcon == ""
@@ -113,7 +113,7 @@ Item { // Notification group area
id: background
anchors.left: parent.left
width: parent.width
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.leftMargin: root.xOffset
@@ -154,42 +154,56 @@ Item { // Notification group area
ColumnLayout { // Content
Layout.fillWidth: true
spacing: expanded ?
((root.multipleNotifications &&
notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
5) : 0
spacing: expanded ? (root.multipleNotifications ?
(notificationGroup?.notifications[root.notificationCount - 1].image != "") ? 35 :
5 : 0) : 0
// spacing: 00
Behavior on spacing {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
RowLayout { // App name (or summary when there's only 1 notif) and time
Item { // App name (or summary when there's only 1 notif) and time
id: topRow
spacing: 0
// spacing: 0
Layout.fillWidth: true
property real fontSize: Appearance.font.pixelSize.smaller
property bool showAppName: root.multipleNotifications
implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)
StyledText {
id: appName
text: (topRow.showAppName ?
notificationGroup?.appName :
notificationGroup?.notifications[0]?.summary) || ""
font.pixelSize: topRow.showAppName ?
topRow.fontSize :
Appearance.font.pixelSize.small
color: topRow.showAppName ?
Appearance.colors.colSubtext :
Appearance.colors.colOnLayer2
RowLayout {
id: topTextRow
anchors.left: parent.left
anchors.right: expandButton.left
anchors.verticalCenter: parent.verticalCenter
spacing: 5
StyledText {
id: appName
elide: Text.ElideRight
Layout.fillWidth: true
text: (topRow.showAppName ?
notificationGroup?.appName :
notificationGroup?.notifications[0]?.summary) || ""
font.pixelSize: topRow.showAppName ?
topRow.fontSize :
Appearance.font.pixelSize.small
color: topRow.showAppName ?
Appearance.colors.colSubtext :
Appearance.colors.colOnLayer2
}
StyledText {
id: timeText
// Layout.fillWidth: true
Layout.rightMargin: 10
horizontalAlignment: Text.AlignLeft
text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
font.pixelSize: topRow.fontSize
color: Appearance.colors.colSubtext
}
}
StyledText {
id: timeText
text: " • " + NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)
font.pixelSize: topRow.fontSize
color: Appearance.colors.colSubtext
Layout.alignment: Qt.AlignRight
Layout.fillWidth: true
}
Item { Layout.fillWidth: true }
NotificationGroupExpandButton {
id: expandButton
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
count: root.notificationCount
expanded: root.expanded
fontSize: topRow.fontSize
@@ -129,9 +129,9 @@ Item { // Notification item area
color: (expanded && !onlyNotification) ?
(notificationObject.urgency == NotificationUrgency.Critical) ?
ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.m3colors.m3surfaceContainerHigh) :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest)
ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :
(Appearance.colors.colSurfaceContainerHigh) :
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest)
implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight
Behavior on implicitHeight {
@@ -110,13 +110,29 @@ TabButton {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
Rectangle {
Item {
id: ripple
radius: Appearance?.rounding.full ?? 9999
color: button.colRipple
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
property real implicitWidth: 0
property real implicitHeight: 0
visible: width > 0 && height > 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: button.colRipple }
GradientStop { position: 0.3; color: button.colRipple }
GradientStop { position: 0.5 ; color: Qt.rgba(button.colRipple.r, button.colRipple.g, button.colRipple.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
@@ -13,6 +13,7 @@ Item {
implicitWidth: (reveal || vertical) ? childrenRect.width : 0
implicitHeight: (reveal || !vertical) ? childrenRect.height : 0
visible: width > 0 && height > 0
Behavior on implicitWidth {
enabled: !vertical
@@ -150,16 +150,29 @@ Button {
}
}
Rectangle {
Item {
id: ripple
radius: Appearance?.rounding.full ?? 9999
width: ripple.implicitWidth
height: ripple.implicitHeight
opacity: 0
color: root.rippleColor
Behavior on color {
visible: width > 0 && height > 0
property real implicitWidth: 0
property real implicitHeight: 0
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)
}
RadialGradient {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: root.rippleColor }
GradientStop { position: 0.3; color: root.rippleColor }
GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }
}
}
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
@@ -0,0 +1,129 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
Item {
id: root
property real dialogPadding: 15
property real dialogMargin: 30
property string titleText: "Selection Dialog"
property alias items: choiceModel.values
property int selectedId: choiceListView.currentIndex
property var defaultChoice
signal canceled();
signal selected(var result);
Rectangle { // Scrim
id: scrimOverlay
anchors.fill: parent
radius: Appearance.rounding.small
color: Appearance.colors.colScrim
MouseArea {
hoverEnabled: true
anchors.fill: parent
preventStealing: true
propagateComposedEvents: false
}
}
Rectangle { // The dialog
id: dialog
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
anchors.fill: parent
anchors.margins: dialogMargin
implicitHeight: dialogColumnLayout.implicitHeight
ColumnLayout {
id: dialogColumnLayout
anchors.fill: parent
spacing: 16
StyledText {
id: dialogTitle
Layout.topMargin: dialogPadding
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
Layout.alignment: Qt.AlignLeft
color: Appearance.m3colors.m3onSurface
font.pixelSize: Appearance.font.pixelSize.larger
text: root.titleText
}
Rectangle {
color: Appearance.m3colors.m3outline
implicitHeight: 1
Layout.fillWidth: true
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
}
ListView {
id: choiceListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1
model: ScriptModel {
id: choiceModel
}
delegate: StyledRadioButton {
id: radioButton
required property var modelData
required property int index
anchors {
left: parent?.left
right: parent?.right
leftMargin: root.dialogPadding
rightMargin: root.dialogPadding
}
description: modelData.toString()
checked: index === choiceListView.currentIndex
onCheckedChanged: {
if (checked) {
choiceListView.currentIndex = index;
}
}
}
}
Rectangle {
color: Appearance.m3colors.m3outline
implicitHeight: 1
Layout.fillWidth: true
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
}
RowLayout {
id: dialogButtonsRowLayout
Layout.bottomMargin: dialogPadding
Layout.leftMargin: dialogPadding
Layout.rightMargin: dialogPadding
Layout.alignment: Qt.AlignRight
DialogButton {
buttonText: qsTr("Cancel")
onClicked: root.canceled()
}
DialogButton {
buttonText: qsTr("OK")
onClicked: root.selected(
root.selectedId === -1 ? null :
root.items[root.selectedId]
)
}
}
}
}
}
@@ -0,0 +1,16 @@
import "root:/modules/common"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Label {
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.main ?? "sans-serif"
pixelSize: Appearance?.font.pixelSize.small ?? 15
}
color: Appearance?.m3colors.m3onBackground ?? "black"
linkColor: Appearance?.m3colors.m3primary
}
@@ -18,6 +18,7 @@ ListView {
property real removeOvershoot: 20 // Account for gaps and bouncy animations
property int dragIndex: -1
property real dragDistance: 0
property bool popin: true
function resetDrag() {
root.dragIndex = -1
@@ -27,7 +28,7 @@ ListView {
add: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
properties: popin ? "opacity,scale" : "opacity",
from: 0,
to: 1,
}),
@@ -40,46 +41,46 @@ ListView {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
properties: popin ? "opacity,scale" : "opacity",
to: 1,
}),
]
}
displaced: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
// displaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
move: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
moveDisplaced: Transition {
animations: [
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
property: "y",
}),
Appearance?.animation.elementMove.numberAnimation.createObject(this, {
properties: "opacity,scale",
to: 1,
}),
]
}
// move: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
// moveDisplaced: Transition {
// animations: [
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// property: "y",
// }),
// Appearance?.animation.elementMove.numberAnimation.createObject(this, {
// properties: "opacity,scale",
// to: 1,
// }),
// ]
// }
remove: Transition {
animations: [
@@ -19,7 +19,7 @@ Slider {
property real handleLimit: root.backgroundDotMargins
property real trackHeight: 30 * scale
property color highlightColor: Appearance.colors.colPrimary
property color trackColor: Appearance.m3colors.m3secondaryContainer
property color trackColor: Appearance.colors.colSecondaryContainer
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
property real trackRadius: Appearance.rounding.verysmall * scale
property real unsharpenRadius: Appearance.rounding.unsharpen
@@ -13,7 +13,7 @@ Switch {
implicitHeight: 32 * root.scale
implicitWidth: 52 * root.scale
property color activeColor: Appearance?.colors.colPrimary ?? "#685496"
property color inactiveColor: Appearance?.m3colors.m3surfaceContainerHighest ?? "#45464F"
property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? "#45464F"
PointingHandInteraction {}
@@ -11,4 +11,5 @@ Text {
pixelSize: Appearance?.font.pixelSize.small ?? 15
}
color: Appearance?.m3colors.m3onBackground ?? "black"
linkColor: Appearance?.m3colors.m3primary
}
@@ -5,7 +5,7 @@ import QtQuick.Controls
TextArea {
renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderTextColor: Appearance.m3colors.m3outline
font {
family: Appearance?.font.family.main ?? "sans-serif"
@@ -0,0 +1,78 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
Canvas { // Visualizer
id: root
property list<var> points
property list<var> smoothPoints
property real maxVisualizerValue: 1000
property int smoothing: 2
property bool live: true
property color color: Appearance.m3colors.m3primary
onPointsChanged: () => {
root.requestPaint()
}
anchors.fill: parent
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
var points = root.points;
var maxVal = root.maxVisualizerValue || 1;
var h = height;
var w = width;
var n = points.length;
if (n < 2) return;
// Smoothing: simple moving average (optional)
var smoothWindow = root.smoothing; // adjust for more/less smoothing
root.smoothPoints = [];
for (var i = 0; i < n; ++i) {
var sum = 0, count = 0;
for (var j = -smoothWindow; j <= smoothWindow; ++j) {
var idx = Math.max(0, Math.min(n - 1, i + j));
sum += points[idx];
count++;
}
root.smoothPoints.push(sum / count);
}
if (!root.live) smoothedPoints.fill(0); // If not playing, show no points
ctx.beginPath();
ctx.moveTo(0, h);
for (var i = 0; i < n; ++i) {
var x = i * w / (n - 1);
var y = h - (root.smoothPoints[i] / maxVal) * h;
ctx.lineTo(x, y);
}
ctx.lineTo(w, h);
ctx.closePath();
ctx.fillStyle = Qt.rgba(
root.color.r,
root.color.g,
root.color.b,
0.15
);
ctx.fill();
}
layer.enabled: true
layer.effect: MultiEffect { // Blur a bit to obscure away the points
source: root
saturation: 0.2
blurEnabled: true
blurMax: 7
blur: 1
}
}
+107 -92
View File
@@ -18,115 +18,130 @@ Scope { // Scope
Variants { // For each monitor
model: Quickshell.screens
PanelWindow { // Window
Loader {
id: dockLoader
required property var modelData
id: dockRoot
screen: modelData
property bool reveal: root.pinned || dockMouseArea.containsMouse || dockApps.requestDockShow
active: ConfigOptions?.dock.hoverToReveal || (!ToplevelManager.activeToplevel?.activated)
anchors {
bottom: true
left: true
right: true
}
sourceComponent: PanelWindow { // Window
id: dockRoot
screen: dockLoader.modelData
property bool reveal: root.pinned
|| (ConfigOptions?.dock.hoverToReveal && dockMouseArea.containsMouse)
|| dockApps.requestDockShow
|| (!ToplevelManager.activeToplevel?.activated)
function hide() {
cheatsheetLoader.active = false
}
exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0
implicitWidth: dockBackground.implicitWidth
WlrLayershell.namespace: "quickshell:dock"
color: "transparent"
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region {
item: dockMouseArea
}
MouseArea {
id: dockMouseArea
anchors.top: parent.top
height: parent.height
anchors.topMargin: dockRoot.reveal ? 0 : dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight
anchors.left: parent.left
anchors.right: parent.right
hoverEnabled: true
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
anchors {
bottom: true
left: true
right: true
}
Item {
id: dockHoverRegion
anchors.fill: parent
exclusiveZone: root.pinned ? implicitHeight
- (Appearance.sizes.hyprlandGapsOut)
- (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0
implicitWidth: dockBackground.implicitWidth
WlrLayershell.namespace: "quickshell:dock"
color: "transparent"
implicitHeight: (ConfigOptions?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut
mask: Region {
item: dockMouseArea
}
MouseArea {
id: dockMouseArea
anchors.top: parent.top
height: parent.height
anchors.topMargin: dockRoot.reveal ? 0 :
ConfigOptions?.dock.hoverToReveal ? (dockRoot.implicitHeight - ConfigOptions.dock.hoverRegionHeight) :
(dockRoot.implicitHeight + 1)
anchors.left: parent.left
anchors.right: parent.right
hoverEnabled: true
Behavior on anchors.topMargin {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Item {
id: dockBackground
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
id: dockHoverRegion
anchors.fill: parent
implicitWidth: dockRow.implicitWidth + 5 * 2
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
Item { // Wrapper for the dock background
id: dockBackground
anchors {
top: parent.top
bottom: parent.bottom
horizontalCenter: parent.horizontalCenter
}
StyledRectangularShadow {
target: dockVisualBackground
}
Rectangle {
id: dockVisualBackground
property real margin: Appearance.sizes.elevationMargin
anchors.fill: parent
anchors.topMargin: margin
anchors.bottomMargin: margin
color: Appearance.colors.colLayer0
radius: Appearance.rounding.large
}
implicitWidth: dockRow.implicitWidth + 5 * 2
height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
RowLayout {
id: dockRow
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property real padding: 5
StyledRectangularShadow {
target: dockVisualBackground
}
Rectangle { // The real rectangle that is visible
id: dockVisualBackground
property real margin: Appearance.sizes.elevationMargin
anchors.fill: parent
anchors.topMargin: Appearance.sizes.elevationMargin
anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut
color: Appearance.colors.colLayer0
radius: Appearance.rounding.large
}
VerticalButtonGroup {
GroupButton { // Pin button
baseWidth: 35
baseHeight: 35
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
buttonRadius: Appearance.rounding.normal
toggled: root.pinned
onClicked: root.pinned = !root.pinned
RowLayout {
id: dockRow
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 3
property real padding: 5
VerticalButtonGroup {
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
GroupButton { // Pin button
baseWidth: 35
baseHeight: 35
clickedWidth: baseWidth
clickedHeight: baseHeight + 20
buttonRadius: Appearance.rounding.normal
toggled: root.pinned
onClicked: root.pinned = !root.pinned
contentItem: MaterialSymbol {
text: "keep"
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
}
}
}
DockSeparator {}
DockApps { id: dockApps; }
DockSeparator {}
DockButton {
Layout.fillHeight: true
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
contentItem: MaterialSymbol {
text: "keep"
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0
font.pixelSize: parent.width / 2
text: "apps"
color: Appearance.colors.colOnLayer0
}
}
}
DockSeparator {}
DockApps { id: dockApps }
DockSeparator {}
DockButton {
onClicked: Hyprland.dispatch("global quickshell:overviewToggle")
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
font.pixelSize: parent.width / 2
text: "apps"
color: Appearance.colors.colOnLayer0
}
}
}
}
}
}
}
}
}
}
}
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
@@ -13,32 +14,102 @@ import Quickshell.Wayland
import Quickshell.Hyprland
DockButton {
id: appButton
required property var appToplevel
id: root
property var appToplevel
property var appListRoot
property int lastFocused: -1
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
appListRoot.lastHoveredButton = appButton
appListRoot.buttonHovered = true
lastFocused = appToplevel.toplevels.length - 1
property real iconSize: 35
property real countDotWidth: 10
property real countDotHeight: 4
property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined
property bool isSeparator: appToplevel.appId === "SEPARATOR"
property var desktopEntry: DesktopEntries.byId(appToplevel.appId)
enabled: !isSeparator
implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset
Loader {
active: isSeparator
anchors {
fill: parent
topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
}
onExited: {
if (appListRoot.lastHoveredButton === appButton) {
appListRoot.buttonHovered = false
sourceComponent: DockSeparator {}
}
Loader {
anchors.fill: parent
active: appToplevel.toplevels.length > 0
sourceComponent: MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
onEntered: {
appListRoot.lastHoveredButton = root
appListRoot.buttonHovered = true
lastFocused = appToplevel.toplevels.length - 1
}
onExited: {
if (appListRoot.lastHoveredButton === root) {
appListRoot.buttonHovered = false
}
}
}
}
onClicked: {
if (appToplevel.toplevels.length === 0) {
root.desktopEntry?.execute();
return;
}
lastFocused = (lastFocused + 1) % appToplevel.toplevels.length
appToplevel.toplevels[lastFocused].activate()
}
contentItem: IconImage {
id: iconImage
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
middleClickAction: () => {
root.desktopEntry?.execute();
}
contentItem: Loader {
active: !isSeparator
sourceComponent: Item {
anchors.centerIn: parent
Loader {
id: iconImageLoader
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
active: !root.isSeparator
sourceComponent: IconImage {
source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), "image-missing")
implicitSize: root.iconSize
}
}
RowLayout {
spacing: 3
anchors {
top: iconImageLoader.bottom
topMargin: 2
horizontalCenter: parent.horizontalCenter
}
Repeater {
model: Math.min(appToplevel.toplevels.length, 3)
delegate: Rectangle {
required property int index
radius: Appearance.rounding.full
implicitWidth: (appToplevel.toplevels.length <= 3) ?
root.countDotWidth : root.countDotHeight // Circles when too many
implicitHeight: root.countDotHeight
color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4)
}
}
}
}
}
}
+60 -37
View File
@@ -23,40 +23,66 @@ Item {
property Item lastHoveredButton
property bool buttonHovered: false
property bool requestDockShow: previewPopup.show
property real popupX: parentWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, root.lastHoveredButton.height / 2).x - implicitWidth / 2
implicitWidth: rowLayout.implicitWidth
implicitHeight: rowLayout.implicitHeight
RowLayout {
id: rowLayout
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work
implicitWidth: listView.implicitWidth
StyledListView {
id: listView
spacing: 2
orientation: ListView.Horizontal
anchors {
top: parent.top
bottom: parent.bottom
}
implicitWidth: contentWidth
Repeater {
model: ScriptModel {
objectProp: "appId"
values: {
var map = new Map();
Behavior on implicitWidth {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
for (const toplevel of ToplevelManager.toplevels.values) {
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), []);
map.get(toplevel.appId.toLowerCase()).push(toplevel);
}
model: ScriptModel {
objectProp: "appId"
values: {
var map = new Map();
var values = [];
for (const [key, value] of map) {
values.push({ appId: key, toplevels: value });
}
return values;
// Pinned apps
const pinnedApps = ConfigOptions?.dock.pinnedApps ?? [];
for (const appId of pinnedApps) {
if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({
pinned: true,
toplevels: []
}));
}
// Separator
if (pinnedApps.length > 0) {
map.set("SEPARATOR", { pinned: false, toplevels: [] });
}
// Open windows
for (const toplevel of ToplevelManager.toplevels.values) {
if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({
pinned: false,
toplevels: []
}));
map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel);
}
var values = [];
for (const [key, value] of map) {
values.push({ appId: key, toplevels: value.toplevels, pinned: value.pinned });
}
return values;
}
delegate: DockAppButton {
required property var modelData
appToplevel: modelData
appListRoot: root
}
}
delegate: DockAppButton {
required property var modelData
appToplevel: modelData
appListRoot: root
}
}
@@ -118,14 +144,9 @@ Item {
anchors.bottom: parent.bottom
implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2
// anchors.horizontalCenter: parent.horizontalCenter
hoverEnabled: true
// x: previewPopup.width / 2 + root.popupX
// Behavior on x {
// animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
// }
x: {
const itemCenter = root.QsWindow.mapFromItem(root.lastHoveredButton, root.lastHoveredButton.width / 2, 0);
const itemCenter = root.QsWindow?.mapFromItem(root.lastHoveredButton, root.lastHoveredButton?.width / 2, 0);
return itemCenter.x - width / 2
}
StyledRectangularShadow {
@@ -145,7 +166,7 @@ Item {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
clip: true
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.sizes.elevationMargin
@@ -163,7 +184,9 @@ Item {
id: previewRowLayout
anchors.centerIn: parent
Repeater {
model: previewPopup.appTopLevel?.toplevels ?? []
model: ScriptModel {
values: previewPopup.appTopLevel?.toplevels ?? []
}
RippleButton {
id: windowButton
required property var modelData
@@ -182,7 +205,7 @@ Item {
contentWidth: parent.width - anchors.margins * 2
WrapperRectangle {
Layout.fillWidth: true
color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
color: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
radius: Appearance.rounding.small
margin: 5
StyledText {
@@ -195,7 +218,7 @@ Item {
}
GroupButton {
id: closeButton
colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer)
colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)
baseWidth: windowControlsHeight
baseHeight: windowControlsHeight
buttonRadius: Appearance.rounding.full
@@ -7,9 +7,10 @@ import QtQuick.Layouts
RippleButton {
Layout.fillHeight: true
Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut
implicitWidth: implicitHeight - topInset - bottomInset
buttonRadius: Appearance.rounding.normal
topInset: dockVisualBackground.margin + dockRow.padding
bottomInset: dockVisualBackground.margin + dockRow.padding
topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding
}
@@ -5,9 +5,9 @@ import QtQuick.Controls
import QtQuick.Layouts
Rectangle {
Layout.topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal
Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal
Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
@@ -26,6 +26,7 @@ Scope {
property real contentPadding: 13
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real artRounding: Appearance.rounding.verysmall
property list<real> visualizerPoints: []
property bool hasPlasmaIntegration: false
function isRealPlayer(player) {
@@ -53,7 +54,7 @@ Scope {
for (let j = i + 1; j < players.length; ++j) {
let p2 = players[j];
if (p1.trackTitle && p2.trackTitle &&
(p1.trackTitle.startsWith(p2.trackTitle) || p2.trackTitle.startsWith(p1.trackTitle))) {
(p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle))) {
group.push(j);
}
}
@@ -68,13 +69,31 @@ Scope {
return filtered;
}
Process {
id: cavaProc
running: mediaControlsLoader.active
onRunningChanged: {
if (!cavaProc.running) {
root.visualizerPoints = [];
}
}
command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`]
stdout: SplitParser {
onRead: data => {
// Parse `;`-separated values into the visualizerPoints array
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p));
root.visualizerPoints = points;
}
}
}
Loader {
id: mediaControlsLoader
active: false
sourceComponent: PanelWindow {
id: mediaControlsRoot
visible: mediaControlsLoader.active
visible: true
exclusiveZone: 0
implicitWidth: (
@@ -112,6 +131,7 @@ Scope {
delegate: PlayerControl {
required property MprisPlayer modelData
player: modelData
visualizerPoints: root.visualizerPoints
}
}
}
@@ -25,6 +25,9 @@ Item { // Player instance
property string artFilePath: `${artDownloadLocation}/${artFileName}`
property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer
property bool downloaded: false
property list<real> visualizerPoints: []
property real maxVisualizerValue: 1000 // Max value in the data points
property int visualizerSmoothing: 2 // Number of points to average for smoothing
implicitWidth: widgetWidth
implicitHeight: widgetHeight
@@ -150,6 +153,16 @@ Item { // Player instance
}
}
WaveVisualizer {
id: visualizerCanvas
anchors.fill: parent
live: playerController.player?.isPlaying
points: playerController.visualizerPoints
maxVisualizerValue: playerController.maxVisualizerValue
smoothing: playerController.visualizerSmoothing
color: blendedColors.colPrimary
}
RowLayout {
anchors.fill: parent
anchors.margins: root.contentPadding
@@ -160,7 +173,7 @@ Item { // Player instance
Layout.fillHeight: true
implicitWidth: height
radius: root.artRounding
color: blendedColors.colLayer1
color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
layer.enabled: true
layer.effect: OpacityMask {
@@ -235,12 +248,18 @@ Item { // Player instance
iconName: "skip_previous"
onClicked: playerController.player?.previous()
}
StyledProgressBar {
id: slider
Item {
id: progressBarContainer
Layout.fillWidth: true
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
implicitHeight: progressBar.implicitHeight
StyledProgressBar {
id: progressBar
anchors.fill: parent
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
}
TrackChangeButton {
iconName: "skip_next"
@@ -15,7 +15,7 @@ Scope {
PanelWindow {
id: root
visible: (Notifications.popupList.length > 0)
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null
WlrLayershell.namespace: "quickshell:notificationPopup"
WlrLayershell.layer: WlrLayer.Overlay
@@ -73,7 +73,7 @@ Scope {
item: osdValuesWrapper
}
implicitWidth: Appearance.sizes.osdWidth
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
Scope {
id: root
property bool showOsdValues: false
property string protectionMessage: ""
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
function triggerOsd() {
@@ -25,7 +26,8 @@ Scope {
repeat: false
running: false
onTriggered: {
showOsdValues = false
root.showOsdValues = false
root.protectionMessage = ""
}
}
@@ -36,7 +38,7 @@ Scope {
}
}
Connections {
Connections { // Listen to volume changes
target: Audio.sink?.audio ?? null
function onVolumeChanged() {
if (!Audio.ready) return
@@ -48,6 +50,14 @@ Scope {
}
}
Connections { // Listen to protection triggers
target: Audio
function onSinkProtectionTriggered(reason) {
root.protectionMessage = reason;
root.triggerOsd()
}
}
Loader {
id: osdLoader
active: showOsdValues
@@ -75,7 +85,7 @@ Scope {
item: osdValuesWrapper
}
implicitWidth: Appearance.sizes.osdWidth
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
@@ -85,8 +95,8 @@ Scope {
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth
implicitHeight: contentColumnLayout.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: contentColumnLayout.implicitWidth
clip: true
MouseArea {
@@ -95,20 +105,63 @@ Scope {
onEntered: root.showOsdValues = false
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
ColumnLayout {
id: contentColumnLayout
anchors {
top: parent.top
left: parent.left
right: parent.right
leftMargin: Appearance.sizes.elevationMargin
rightMargin: Appearance.sizes.elevationMargin
}
}
spacing: 0
OsdValueIndicator {
id: osdValues
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
OsdValueIndicator {
id: osdValues
Layout.fillWidth: true
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
}
Item {
id: protectionMessageWrapper
implicitHeight: protectionMessageBackground.implicitHeight
implicitWidth: protectionMessageBackground.implicitWidth
Layout.alignment: Qt.AlignHCenter
opacity: root.protectionMessage !== "" ? 1 : 0
StyledRectangularShadow {
target: protectionMessageBackground
}
Rectangle {
id: protectionMessageBackground
anchors.centerIn: parent
color: Appearance.m3colors.m3error
property real padding: 10
implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2
implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2
radius: Appearance.rounding.normal
RowLayout {
id: protectionMessageRowLayout
anchors.centerIn: parent
MaterialSymbol {
id: protectionMessageIcon
text: "dangerous"
iconSize: Appearance.font.pixelSize.hugeass
color: Appearance.m3colors.m3onError
}
StyledText {
id: protectionMessageTextWidget
horizontalAlignment: Text.AlignHCenter
color: Appearance.m3colors.m3onError
wrapMode: Text.Wrap
text: root.protectionMessage
}
}
}
}
}
}
}
@@ -111,7 +111,7 @@ Scope { // Scope
Layout.bottomMargin: 20
Layout.fillHeight: true
implicitWidth: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
OskContent {
id: oskContent
@@ -92,8 +92,8 @@ Scope {
visible: GlobalStates.overviewOpen
anchors {
horizontalCenter: parent.horizontalCenter
top: !ConfigOptions.bar.bottom ? parent.top : null
bottom: ConfigOptions.bar.bottom ? parent.bottom : null
top: !ConfigOptions.bar.bottom ? parent.top : undefined
bottom: ConfigOptions.bar.bottom ? parent.bottom : undefined
}
Keys.onPressed: (event) => {
@@ -25,7 +25,7 @@ Item {
property var windowAddresses: HyprlandData.addresses
property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id)
property real scale: ConfigOptions.overview.scale
property color activeBorderColor: Appearance.m3colors.m3secondary
property color activeBorderColor: Appearance.colors.colSecondary
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :
@@ -144,25 +144,38 @@ Item {
implicitHeight: workspaceColumnLayout.implicitHeight
Repeater { // Window repeater
model: windowAddresses.filter((address) => {
var win = windowByAddress[address]
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
})
model: ScriptModel {
values: {
// console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))
return ToplevelManager.toplevels.values.filter((toplevel) => {
const address = `0x${toplevel.HyprlandToplevel.address}`
// console.log(`Checking window with address: ${address}`)
var win = windowByAddress[address]
return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
})
}
}
delegate: OverviewWindow {
id: window
windowData: windowByAddress[modelData]
required property var modelData
property var address: `0x${modelData.HyprlandToplevel.address}`
windowData: windowByAddress[address]
toplevel: modelData
monitorData: root.monitorData
scale: root.scale
availableWorkspaceWidth: root.workspaceImplicitWidth
availableWorkspaceHeight: root.workspaceImplicitHeight
property int monitorId: windowData?.monitor
property var monitor: HyprlandData.monitors[monitorId]
property bool atInitPosition: (initX == x && initY == y)
restrictToWorkspace: Drag.active || atInitPosition
property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols)
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale)
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale)
Timer {
id: updateWindowPosition
@@ -170,8 +183,9 @@ Item {
repeat: false
running: false
onTriggered: {
window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset
window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset
window.x = Math.round(Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset)
window.y = Math.round(Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset)
console.log(`[OverviewWindow] Updated position for window ${windowData?.address} to (${window.x}, ${window.y})`)
}
}
@@ -191,6 +205,7 @@ Item {
window.pressed = true
window.Drag.active = true
window.Drag.source = window
console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`)
}
onReleased: {
const targetWorkspace = root.draggingTargetWorkspace
@@ -1,17 +1,21 @@
import "root:/"
import "root:/services/"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
Rectangle { // Window
Item { // Window
id: root
property var toplevel
property var windowData
property var monitorData
property var scale
@@ -38,14 +42,17 @@ Rectangle { // Window
x: initX
y: initY
width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset))
height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset))
width: Math.round(Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)))
height: Math.round(Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)))
radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9)
border.pixelAligned : false
border.width : 1
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: root.width
height: root.height
radius: Appearance.rounding.windowRounding * root.scale
}
}
Behavior on x {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
@@ -60,34 +67,45 @@ Rectangle { // Window
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
ScreencopyView {
id: windowPreview
anchors.fill: parent
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
live: true
IconImage {
id: windowIcon
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
Behavior on implicitSize {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Rectangle {
anchors.fill: parent
radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
ColorUtils.transparentize(Appearance.colors.colLayer2)
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
border.width : 1
}
StyledText {
Layout.leftMargin: 10
Layout.rightMargin: 10
visible: !compactMode
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Appearance.font.pixelSize.smaller
font.italic: indicateXWayland ? true : false
elide: Text.ElideRight
text: windowData?.title ?? ""
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
Image {
id: windowIcon
property var iconSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
// mipmap: true
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(iconSize, iconSize)
Behavior on width {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on height {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
}
}
}
@@ -80,7 +80,7 @@ RippleButton {
buttonRadius: Appearance.rounding.normal
colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active :
((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh, 1))
colBackgroundHover: Appearance.colors.colLayer1Hover
colRipple: Appearance.colors.colLayer1Active
@@ -219,7 +219,7 @@ Item { // Wrapper
}
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Search, calculate or run")
placeholderTextColor: Appearance.m3colors.m3outline
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
@@ -260,7 +260,7 @@ Item { // Wrapper
visible: root.showResults
Layout.fillWidth: true
height: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
ListView { // App results
@@ -5,6 +5,7 @@ import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: screenCorners
@@ -14,7 +15,9 @@ Scope {
model: Quickshell.screens
PanelWindow {
visible: (ConfigOptions.appearance.fakeScreenRounding === 1 || (ConfigOptions.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen))
visible: (ConfigOptions.appearance.fakeScreenRounding === 1
|| (ConfigOptions.appearance.fakeScreenRounding === 2
&& !activeWindow?.fullscreen))
property var modelData
@@ -23,6 +26,20 @@ Scope {
mask: Region {
item: null
}
HyprlandWindow.visibleMask: Region {
Region {
item: topLeftCorner
}
Region {
item: topRightCorner
}
Region {
item: bottomLeftCorner
}
Region {
item: bottomRightCorner
}
}
WlrLayershell.namespace: "quickshell:screenCorners"
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
@@ -35,24 +52,28 @@ Scope {
}
RoundCorner {
id: topLeftCorner
anchors.top: parent.top
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.topLeft
}
RoundCorner {
id: topRightCorner
anchors.top: parent.top
anchors.right: parent.right
size: Appearance.rounding.screenRounding
corner: cornerEnum.topRight
}
RoundCorner {
id: bottomLeftCorner
anchors.bottom: parent.bottom
anchors.left: parent.left
size: Appearance.rounding.screenRounding
corner: cornerEnum.bottomLeft
}
RoundCorner {
id: bottomRightCorner
anchors.bottom: parent.bottom
anchors.right: parent.right
size: Appearance.rounding.screenRounding
@@ -18,7 +18,7 @@ RippleButton {
buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge
colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive :
button.focus ? Appearance.colors.colPrimary :
Appearance.m3colors.m3secondaryContainer
Appearance.colors.colSecondaryContainer
colBackgroundHover: Appearance.colors.colPrimary
colRipple: Appearance.colors.colPrimaryActive
property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ?
@@ -125,9 +125,14 @@ int main(int argc, char* argv[]) {
### LaTeX
- Simple inline: $\\frac{1}{2} = \\frac{2}{4}$
- Complex inline: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
- Another complex inline: \\\\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\\\]
Inline w/ dollar signs: $\\frac{1}{2} = \\frac{2}{4}$
Inline w/ double dollar signs: $$\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$$
Inline w/ backslash and square brackets \\[\\int_0^\\infty \\frac{1}{x^2} dx = \\infty\\]
Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\)
`,
Ai.interfaceRole);
}
@@ -162,6 +167,7 @@ int main(int argc, char* argv[]) {
id: messageListView
anchors.fill: parent
spacing: 10
popin: false
property int lastResponseLength: 0
@@ -175,6 +181,8 @@ int main(int argc, char* argv[]) {
}
}
add: null // Prevent function calls from being janky
Behavior on contentY {
NumberAnimation {
id: scrollAnim
@@ -337,7 +345,7 @@ int main(int argc, char* argv[]) {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true
border.color: Appearance.m3colors.m3outlineVariant
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
@@ -359,7 +359,7 @@ Item {
implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin
+ commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)
clip: true
border.color: Appearance.m3colors.m3outlineVariant
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Behavior on implicitHeight {
@@ -562,8 +562,10 @@ Item {
text: "•"
}
Rectangle { // NSFW toggle
Item { // NSFW toggle
visible: width > 0
implicitWidth: switchesRow.implicitWidth
Layout.fillHeight: true
RowLayout {
id: switchesRow
@@ -97,7 +97,7 @@ Scope { // Scope
anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1
@@ -3,6 +3,7 @@ import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "./translator/"
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
@@ -15,11 +16,24 @@ import Quickshell.Hyprland
*/
Item {
id: root
property var inputField: inputTextArea
property var outputField: outputTextArea
// Widgets
property var inputField: inputCanvas.inputTextArea
// Widget variables
property bool translationFor: false // Indicates if the translation is for an autocorrected text
property string translatedText: ""
property list<string> languages: []
// Options
property string targetLanguage: ConfigOptions.language.translator.targetLanguage
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage
property string hostLanguage: targetLanguage
property bool showLanguageSelector: false
property bool languageSelectorTarget: false // true for target language, false for source language
function showLanguageSelectorDialog(isTargetLang: bool) {
root.languageSelectorTarget = isTargetLang;
root.showLanguageSelector = true
}
onFocusChanged: (focus) => {
if (focus) {
@@ -32,19 +46,23 @@ Item {
interval: ConfigOptions.sidebar.translator.delay
repeat: false
onTriggered: () => {
if (inputTextArea.text.trim().length > 0) {
if (root.inputField.text.trim().length > 0) {
// console.log("Translating with command:", translateProc.command);
translateProc.running = false;
translateProc.buffer = ""; // Clear the buffer
translateProc.running = true; // Restart the process
} else {
outputTextArea.text = "";
root.translatedText = "";
}
}
}
Process {
id: translateProc
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
command: ["bash", "-c", `trans -no-theme`
+ ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'`
+ ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'`
+ ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`]
property string buffer: ""
stdout: SplitParser {
onRead: data => {
@@ -59,171 +77,172 @@ Item {
// 2. Extract relevant data
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
root.outputField.text = root.translatedText;
}
}
Flickable {
Process {
id: getLanguagesProc
command: ["trans", "-list-languages"]
property list<string> bufferList: ["auto"]
running: true
stdout: SplitParser {
onRead: data => {
getLanguagesProc.bufferList.push(data.trim());
}
}
onExited: (exitCode, exitStatus) => {
// Ensure "auto" is always the first language
let langs = getLanguagesProc.bufferList
.filter(lang => lang.trim().length > 0 && lang !== "auto")
.sort((a, b) => a.localeCompare(b));
langs.unshift("auto");
root.languages = langs;
getLanguagesProc.bufferList = []; // Clear the buffer
}
}
ColumnLayout {
anchors.fill: parent
contentHeight: contentColumn.implicitHeight
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
contentHeight: contentColumn.implicitHeight
ColumnLayout {
id: contentColumn
anchors.fill: parent
ColumnLayout {
id: contentColumn
anchors.fill: parent
Rectangle { // INPUT
id: inputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: Appearance.colors.colLayer1
radius: Appearance.rounding.normal
border.color: Appearance.m3colors.m3outlineVariant
border.width: 1
LanguageSelectorButton { // Target language button
id: targetLanguageButton
displayText: root.targetLanguage
onClicked: {
root.showLanguageSelectorDialog(true);
}
}
ColumnLayout {
id: inputColumn
anchors.fill: parent
spacing: 0
StyledTextArea { // Input area
id: inputTextArea
Layout.fillWidth: true
placeholderText: qsTr("Enter text to translate...")
wrapMode: TextEdit.Wrap
textFormat: TextEdit.PlainText
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
padding: 15
background: null
onTextChanged: {
if (inputTextArea.text.trim().length > 0) {
translateTimer.restart();
} else {
outputTextArea.text = "";
}
TextCanvas { // Content translation
id: outputCanvas
isInput: false
placeholderText: qsTr("Translation goes here...")
property bool hasTranslation: (root.translatedText.trim().length > 0)
text: hasTranslation ? root.translatedText : ""
GroupButton {
id: copyButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: outputCanvas.displayedText.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_copy"
color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
Quickshell.clipboardText = outputCanvas.displayedText
}
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Text {
Layout.leftMargin: 10
text: qsTr("%1 characters").arg(inputTextArea.text.length)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
GroupButton {
id: searchButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: outputCanvas.displayedText.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "travel_explore"
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: pasteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_paste"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = Quickshell.clipboardText
}
}
GroupButton {
id: deleteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: inputTextArea.text.length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "close"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = ""
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
}
}
}
}
}
LanguageSelectorButton { // Source language button
id: sourceLanguageButton
displayText: root.sourceLanguage
onClicked: {
root.showLanguageSelectorDialog(false);
}
}
Rectangle { // OUTPUT
id: outputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, outputColumn.implicitHeight)
color: Appearance.m3colors.m3surfaceContainer
radius: Appearance.rounding.normal
ColumnLayout { // Output column
id: outputColumn
anchors.fill: parent
spacing: 0
StyledText { // Output area
id: outputTextArea
Layout.fillWidth: true
property bool hasTranslation: (root.translatedText.trim().length > 0)
wrapMode: TextEdit.Wrap
font.pixelSize: Appearance.font.pixelSize.small
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
padding: 15
text: hasTranslation ? root.translatedText : ""
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: copyButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_copy"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
Quickshell.clipboardText = root.outputField.text
}
}
GroupButton {
id: searchButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "travel_explore"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + root.outputField.text;
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
}
}
}
}
TextCanvas { // Content input
id: inputCanvas
isInput: true
placeholderText: qsTr("Enter text to translate...")
onInputTextChanged: {
translateTimer.restart();
}
GroupButton {
id: pasteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_paste"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = Quickshell.clipboardText
}
}
}
GroupButton {
id: deleteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: inputCanvas.inputTextArea.text.length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "close"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = ""
}
}
}
}
Loader {
anchors.fill: parent
active: root.showLanguageSelector
visible: root.showLanguageSelector
z: 9999
sourceComponent: SelectionDialog {
id: languageSelectorDialog
titleText: qsTr("Select Language")
items: root.languages
defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage
onCanceled: () => {
root.showLanguageSelector = false;
}
onSelected: (result) => {
root.showLanguageSelector = false;
if (!result || result.length === 0) return; // No selection made
if (root.languageSelectorTarget) {
root.targetLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.targetLanguage", result); // Save to config
} else {
root.sourceLanguage = result;
ConfigLoader.setConfigValueAndSave("language.translator.sourceLanguage", result); // Save to config
}
translateTimer.restart(); // Restart translation after language change
}
}
}
}
@@ -28,6 +28,8 @@ Rectangle {
property bool renderMarkdown: true
property bool editing: false
property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2
@@ -89,7 +91,7 @@ Rectangle {
Rectangle { // Name
id: nameWrapper
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
// color: "transparent"
radius: Appearance.rounding.small
implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)
@@ -246,23 +248,25 @@ Rectangle {
spacing: 0
Repeater {
model: ScriptModel {
values: StringUtils.splitMarkdownBlocks(root.messageData?.content)
values: root.messageBlocks.map((block, index) => index)
}
delegate: Loader {
required property int index
property var thisBlock: root.messageBlocks[index]
Layout.fillWidth: true
// property var segment: modelData
property var segmentContent: modelData.content
property var segmentLang: modelData.lang
// property var segment: thisBlock
property var segmentContent: thisBlock.content
property var segmentLang: thisBlock.lang
property var messageData: root.messageData
property var editing: root.editing
property var renderMarkdown: root.renderMarkdown
property var enableMouseSelection: root.enableMouseSelection
property bool thinking: root.messageData?.thinking ?? true
property bool done: root.messageData?.done ?? false
property bool completed: modelData.completed ?? false
property bool completed: thisBlock.completed ?? false
source: modelData.type === "code" ? "MessageCodeBlock.qml" :
modelData.type === "think" ? "MessageThinkBlock.qml" :
source: thisBlock.type === "code" ? "MessageCodeBlock.qml" :
thisBlock.type === "think" ? "MessageThinkBlock.qml" :
"MessageTextBlock.qml"
}
@@ -277,7 +281,9 @@ Rectangle {
Layout.alignment: Qt.AlignLeft
Repeater {
model: root.messageData?.annotationSources
model: ScriptModel {
values: root.messageData?.annotationSources || []
}
delegate: AnnotationSourceButton {
id: annotationButton
displayText: modelData.text
@@ -21,7 +21,7 @@ RippleButton {
leftPadding: (implicitHeight - faviconSize) / 2
rightPadding: 10
buttonRadius: Appearance.rounding.full
colBackground: Appearance.m3colors.m3surfaceContainerHighest
colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive
@@ -40,7 +40,7 @@ ColumnLayout {
topRightRadius: codeBlockBackgroundRounding
bottomLeftRadius: Appearance.rounding.unsharpen
bottomRightRadius: Appearance.rounding.unsharpen
color: Appearance.m3colors.m3surfaceContainerHighest
color: Appearance.colors.colSurfaceContainerHighest
implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2
RowLayout { // Language and buttons
@@ -97,7 +97,7 @@ ColumnLayout {
onClicked: {
const downloadPath = FileUtils.trimFileProtocol(Directories.downloads)
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || "txt"}'`)
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}'`)
Hyprland.dispatch(`exec notify-send 'Code saved to file' '${downloadPath}/code.${segmentLang || "txt"}' -a Shell`)
saveCodeButton.activated = true
saveIconTimer.restart()
}
@@ -209,7 +209,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
// wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
@@ -30,20 +30,32 @@ ColumnLayout {
Layout.fillWidth: true
Timer {
id: renderTimer
interval: 1000
repeat: false
onTriggered: {
renderLatex()
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
}
}
function renderLatex() {
// Regex for $...$, $$...$$, \[...\]
// Note: This is a simple approach and may need refinement for edge cases
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])/g;
let regex = /(\$\$([\s\S]+?)\$\$)|(\$([^\$]+?)\$)|(\\\[((?:.|\n)+?)\\\])|(\\\(([\s\S]+?)\\\))/g;
let match;
while ((match = regex.exec(segmentContent)) !== null) {
let expression = match[1] || match[2] || match[3];
let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8];
if (expression) {
// Qt.callLater(() => {
// });
Qt.callLater(() => {
const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());
if (!renderedLatexHashes.includes(renderHash)) {
renderedLatexHashes.push(renderHash);
}
});
}
}
}
@@ -53,16 +65,13 @@ ColumnLayout {
const imagePath = LatexRenderer.renderedImagePaths[hash];
const markdownImage = `![latex](${imagePath})`;
const expression = StringUtils.escapeBackslashes(LatexRenderer.processedExpressions[hash]);
const expression = LatexRenderer.processedExpressions[hash];
renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);
}
}
onDoneChanged: {
renderLatex()
for (const hash of renderedLatexHashes) {
handleRenderedLatex(hash, true);
}
renderTimer.restart();
}
onEditingChanged: {
if (!editing) {
@@ -111,7 +120,7 @@ ColumnLayout {
font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text
font.pixelSize: Appearance.font.pixelSize.small
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
wrapMode: TextEdit.Wrap
color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1
textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText
@@ -64,7 +64,7 @@ Item {
Rectangle { // Header background
id: header
color: Appearance.m3colors.m3surfaceContainerHighest
color: Appearance.colors.colSurfaceContainerHighest
Layout.fillWidth: true
implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2
@@ -133,7 +133,7 @@ Button {
opacity: root.showActions ? 1 : 0
visible: opacity > 0
radius: Appearance.rounding.small
color: Appearance.m3colors.m3surfaceContainer
color: Appearance.colors.colSurfaceContainer
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth
@@ -67,7 +67,7 @@ Rectangle {
RowLayout { // Header
Rectangle { // Provider name
id: providerNameWrapper
color: Appearance.m3colors.m3secondaryContainer
color: Appearance.colors.colSecondaryContainer
radius: Appearance.rounding.small
implicitWidth: providerName.implicitWidth + 10 * 2
implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)
@@ -269,7 +269,7 @@ Rectangle {
}
buttonRadius: Appearance.rounding.small
colBackground: Appearance.m3colors.m3surfaceContainerHighest
colBackground: Appearance.colors.colSurfaceContainerHighest
colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover
colRipple: Appearance.colors.colSurfaceContainerHighestActive
@@ -0,0 +1,45 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
RippleButton {
id: root
property string displayText: ""
colBackground: Appearance.colors.colLayer2
implicitWidth: contentItem.implicitWidth + horizontalPadding * 2
implicitHeight: contentItem.implicitHeight + verticalPadding * 2
contentItem: Item {
anchors.centerIn: parent
implicitWidth: languageRow.implicitWidth
implicitHeight: languageText.implicitHeight
RowLayout {
id: languageRow
anchors.centerIn: parent
spacing: 0
StyledText {
id: languageText
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 5
text: root.displayText
color: Appearance.colors.colOnLayer2
font.pixelSize: Appearance.font.pixelSize.small
}
MaterialSymbol {
Layout.alignment: Qt.AlignVCenter
iconSize: Appearance.font.pixelSize.hugeass
text: "arrow_drop_down"
color: Appearance.colors.colOnLayer2
}
}
}
}
@@ -0,0 +1,92 @@
import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
Rectangle {
id: root
property bool isInput: true // true for input, false for output
property string placeholderText
property string text: ""
property var inputTextArea: isInput ? inputLoader.item : undefined
readonly property string displayedText: isInput ? inputLoader.item.text :
root.text.length > 0 ? outputLoader.item.text : ""
default property alias actionButtons: actions.data
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: isInput ? Appearance.colors.colLayer1 : Appearance.colors.colSurfaceContainer
radius: Appearance.rounding.normal
border.color: isInput ? Appearance.colors.colOutlineVariant : "transparent"
border.width: isInput ? 1 : 0
signal inputTextChanged(); // Signal emitted when text changes
ColumnLayout {
id: inputColumn
anchors.fill: parent
spacing: 0
Loader {
id: inputLoader
active: root.isInput
visible: root.isInput
Layout.fillWidth: true
sourceComponent: StyledTextArea { // Input area
id: inputTextArea
placeholderText: root.placeholderText
wrapMode: TextEdit.Wrap
textFormat: TextEdit.PlainText
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
padding: 15
background: null
onTextChanged: root.inputTextChanged()
}
}
Loader {
id: outputLoader
active: !root.isInput
visible: !root.isInput
Layout.fillWidth: true
sourceComponent: StyledText { // Output area
id: outputTextArea
padding: 15
wrapMode: Text.Wrap
font.pixelSize: Appearance.font.pixelSize.small
color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
text: root.text.length > 0 ? root.text : root.placeholderText
}
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Loader {
active: root.isInput
visible: root.isInput
Layout.leftMargin: 10
sourceComponent: Text {
text: qsTr("%1 characters").arg(inputLoader.item.text.length)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
}
}
Item { Layout.fillWidth: true }
ButtonGroup {
id: actions
}
}
}
}
@@ -52,8 +52,17 @@ Scope {
Loader {
id: sidebarContentLoader
active: GlobalStates.sidebarRightOpen
anchors.centerIn: parent
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
anchors {
top: parent.top
bottom: parent.bottom
right: parent.right
left: parent.left
topMargin: Appearance.sizes.hyprlandGapsOut
rightMargin: Appearance.sizes.hyprlandGapsOut
bottomMargin: Appearance.sizes.hyprlandGapsOut
leftMargin: Appearance.sizes.elevationMargin
}
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
sourceComponent: Item {
@@ -118,14 +127,38 @@ Scope {
Layout.fillWidth: true
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
ButtonGroup {
QuickToggleButton {
toggled: false
buttonIcon: "restart_alt"
onClicked: {
Hyprland.dispatch("reload")
Quickshell.reload(true)
}
StyledToolTip {
content: qsTr("Reload Hyprland & Quickshell")
}
}
StyledToolTip {
content: qsTr("Session")
QuickToggleButton {
toggled: false
buttonIcon: "settings"
onClicked: {
Hyprland.dispatch(`exec ${ConfigOptions.apps.settings}`)
Hyprland.dispatch(`global quickshell:sidebarRightClose`)
}
StyledToolTip {
content: qsTr("Plasma Settings")
}
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
}
StyledToolTip {
content: qsTr("Session")
}
}
}
}
@@ -26,7 +26,7 @@ RippleButton {
font.weight: bold ? Font.DemiBold : Font.Normal
color: (isToday == 1) ? Appearance.m3colors.m3onPrimary :
(isToday == 0) ? Appearance.colors.colOnLayer1 :
Appearance.m3colors.m3outlineVariant
Appearance.colors.colOutlineVariant
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
@@ -3,16 +3,28 @@ import "root:/modules/common/widgets"
import "../"
import Quickshell.Io
import Quickshell
import Quickshell.Hyprland
QuickToggleButton {
toggled: idleInhibitor.running
id: root
toggled: false
buttonIcon: "coffee"
onClicked: {
idleInhibitor.running = !idleInhibitor.running
if (toggled) {
root.toggled = false
Hyprland.dispatch("exec pkill wayland-idle") // pkill doesn't accept too long names
} else {
root.toggled = true
Hyprland.dispatch('exec ${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py')
}
}
Process {
id: idleInhibitor
command: ["bash", "-c", "${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/scripts/wayland-idle-inhibitor.py"]
id: fetchActiveState
running: true
command: ["bash", "-c", "pidof wayland-idle-inhibitor.py"]
onExited: (exitCode, exitStatus) => {
root.toggled = exitCode === 0
}
}
StyledToolTip {
content: qsTr("Keep system awake")
@@ -15,7 +15,7 @@ QuickToggleButton {
toggleNetwork.running = true
}
altAction: () => {
Hyprland.dispatch(`exec ${ConfigOptions.apps.network}`)
Hyprland.dispatch(`exec ${Network.ethernet ? ConfigOptions.apps.networkEthernet : ConfigOptions.apps.network}`)
Hyprland.dispatch("global quickshell:sidebarRightClose")
}
Process {
@@ -114,7 +114,7 @@ Item {
id: tabBarBottomBorder
Layout.fillWidth: true
height: 1
color: Appearance.m3colors.m3outlineVariant
color: Appearance.colors.colOutlineVariant
}
SwipeView {
@@ -228,7 +228,7 @@ Item {
anchors.margins: root.dialogMargins
implicitHeight: dialogColumnLayout.implicitHeight
color: Appearance.m3colors.m3surfaceContainerHigh
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
function addTask() {
@@ -264,7 +264,7 @@ Item {
color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant
renderType: Text.NativeRendering
selectedTextColor: Appearance.m3colors.m3onSecondaryContainer
selectionColor: Appearance.m3colors.m3secondaryContainer
selectionColor: Appearance.colors.colSecondaryContainer
placeholderText: qsTr("Task description")
placeholderTextColor: Appearance.m3colors.m3outline
focus: root.showAddDialog
@@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Pipewire
@@ -16,7 +17,7 @@ Item {
property int dialogMargins: 16
property PwNode selectedDevice
function showDeviceSelectorDialog(input) {
function showDeviceSelectorDialog(input: bool) {
root.selectedDevice = null
root.showDeviceSelector = true
root.deviceSelectorInput = input
@@ -160,7 +161,7 @@ Item {
Rectangle { // The dialog
id: dialog
color: Appearance.m3colors.m3surfaceContainerHigh
color: Appearance.colors.colSurfaceContainerHigh
radius: Appearance.rounding.normal
anchors.left: parent.left
anchors.right: parent.right
@@ -207,9 +208,11 @@ Item {
spacing: 0
Repeater {
model: Pipewire.nodes.values.filter(node => {
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
})
model: ScriptModel {
values: Pipewire.nodes.values.filter(node => {
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
})
}
// This could and should be refractored, but all data becomes null when passed wtf
delegate: StyledRadioButton {
@@ -0,0 +1,17 @@
[general]
mode = waves
framerate = 60
autosens = 1
bars = 50
[output]
method = raw
raw_target = /dev/stdout
data_format = ascii
channels = mono
mono_option = average
[smoothing]
noise_reduction = 20
+20 -4
View File
@@ -7,6 +7,7 @@ CONFIG_DIR="$XDG_CONFIG_HOME/quickshell"
CACHE_DIR="$XDG_CACHE_HOME/quickshell"
STATE_DIR="$XDG_STATE_HOME/quickshell"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATUGEN_DIR="$XDG_CONFIG_HOME/matugen"
terminalscheme="$XDG_CONFIG_HOME/quickshell/scripts/terminal/scheme-base.json"
pre_process() {
@@ -26,7 +27,19 @@ pre_process() {
}
post_process() {
true
local screen_width="$1"
local screen_height="$2"
local wallpaper_path="$3"
# Determine the largest region on the wallpaper that's sufficiently un-busy to put widgets in
if [ ! -f "$MATUGEN_DIR/scripts/least_busy_region.py" ]; then
echo "Error: least_busy_region.py script not found in $MATUGEN_DIR/scripts/"
else
"$MATUGEN_DIR/scripts/least_busy_region.py" \
--screen-width "$screen_width" --screen-height "$screen_height" \
--width 300 --height 200 \
"$wallpaper_path" > "$STATE_DIR"/user/generated/wallpaper/least_busy_region.json
fi
}
check_and_prompt_upscale() {
@@ -219,7 +232,10 @@ switch() {
"$SCRIPT_DIR"/applycolor.sh
deactivate
post_process
# Pass screen width, height, and wallpaper path to post_process
max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)"
max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)"
post_process "$max_width_desired" "$max_height_desired" "$imgpath"
}
main() {
@@ -267,10 +283,10 @@ main() {
# Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set
if [[ -z "$imgpath" && -z "$color_flag" && -z "$noswitch_flag" ]]; then
cd "$(xdg-user-dir PICTURES)/Wallpapers" 2>/dev/null || cd "$(xdg-user-dir PICTURES)" || return 1
imgpath="$(yad --width 1200 --height 800 --file --add-preview --large-preview --title='Choose wallpaper')"
imgpath="$(kdialog --getopenfilename . --title 'Choose wallpaper')"
fi
switch "$imgpath" "$mode_flag" "$type_flag" "$color_flag" "$color"
}
main "$@"
main "$@"
+2 -3
View File
@@ -430,8 +430,7 @@ Singleton {
"parts": [{ text: root.systemPrompt }]
},
"generationConfig": {
"temperature": root.temperature,
"responseMimeType": "text/plain",
// "temperature": root.temperature,
},
};
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
@@ -450,7 +449,7 @@ Singleton {
}),
],
"stream": true,
"temperature": root.temperature,
// "temperature": root.temperature,
};
return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;
}
+32 -2
View File
@@ -1,3 +1,4 @@
import "root:/modules/common"
import QtQuick
import Quickshell
import Quickshell.Services.Pipewire
@@ -11,11 +12,40 @@ Singleton {
id: root
property bool ready: Pipewire.defaultAudioSink?.ready ?? false
property var sink: Pipewire.defaultAudioSink
property var source: Pipewire.defaultAudioSource
property PwNode sink: Pipewire.defaultAudioSink
property PwNode source: Pipewire.defaultAudioSource
signal sinkProtectionTriggered(string reason);
PwObjectTracker {
objects: [sink, source]
}
Connections { // Protection against sudden volume changes
target: sink?.audio ?? null
property bool lastReady: false
property real lastVolume: 0
function onVolumeChanged() {
if (!ConfigOptions.audio.protection.enable) return;
if (!lastReady) {
lastVolume = sink.audio.volume;
lastReady = true;
return;
}
const newVolume = sink.audio.volume;
const maxAllowedIncrease = ConfigOptions.audio.protection.maxAllowedIncrease / 100;
const maxAllowed = ConfigOptions.audio.protection.maxAllowed / 100;
if (newVolume - lastVolume > maxAllowedIncrease) {
sink.audio.volume = lastVolume;
root.sinkProtectionTriggered("Illegal increment");
} else if (newVolume > maxAllowed) {
sink.audio.volume = lastVolume;
root.sinkProtectionTriggered("Exceeded max allowed");
}
lastVolume = sink.audio.volume;
}
}
}
+31 -9
View File
@@ -14,12 +14,14 @@ import Qt.labs.platform
/**
* Loads and manages the shell configuration file.
* The config file is by default at XDG_CONFIG_HOME/illogical-impulse/config.json.
* Automatically reloaded when the file changes, but does not provide a way to save changes.
* Automatically reloaded when the file changes.
*/
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property bool firstLoad: true
property bool preventNextLoad: false
property var preventNextNotification: false
function loadConfig() {
configFileView.reload()
@@ -27,16 +29,21 @@ Singleton {
function applyConfig(fileContent) {
try {
if (fileContent.trim() === "") {
console.warn("[ConfigLoader] Config file is empty, skipping load.");
return;
}
const json = JSON.parse(fileContent);
ObjectUtils.applyToQtObject(ConfigOptions, json);
if (root.firstLoad) {
root.firstLoad = false;
} else {
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
root.preventNextLoad = true;
root.saveConfig(); // Make sure new properties are added to the user's config file
}
} catch (e) {
console.error("[ConfigLoader] Error reading file:", e);
console.log("[ConfigLoader] File content was:", fileContent);
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
return;
@@ -70,8 +77,6 @@ Singleton {
}
}
console.log(parents.join("."));
console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`);
obj[keys[keys.length - 1]] = convertedValue;
}
@@ -80,13 +85,31 @@ Singleton {
Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`)
}
function setConfigValueAndSave(nestedKey, value, preventNextNotification = true) {
setLiveConfigValue(nestedKey, value);
root.preventNextNotification = preventNextNotification;
saveConfig();
}
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
running: false
onTriggered: {
root.applyConfig(configFileView.text())
if (root.preventNextLoad) {
root.preventNextLoad = false;
return;
}
if (root.firstLoad) {
root.applyConfig(configFileView.text())
} else {
root.applyConfig(configFileView.text())
if (!root.preventNextNotification) {
// Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
} else {
root.preventNextNotification = false;
}
}
}
}
@@ -95,13 +118,12 @@ Singleton {
path: Qt.resolvedUrl(root.filePath)
watchChanges: true
onFileChanged: {
console.log("[ConfigLoader] File changed, reloading...")
this.reload()
delayedFileRead.start()
}
onLoadedChanged: {
const fileContent = configFileView.text()
root.applyConfig(fileContent)
delayedFileRead.start()
}
onLoadFailed: (error) => {
if(error == FileViewError.FileNotFound) {
+15 -9
View File
@@ -25,7 +25,8 @@ Singleton {
property list<string> processedHashes: []
property var processedExpressions: ({})
property var renderedImagePaths: ({})
property string microtexBinaryPath: Qt.resolvedUrl("/opt/MicroTeX/LaTeX")
property string microtexBinaryDir: "/opt/MicroTeX"
property string microtexBinaryName: "LaTeX"
property string latexOutputPath: Directories.latexOutput
signal renderFinished(string hash, string imagePath)
@@ -51,23 +52,28 @@ Singleton {
}
// 3. If not, render it with MicroTeX and mark as processed
// console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`)
// console.log(` to file: ${imagePath}`)
// console.log(` with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`)
const processQml = `
import Quickshell.Io
Process {
id: microtexProcess${hash}
running: true
command: [ "${microtexBinaryPath}", "-headless",
"-input=${StringUtils.escapeBackslashes(expression)}",
"-output=${imagePath}",
"-textsize=${Appearance.font.pixelSize.normal}",
"-padding=${renderPadding}",
"-background=${Appearance.m3colors.m3tertiary}",
"-foreground=${Appearance.m3colors.m3onTertiary}",
"-maxwidth=0.85" ]
command: [ "bash", "-c",
"cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' "
+ "'-output=${imagePath}' "
+ "'-textsize=${Appearance.font.pixelSize.normal}' "
+ "'-padding=${renderPadding}' "
// + "'-background=${Appearance.m3colors.m3tertiary}' "
+ "'-foreground=${Appearance.colors.colOnLayer1}' "
+ "-maxwidth=0.85 "
]
// stdout: SplitParser {
// onRead: data => { console.log("MicroTeX: " + data) }
// }
onExited: (exitCode, exitStatus) => {
// console.log("[LatexRenderer] MicroTeX process exited with code: " + exitCode + ", status: " + exitStatus)
renderedImagePaths["${hash}"] = "${imagePath}"
root.renderFinished("${hash}", "${imagePath}")
microtexProcess${hash}.destroy()
+17 -14
View File
@@ -3,6 +3,7 @@
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
import "./modules/common/"
import "./modules/backgroundWidgets/"
import "./modules/bar/"
import "./modules/cheatsheet/"
import "./modules/dock/"
@@ -26,6 +27,7 @@ ShellRoot {
// Enable/disable modules here. False = not loaded at all, so rest assured
// no unnecessary stuff will take up memory if you decide to only use, say, the overview.
property bool enableBar: true
property bool enableBackgroundWidgets: true
property bool enableCheatsheet: true
property bool enableDock: false
property bool enableMediaControls: true
@@ -49,19 +51,20 @@ ShellRoot {
FirstRunExperience.load()
}
Loader { active: enableBar; sourceComponent: Bar {} }
Loader { active: enableCheatsheet; sourceComponent: Cheatsheet {} }
Loader { active: (enableDock || ConfigOptions?.dock.enable); sourceComponent: Dock {} }
Loader { active: enableMediaControls; sourceComponent: MediaControls {} }
Loader { active: enableNotificationPopup; sourceComponent: NotificationPopup {} }
Loader { active: enableOnScreenDisplayBrightness; sourceComponent: OnScreenDisplayBrightness {} }
Loader { active: enableOnScreenDisplayVolume; sourceComponent: OnScreenDisplayVolume {} }
Loader { active: enableOnScreenKeyboard; sourceComponent: OnScreenKeyboard {} }
Loader { active: enableOverview; sourceComponent: Overview {} }
Loader { active: enableReloadPopup; sourceComponent: ReloadPopup {} }
Loader { active: enableScreenCorners; sourceComponent: ScreenCorners {} }
Loader { active: enableSession; sourceComponent: Session {} }
Loader { active: enableSidebarLeft; sourceComponent: SidebarLeft {} }
Loader { active: enableSidebarRight; sourceComponent: SidebarRight {} }
LazyLoader { active: enableBar; component: Bar {} }
LazyLoader { active: enableBackgroundWidgets; component: BackgroundWidgets {} }
LazyLoader { active: enableCheatsheet; component: Cheatsheet {} }
LazyLoader { active: enableDock; component: Dock {} }
LazyLoader { active: enableMediaControls; component: MediaControls {} }
LazyLoader { active: enableNotificationPopup; component: NotificationPopup {} }
LazyLoader { active: enableOnScreenDisplayBrightness; component: OnScreenDisplayBrightness {} }
LazyLoader { active: enableOnScreenDisplayVolume; component: OnScreenDisplayVolume {} }
LazyLoader { active: enableOnScreenKeyboard; component: OnScreenKeyboard {} }
LazyLoader { active: enableOverview; component: Overview {} }
LazyLoader { active: enableReloadPopup; component: ReloadPopup {} }
LazyLoader { active: enableScreenCorners; component: ScreenCorners {} }
LazyLoader { active: enableSession; component: Session {} }
LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} }
LazyLoader { active: enableSidebarRight; component: SidebarRight {} }
}
+11 -10
View File
@@ -10,25 +10,25 @@ add_newline = false
# $character
# """
format = """
$cmd_duration$directory $git_branch
$cmd_duration 󰜥 $directory $git_branch
$character
"""
# Replace the "" symbol in the prompt with "➜"
[character] # The name of the module we are configuring is "character"
success_symbol = "[• ](bold fg:green) "
error_symbol = "[• 󰅙](bold fg:red) "
success_symbol = "[ 󰜥 ](bold fg:blue)"
error_symbol = "[ 󰜥 ](bold fg:red)"
# Disable the package module, hiding it from the prompt completely
[package]
disabled = true
[git_branch]
style = "bg: green"
style = "bg: cyan"
symbol = "󰘬"
truncation_length = 4
truncation_length = 12
truncation_symbol = ""
format = " [](bold fg:green)[$symbol $branch(:$remote_branch)](fg:black bg:green)[ ](bold fg:green)"
format = "󰜥 [](bold fg:cyan)[$symbol $branch(:$remote_branch)](fg:black bg:cyan)[ ](bold fg:cyan)"
[git_commit]
commit_hash_length = 4
@@ -52,7 +52,7 @@ deleted = " 🗑 "
[hostname]
ssh_only = false
format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan )"
format = "[•$hostname](bg:cyan bold fg:black)[](bold fg:cyan)"
trim_at = ".companyname.com"
disabled = false
@@ -82,8 +82,8 @@ home_symbol = "  "
read_only = "  "
style = "bg:green fg:black"
truncation_length = 6
truncation_symbol = "••/"
format = '[](bold fg:green)[$path ]($style)[](bold fg:green)'
truncation_symbol = " ••/"
format = '[](bold fg:green)[󰉋 $path]($style)[](bold fg:green)'
[directory.substitutions]
@@ -93,7 +93,8 @@ format = '[](bold fg:green)[$path ]($style)[](bold fg:green)'
"Music" = " 󰎈 "
"Pictures" = "  "
"Videos" = "  "
"GitHub" = " 󰊤 "
[cmd_duration]
min_time = 0
format = '[](bold fg:yellow)[ $duration](bold bg:yellow fg:black)[](bold fg:yellow) •• '
format = '[](bold fg:yellow)[󰪢 $duration](bold bg:yellow fg:black)[](bold fg:yellow)'
+15 -13
View File
@@ -81,23 +81,25 @@ It's ready if you don't need localization... so quite likely
<h3></h3>
</div>
## Main branch (*illogical-impulse*)
## illogical-impulse<sup>Quickshell</sup>
### AI
![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60)
_<sup>Sidebar offers online and offline chat. Text selection summary is offline only for privacy.</sup>_
| AI | Common widgets |
|:---|:---------------|
| ![image](https://github.com/user-attachments/assets/08d26785-b54d-4ad1-875b-bb08cc6757f5) | ![image](https://github.com/user-attachments/assets/4fcd63d9-0943-4b21-8737-4bed97b71961) |
| Window management | Weeb power |
| ![image](https://github.com/user-attachments/assets/86cc511b-0d33-4c78-bcc0-3037d02a17da) | ![image](https://github.com/user-attachments/assets/e402f74a-6bd8-4ebe-bcf4-3a4a4846de10) |
### Notifications, music controls, system, calendar
![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7)
_<sup>On the sidebar: flicking the notification</sup>_
By the way...
- The funny notification positions are mimicking Android 16's dragging behavior
- The clock on the wallpaper is automatically placed at the "least busy" region of the image
### Intuitive window management
![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5)
_<sup>You can also drag and drop windows across workspaces</sup>_
## illogical-impulse<sup>AGS</sup>
### Power to weebs
![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76)
_<sup>Get yande.re and konachan images from sidebar</sup>_
| 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) |
## Unsupported stuff
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies'
arch=(any)
license=(None)
depends=(
cava
pavucontrol-qt
wireplumber
libdbusmenu-gtk3
@@ -7,6 +7,7 @@ license=(None)
depends=(
adw-gtk-theme-git
breeze-plus
eza
fish
fontconfig
kde-material-you-colors
+5 -3
View File
@@ -5,8 +5,10 @@ pkgdesc='Illogical Impulse KDE Dependencies'
arch=(any)
license=(None)
depends=(
polkit-kde-agent
bluedevil
gnome-keyring
gnome-control-center
networkmanager better-control-git
networkmanager
plasma-nm
polkit-kde-agent
systemsettings
)
@@ -13,4 +13,5 @@ depends=(
libportal-gtk4
gobject-introspection
sassc
python-opencv
)
@@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse GTK/Qt Dependencies'
arch=(any)
license=(None)
depends=(
kdialog
qt6-5compat
qt6-base
qt6-declarative
@@ -21,7 +22,5 @@ depends=(
syntax-highlighting
upower
wtype
xdg-user-dirs-gtk
yad
ydotool
)
-11
View File
@@ -38,16 +38,6 @@ ii_check_venv() {
which python
deactivate
}
ii_check_ags() {
pkill ags
pkill agsv1
agsv1 > ii_ags.log 2>&1 &
GUI_PID=$!
sleep 10
kill $GUI_PID
echo "AGS log saved to \"ii_ags.log\"."
}
#x ii_check_ags
e "Checking git repo info"
x git remote get-url origin
@@ -57,7 +47,6 @@ e "Checking distro"
x ii_check_distro
e "Checking variables"
x declare -p XDG_BIN_HOME # ~/.local/bin
x declare -p XDG_CACHE_HOME # ~/.cache
x declare -p XDG_CONFIG_HOME # ~/.config
x declare -p XDG_DATA_HOME # ~/.local/share
+7 -38
View File
@@ -155,11 +155,11 @@ v mkdir -p $XDG_BIN_HOME $XDG_CACHE_HOME $XDG_CONFIG_HOME $XDG_DATA_HOME
# original dotfiles and new ones in the SAME DIRECTORY
# (eg. in ~/.config/hypr) won't be mixed together
# MISC (For .config/* but not AGS, not Fish, not Hyprland)
# MISC (For .config/* but not fish, not Hyprland)
case $SKIP_MISCCONF in
true) sleep 0;;
*)
for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'ags' ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do
for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'fish' ! -name 'hypr' -exec basename {} \;); do
# i=".config/$i"
echo "[$0]: Found target: .config/$i"
if [ -d ".config/$i" ];then v rsync -av --delete ".config/$i/" "$XDG_CONFIG_HOME/$i/"
@@ -176,24 +176,6 @@ case $SKIP_FISH in
;;
esac
# For AGS
case $SKIP_AGS in
true) sleep 0;;
*)
v rsync -av --delete --exclude '/user_options.jsonc' .config/ags/ "$XDG_CONFIG_HOME"/ags/
t="$XDG_CONFIG_HOME/ags/user_options.jsonc"
if [ -f $t ];then
echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m"
# v cp -f .config/ags/user_options.jsonc $t.new
existed_ags_opt=y
else
echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m"
v cp .config/ags/user_options.jsonc $t
existed_ags_opt=n
fi
;;
esac
# For Hyprland
case $SKIP_HYPRLAND in
true) sleep 0;;
@@ -202,15 +184,9 @@ case $SKIP_HYPRLAND in
t="$XDG_CONFIG_HOME/hypr/hyprland.conf"
if [ -f $t ];then
echo -e "\e[34m[$0]: \"$t\" already exists.\e[0m"
if [ -f "$XDG_STATE_HOME/ags/user/firstrun.txt" ]
then
v cp -f .config/hypr/hyprland.conf $t.new
existed_hypr_conf=y
else
v mv $t $t.old
v cp -f .config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
fi
v mv $t $t.old
v cp -f .config/hypr/hyprland.conf $t
existed_hypr_conf_firstrun=y
else
echo -e "\e[33m[$0]: \"$t\" does not exist yet.\e[0m"
v cp .config/hypr/hyprland.conf $t
@@ -249,7 +225,7 @@ esac
# some foldes (eg. .local/bin) should be processed separately to avoid `--delete' for rsync,
# since the files here come from different places, not only about one program.
v rsync -av ".local/bin/" "$XDG_BIN_HOME"
# v rsync -av ".local/bin/" "$XDG_BIN_HOME" # No longer needed since scripts are no longer in ~/.local/bin
# Prevent hyprland from not fully loaded
sleep 1
@@ -260,10 +236,7 @@ grep -q 'source ${XDG_CONFIG_HOME:-~/.config}/zshrc.d/dots-hyprland.zsh' ~/.zshr
warn_files=()
warn_files_tests=()
warn_files_tests+=(/usr/local/bin/ags)
warn_files_tests+=(/usr/local/etc/pam.d/ags)
warn_files_tests+=(/usr/local/lib/{GUtils-1.0.typelib,Gvc-1.0.typelib,libgutils.so,libgvc.so})
warn_files_tests+=(/usr/local/share/com.github.Aylur.ags)
warn_files_tests+=(/usr/local/share/fonts/TTF/Rubik{,-Italic}'[wght]'.ttf)
warn_files_tests+=(/usr/local/share/licenses/ttf-rubik)
warn_files_tests+=(/usr/local/share/fonts/TTF/Gabarito-{Black,Bold,ExtraBold,Medium,Regular,SemiBold}.ttf)
@@ -289,10 +262,6 @@ printf "\e[36mPress \e[30m\e[46m Ctrl+Super+T \e[0m\e[36m to select a wallpaper\
printf "\e[36mPress \e[30m\e[46m Super+/ \e[0m\e[36m for a list of keybinds\e[0m\n"
printf "\n"
case $existed_ags_opt in
y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/ags/user_options.jsonc\" already existed before and we didn't overwrite it. \e[0m\n"
# printf "\e[33mPlease use \"$XDG_CONFIG_HOME/ags/user_options.jsonc.new\" as a reference for a proper format.\e[0m\n"
;;esac
case $existed_hypr_conf_firstrun in
y) printf "\n\e[33m[$0]: Warning: \"$XDG_CONFIG_HOME/hypr/hyprland.conf\" already existed before. As it seems it is your first run, we replaced it with a new one. \e[0m\n"
printf "\e[33mAs it seems it is your first run, we replaced it with a new one. The old one has been renamed to \"$XDG_CONFIG_HOME/hypr/hyprland.conf.old\".\e[0m\n"
@@ -311,7 +280,7 @@ case $existed_hyprlock_conf in
;;esac
if [[ -z "${ILLOGICAL_IMPULSE_VIRTUAL_ENV}" ]]; then
printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/ags/.venv\"), or AGS config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n"
printf "\n\e[31m[$0]: \!! Important \!! : Please ensure environment variable \e[0m \$ILLOGICAL_IMPULSE_VIRTUAL_ENV \e[31m is set to proper value (by default \"~/.local/state/quickshell/.venv\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.\e[0m\n"
fi
if [[ ! -z "${warn_files[@]}" ]]; then
-1
View File
@@ -11,7 +11,6 @@ source ./scriptdata/installers
prevent_sudo_or_root
if command -v pacman >/dev/null 2>&1;then printf "\e[31m[$0]: pacman found, it seems that the system is ArchLinux or Arch-based distro. Aborting...\e[0m\n";exit 1;fi
v install-agsv1
v install-Rubik
v install-Gabarito
v install-OneUI
-2
View File
@@ -26,8 +26,6 @@ pycparser==2.22
# via cffi
pyproject-hooks==1.2.0
# via build
# pywal==3.3.0
# via -r scriptdata/requirements.in
pywayland==0.4.18
# via -r scriptdata/requirements.in
setproctitle==1.3.4