diff --git a/.config/fish/config.fish b/.config/fish/config.fish index 7f9114d72..3ddb607e5 100755 --- a/.config/fish/config.fish +++ b/.config/fish/config.fish @@ -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) diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf index db2ea6082..2ec488c75 100644 --- a/.config/hypr/hypridle.conf +++ b/.config/hypr/hypridle.conf @@ -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 } diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 32a25be4a..d4945605b 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -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 & diff --git a/.config/hypr/hyprland/general.conf b/.config/hypr/hyprland/general.conf index bf3475f9f..e8895efdc 100644 --- a/.config/hypr/hyprland/general.conf +++ b/.config/hypr/hyprland/general.conf @@ -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 diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index f6e9d03b9..db554af57 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -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 diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 19945f988..42897cafe 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -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:.* diff --git a/.config/matugen/config.toml b/.config/matugen/config.toml index ef7bf7639..66c17a37e 100644 --- a/.config/matugen/config.toml +++ b/.config/matugen/config.toml @@ -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' diff --git a/.config/matugen/scripts/least_busy_region.py b/.config/matugen/scripts/least_busy_region.py new file mode 100755 index 000000000..a1f2f47b4 --- /dev/null +++ b/.config/matugen/scripts/least_busy_region.py @@ -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() + diff --git a/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml new file mode 100644 index 000000000..b6add8968 --- /dev/null +++ b/.config/quickshell/modules/backgroundWidgets/BackgroundWidgets.qml @@ -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 + } + } + } + + } + } + + } + +} diff --git a/.config/quickshell/modules/bar/ActiveWindow.qml b/.config/quickshell/modules/bar/ActiveWindow.qml index 574cdb8c6..86018129b 100644 --- a/.config/quickshell/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/modules/bar/ActiveWindow.qml @@ -12,7 +12,6 @@ Item { height: parent.height width: colLayout.width - ColumnLayout { id: colLayout diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index ed108c69e..eb171bbf7 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -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 diff --git a/.config/quickshell/modules/bar/BatteryIndicator.qml b/.config/quickshell/modules/bar/BatteryIndicator.qml index 74cdf0169..271cd8c28 100644 --- a/.config/quickshell/modules/bar/BatteryIndicator.qml +++ b/.config/quickshell/modules/bar/BatteryIndicator.qml @@ -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) diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index f4b8606b9..d105ea203 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -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 } diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index 18fdcba42..2b2dd0b60 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -23,7 +23,7 @@ Item { lineWidth: 2 value: percentage size: 26 - secondaryColor: Appearance.m3colors.m3secondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer MaterialSymbol { diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index 4639edd22..cb3c6551a 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -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 { diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b9b45c288..49e9576bd 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -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 expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms + readonly property list expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] readonly property list 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 } diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 98abcf364..5edfaa86c 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -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 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" } diff --git a/.config/quickshell/modules/common/Directories.qml b/.config/quickshell/modules/common/Directories.qml index 9e3e3000f..d2c570234 100644 --- a/.config/quickshell/modules/common/Directories.qml +++ b/.config/quickshell/modules/common/Directories.qml @@ -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}'`) diff --git a/.config/quickshell/modules/common/functions/color_utils.js b/.config/quickshell/modules/common/functions/color_utils.js index c0ccfda9d..eb0fc0c2f 100644 --- a/.config/quickshell/modules/common/functions/color_utils.js +++ b/.config/quickshell/modules/common/functions/color_utils.js @@ -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); diff --git a/.config/quickshell/modules/common/widgets/ButtonGroup.qml b/.config/quickshell/modules/common/widgets/ButtonGroup.qml index a1570c6af..5356535f4 100644 --- a/.config/quickshell/modules/common/widgets/ButtonGroup.qml +++ b/.config/quickshell/modules/common/widgets/ButtonGroup.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/CircularProgress.qml b/.config/quickshell/modules/common/widgets/CircularProgress.qml index 19a838c4c..c3731e64e 100644 --- a/.config/quickshell/modules/common/widgets/CircularProgress.qml +++ b/.config/quickshell/modules/common/widgets/CircularProgress.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/KeyboardKey.qml b/.config/quickshell/modules/common/widgets/KeyboardKey.qml index d6ba5ba08..0cc80429e 100644 --- a/.config/quickshell/modules/common/widgets/KeyboardKey.qml +++ b/.config/quickshell/modules/common/widgets/KeyboardKey.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml index dbbfff009..450a856db 100644 --- a/.config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/.config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -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, diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index 1f55ad282..0a241553f 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -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 { diff --git a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml index 370814f91..e85735a71 100644 --- a/.config/quickshell/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/modules/common/widgets/NotificationActionButton.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml index 371093b25..81505aff6 100644 --- a/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/modules/common/widgets/NotificationAppIcon.qml @@ -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 == "" diff --git a/.config/quickshell/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/modules/common/widgets/NotificationGroup.qml index 009a0b3ed..e79f400ae 100644 --- a/.config/quickshell/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/modules/common/widgets/NotificationGroup.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/NotificationItem.qml b/.config/quickshell/modules/common/widgets/NotificationItem.qml index 26286290c..9a2a7c31d 100644 --- a/.config/quickshell/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/modules/common/widgets/NotificationItem.qml @@ -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 { diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index e59c8a962..a47f108b7 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml index 327d4aaa1..f3d438b55 100644 --- a/.config/quickshell/modules/common/widgets/Revealer.qml +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/RippleButton.qml b/.config/quickshell/modules/common/widgets/RippleButton.qml index 0a3a8743b..9931cd02a 100644 --- a/.config/quickshell/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/modules/common/widgets/RippleButton.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/SelectionDialog.qml b/.config/quickshell/modules/common/widgets/SelectionDialog.qml new file mode 100644 index 000000000..9cf0940ed --- /dev/null +++ b/.config/quickshell/modules/common/widgets/SelectionDialog.qml @@ -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] + ) + } + } + } + } +} diff --git a/.config/quickshell/modules/common/widgets/StyledLabel.qml b/.config/quickshell/modules/common/widgets/StyledLabel.qml new file mode 100644 index 000000000..f5201baea --- /dev/null +++ b/.config/quickshell/modules/common/widgets/StyledLabel.qml @@ -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 +} diff --git a/.config/quickshell/modules/common/widgets/StyledListView.qml b/.config/quickshell/modules/common/widgets/StyledListView.qml index c13d8082f..76d9782b4 100644 --- a/.config/quickshell/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/modules/common/widgets/StyledListView.qml @@ -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: [ diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index 70491b8bb..ca0980030 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -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 diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index bbc2ae513..217a2f7e4 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -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 {} diff --git a/.config/quickshell/modules/common/widgets/StyledText.qml b/.config/quickshell/modules/common/widgets/StyledText.qml index 6eef57852..7750456e0 100644 --- a/.config/quickshell/modules/common/widgets/StyledText.qml +++ b/.config/quickshell/modules/common/widgets/StyledText.qml @@ -11,4 +11,5 @@ Text { pixelSize: Appearance?.font.pixelSize.small ?? 15 } color: Appearance?.m3colors.m3onBackground ?? "black" + linkColor: Appearance?.m3colors.m3primary } diff --git a/.config/quickshell/modules/common/widgets/StyledTextArea.qml b/.config/quickshell/modules/common/widgets/StyledTextArea.qml index 1ea9a349a..67d417576 100644 --- a/.config/quickshell/modules/common/widgets/StyledTextArea.qml +++ b/.config/quickshell/modules/common/widgets/StyledTextArea.qml @@ -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" diff --git a/.config/quickshell/modules/common/widgets/WaveVisualizer.qml b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml new file mode 100644 index 000000000..eb579d7e1 --- /dev/null +++ b/.config/quickshell/modules/common/widgets/WaveVisualizer.qml @@ -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 points + property list smoothPoints + property real maxVisualizerValue: 1000 + property int smoothing: 2 + property bool live: true + property color color: Appearance.m3colors.m3primary + + onPointsChanged: () => { + root.requestPaint() + } + + anchors.fill: parent + onPaint: { + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + + var points = root.points; + var maxVal = root.maxVisualizerValue || 1; + var h = height; + var w = width; + var n = points.length; + if (n < 2) return; + + // Smoothing: simple moving average (optional) + var smoothWindow = root.smoothing; // adjust for more/less smoothing + root.smoothPoints = []; + for (var i = 0; i < n; ++i) { + var sum = 0, count = 0; + for (var j = -smoothWindow; j <= smoothWindow; ++j) { + var idx = Math.max(0, Math.min(n - 1, i + j)); + sum += points[idx]; + count++; + } + root.smoothPoints.push(sum / count); + } + if (!root.live) 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 + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/dock/Dock.qml b/.config/quickshell/modules/dock/Dock.qml index 777aa6461..524fbc11f 100644 --- a/.config/quickshell/modules/dock/Dock.qml +++ b/.config/quickshell/modules/dock/Dock.qml @@ -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 - } - } - } - } - } + } + } + } } } } diff --git a/.config/quickshell/modules/dock/DockAppButton.qml b/.config/quickshell/modules/dock/DockAppButton.qml index 7ff11bae1..f4623e625 100644 --- a/.config/quickshell/modules/dock/DockAppButton.qml +++ b/.config/quickshell/modules/dock/DockAppButton.qml @@ -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) + } + } + } + } } } diff --git a/.config/quickshell/modules/dock/DockApps.qml b/.config/quickshell/modules/dock/DockApps.qml index 9a8db93a9..ffda024c7 100644 --- a/.config/quickshell/modules/dock/DockApps.qml +++ b/.config/quickshell/modules/dock/DockApps.qml @@ -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 diff --git a/.config/quickshell/modules/dock/DockButton.qml b/.config/quickshell/modules/dock/DockButton.qml index 6c3010bf6..577cbcdc7 100644 --- a/.config/quickshell/modules/dock/DockButton.qml +++ b/.config/quickshell/modules/dock/DockButton.qml @@ -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 } diff --git a/.config/quickshell/modules/dock/DockSeparator.qml b/.config/quickshell/modules/dock/DockSeparator.qml index 2b27b0daf..abb45d1da 100644 --- a/.config/quickshell/modules/dock/DockSeparator.qml +++ b/.config/quickshell/modules/dock/DockSeparator.qml @@ -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 } diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml index 81b1d62d9..4350658f6 100644 --- a/.config/quickshell/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -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 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 } } } diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml index 16be41b83..2100cc90c 100644 --- a/.config/quickshell/modules/mediaControls/PlayerControl.qml +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -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 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" diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml index 122489d88..fb046343d 100644 --- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -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 diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index decb7537c..765386bc8 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -73,7 +73,7 @@ Scope { item: osdValuesWrapper } - implicitWidth: Appearance.sizes.osdWidth + implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight visible: osdLoader.active diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index e1904354e..5d23b4405 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -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 + } + } + } + } } } } diff --git a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml index 39862c55f..e78f45b2e 100644 --- a/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml +++ b/.config/quickshell/modules/onScreenKeyboard/OnScreenKeyboard.qml @@ -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 diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 6fa85e891..d3e4e4567 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -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) => { diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index b060c8dfc..9ab39fd02 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -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 diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index d4617b542..3b376988b 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -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) + } + } } } } \ No newline at end of file diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index 00d5da743..d23cb4c09 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -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 diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index ea8dcb3b9..9e6866240 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -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 diff --git a/.config/quickshell/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/modules/screenCorners/ScreenCorners.qml index 32708dc60..3988d73d8 100644 --- a/.config/quickshell/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/modules/screenCorners/ScreenCorners.qml @@ -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 diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index 207305769..becda60c1 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -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) ? diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index 6eb069e94..1ce752c7b 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -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 { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index acc1af4fe..1300d5482 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index 5cfd79472..ce0b22802 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/Translator.qml b/.config/quickshell/modules/sidebarLeft/Translator.qml index a853f30cf..999fe3e44 100644 --- a/.config/quickshell/modules/sidebarLeft/Translator.qml +++ b/.config/quickshell/modules/sidebarLeft/Translator.qml @@ -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 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 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 + } + } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index e115338ad..8d7952110 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -28,6 +28,8 @@ Rectangle { property bool renderMarkdown: true property bool editing: false + property list messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content) + anchors.left: parent?.left anchors.right: parent?.right implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2 @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index f344308ca..a253a291e 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml index fa4af99e0..ea7bb0ee3 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageCodeBlock.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml index 87087c6b9..faa6c5590 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml index 27dae3bd3..1ae941b78 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index c8c89f72a..4e115bc76 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index c19c8bb98..7a207582f 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml new file mode 100644 index 000000000..37df25f83 --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/translator/LanguageSelectorButton.qml @@ -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 + } + } + } +} diff --git a/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml new file mode 100644 index 000000000..dad25020f --- /dev/null +++ b/.config/quickshell/modules/sidebarLeft/translator/TextCanvas.qml @@ -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 + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index 57d836b0e..a1e5a7478 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -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") + } } } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index 88de9202c..7d1af447d 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -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) diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml index 4ac63d220..b48d3467f 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/IdleInhibitor.qml @@ -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") diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 8f058fd53..5271e3769 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -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 { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index 527ed6978..9f5f4fd08 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -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 diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml index a7a88a403..2e1570f30 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -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 { diff --git a/.config/quickshell/scripts/cava/raw_output_config.txt b/.config/quickshell/scripts/cava/raw_output_config.txt new file mode 100644 index 000000000..7760e4ea2 --- /dev/null +++ b/.config/quickshell/scripts/cava/raw_output_config.txt @@ -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 + + diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 22a87e158..dffd9bd61 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -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 "$@" \ No newline at end of file +main "$@" diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index c58137938..dbb8869d7 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -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; } diff --git a/.config/quickshell/services/Audio.qml b/.config/quickshell/services/Audio.qml index 8b5f9b760..2fd5e0cac 100644 --- a/.config/quickshell/services/Audio.qml +++ b/.config/quickshell/services/Audio.qml @@ -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; + } + + } + } diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index c1e040209..347e8f400 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -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) { diff --git a/.config/quickshell/services/LatexRenderer.qml b/.config/quickshell/services/LatexRenderer.qml index d3b9ade1f..e7066fa4c 100644 --- a/.config/quickshell/services/LatexRenderer.qml +++ b/.config/quickshell/services/LatexRenderer.qml @@ -25,7 +25,8 @@ Singleton { property list 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() diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index bc2cb0221..35a5008ea 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -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 {} } } diff --git a/.config/starship.toml b/.config/starship.toml index 751f2fd2c..5ed04489e 100644 --- a/.config/starship.toml +++ b/.config/starship.toml @@ -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)' diff --git a/README.md b/README.md index a845e4899..4cc7899e4 100644 --- a/README.md +++ b/README.md @@ -81,23 +81,25 @@ It's ready if you don't need localization... so quite likely

-## Main branch (*illogical-impulse*) +## illogical-impulseQuickshell -### AI -![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) -_Sidebar offers online and offline chat. Text selection summary is offline only for privacy._ +| 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) -_On the sidebar: flicking the notification_ +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) -_You can also drag and drop windows across workspaces_ +## illogical-impulseAGS -### Power to weebs -![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) -_Get yande.re and konachan images from sidebar_ +| 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 diff --git a/arch-packages/illogical-impulse-audio/PKGBUILD b/arch-packages/illogical-impulse-audio/PKGBUILD index 81054e0b7..6cab7de53 100644 --- a/arch-packages/illogical-impulse-audio/PKGBUILD +++ b/arch-packages/illogical-impulse-audio/PKGBUILD @@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies' arch=(any) license=(None) depends=( + cava pavucontrol-qt wireplumber libdbusmenu-gtk3 diff --git a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD index c96790c0b..ed1a95070 100644 --- a/arch-packages/illogical-impulse-fonts-themes/PKGBUILD +++ b/arch-packages/illogical-impulse-fonts-themes/PKGBUILD @@ -7,6 +7,7 @@ license=(None) depends=( adw-gtk-theme-git breeze-plus + eza fish fontconfig kde-material-you-colors diff --git a/arch-packages/illogical-impulse-kde/PKGBUILD b/arch-packages/illogical-impulse-kde/PKGBUILD index 35f8d669f..b8b91c3f9 100644 --- a/arch-packages/illogical-impulse-kde/PKGBUILD +++ b/arch-packages/illogical-impulse-kde/PKGBUILD @@ -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 ) diff --git a/arch-packages/illogical-impulse-python/PKGBUILD b/arch-packages/illogical-impulse-python/PKGBUILD index fc1ac1e05..d9c6caa55 100644 --- a/arch-packages/illogical-impulse-python/PKGBUILD +++ b/arch-packages/illogical-impulse-python/PKGBUILD @@ -13,4 +13,5 @@ depends=( libportal-gtk4 gobject-introspection sassc + python-opencv ) diff --git a/arch-packages/illogical-impulse-toolkit/PKGBUILD b/arch-packages/illogical-impulse-toolkit/PKGBUILD index c73e804b0..6f12584a2 100644 --- a/arch-packages/illogical-impulse-toolkit/PKGBUILD +++ b/arch-packages/illogical-impulse-toolkit/PKGBUILD @@ -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 ) diff --git a/diagnose b/diagnose index 7edbcbd1a..df1d0dee4 100755 --- a/diagnose +++ b/diagnose @@ -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 diff --git a/install.sh b/install.sh index 89ee391ef..7b47af0d4 100755 --- a/install.sh +++ b/install.sh @@ -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 diff --git a/manual-install-helper.sh b/manual-install-helper.sh index 663f3009a..d594f2c16 100755 --- a/manual-install-helper.sh +++ b/manual-install-helper.sh @@ -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 diff --git a/scriptdata/requirements.txt b/scriptdata/requirements.txt index 802304132..c2f380c4a 100644 --- a/scriptdata/requirements.txt +++ b/scriptdata/requirements.txt @@ -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