diff --git a/.gitignore b/.gitignore index 50b4af4b..e4e69e73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.tar.zst -pkg/ \ No newline at end of file +pkg/ +.vscode/ \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 00000000..34a94537 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/qt/QtCore", + "/usr/include/qt", + "/usr/include/qt/QtQml", + "/usr/include/qt/QtGui" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "linux-clang-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..9ddf6b28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.ignoreCMakeListsMissing": true +} \ No newline at end of file diff --git a/PKGBUILD b/PKGBUILD index a41a63ca..c1efda87 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,21 +1,21 @@ pkgname="sleex" -pkgver="1.31" -pkgrel="4" +pkgver="2.0_beta.1" +pkgrel="1" pkgdesc="Desktop environment focused on aesthetics and performance" arch=("x86_64") depends=( # Audio 'cava' 'pavucontrol-qt' 'wireplumber' 'libdbusmenu-gtk3' 'playerctl' # Backlight - 'hyprsunset' 'geoclue' 'brightnessctl' 'ddcutil' + 'gammastep' 'geoclue' 'brightnessctl' 'ddcutil' # Basic "axel" "bc" "coreutils" "cliphist" "cmake" "curl" "rsync" "wget" "ripgrep" "jq" "meson" "xdg-user-dirs" "foot" "power-profiles-daemon" "mission-center" "kvantum" "inotify-tools" "lm_sensors" "qt5ct" "qt6ct" "sqlite" # Cursor "bibata-cursor-theme-bin" # Fonts & Themes 'adw-gtk-theme' 'breeze-plus' 'eza' 'fish' 'fontconfig' 'kde-material-you-colors' 'matugen-bin' 'starship' 'ttf-gabarito-git' 'ttf-jetbrains-mono-nerd' 'ttf-material-design-icons-extended' 'ttf-material-symbols-variable' 'ttf-readex-pro' 'ttf-rubik-vf' 'ttf-twemoji' - # Hyprland dependencies - 'hyprutils' 'hyprpicker' 'hyprlang' 'hyprland-qt-support' 'hyprland-guiutils' 'hyprcursor' 'hyprwayland-scanner' 'hyprland' 'xdg-desktop-portal-hyprland' 'wl-clipboard' 'hyprlock' + # Fhtc dependencies + 'fht-compositor-git' 'fht-share-picker-git' 'wl-clipboard' # QT/KDE dependencies 'bluedevil' 'gnome-keyring' 'networkmanager' 'polkit-kde-agent' 'pcmanfm-qt' 'kwrite' "libnm" "gio-qt" "qt6-connectivity" # Microtex @@ -23,9 +23,9 @@ depends=( # Portal 'xdg-desktop-portal' # Python deps - 'clang' 'uv' 'gtk4' 'libadwaita' 'libsoup3' 'libportal-gtk4' 'gobject-introspection' 'sassc' 'python-setproctitle' 'python-pywayland' + 'clang' 'uv' 'gtk4' 'libadwaita' 'libsoup3' 'libportal-gtk4' 'gobject-introspection' 'sassc' 'python-setproctitle' 'python-pywayland' 'python-toml' # Screencast/Screenrecord - 'hyprshot' 'ksnip' 'wf-recorder' 'slurp' 'grim' 'tesseract' 'tesseract-data-eng' + 'ksnip' 'wf-recorder' 'slurp' 'grim' 'tesseract' 'tesseract-data-eng' # Tools 'kdialog' 'qt6-5compat' 'qt6-avif-image-plugin' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-multimedia' 'qt6-positioning' 'qt6-quicktimeline' 'qt6-sensors' 'qt6-svg' 'qt6-tools' 'qt6-translations' 'qt6-virtualkeyboard' 'qt6-wayland' 'syntax-highlighting' 'upower' 'wtype' 'ydotool' 'fprintd' 'khal' 'vdirsyncer' 'python-aiohttp-oauthlib' 'swappy' 'hypnos' 'bluez-utils' # Widgets @@ -36,7 +36,6 @@ depends=( "sleex-artworks" ) optdepends=( - "hyprwayland-scanner: Wayland protocol scanner for Hyprland" "neofetch: Fancy system info in your terminal" "firefox: Web browser" "pipewire-pulse: PulseAudio replacement via PipeWire" @@ -76,7 +75,4 @@ package() { mkdir -p "$pkgdir/usr/share/wayland-sessions" cp -r "$srcdir/share/wayland-sessions/"* "$pkgdir/usr/share/wayland-sessions/" - - mkdir -p "$pkgdir/usr/libalpm/hooks" - cp -r "$srcdir/share/libalpm/hooks/"* "$pkgdir/usr/libalpm/hooks/" } diff --git a/README.md b/README.md index 0bafbfa7..7688cd0c 100644 --- a/README.md +++ b/README.md @@ -2,41 +2,12 @@ AxOS

-Sleex is the third desktop environement of AxOS. It is based on Hyprland with Quickshell. +# [WIP] Sleex with the Fht compositor -## Features -- AI chat integration with external providers (Gemini, OpenAI...) -- Smooth animations -- Tiling window management for seamless multitasking -- Adaptative color scheme based on the wallpaper -- Ready to use -- Multiple available built in tools -- Looks good +If you want to test this very very unstable branch, you can build and install the package: -| | -|------| -| ![preview](preview.mp4) | - -## Installation - -> [!IMPORTANT] -> If you already have an hyprland config, this will erase it. - -If you are using AxOS, you can simply use epsilon: -``` -epsi i sleex +```bash +makepkg -si ``` -### Other arch-based distributions - -Sleex is available on the AUR as `sleex-git`. [See the page](https://aur.archlinux.org/packages/sleex-git). -You can install it using an aur helper such as `yay` or `paru`. - -Then, you can start sleex by selecting the sleex session on your greeter - -## License -Sleex is licensed under the GNU General Public License v3.0 - -## Special thanks -- [@end-4](https://github.com/end-4/): Great inspiration | Sleex is based on his work -- [@xHyperVoid](https://github.com/xHyperVoid): Designer | Made the logo and the wallpapers +[Fht compositor](https://nferhat.github.io/fht-compositor/) is a new Wayland compositor written in Rust. It is designed to be fast, lightweight, and easy to use. It is also highly customizable, allowing users to create their own unique desktop experience. \ No newline at end of file diff --git a/sleex-user-config/PKGBUILD b/sleex-user-config/PKGBUILD index feabad3b..b23824d4 100644 --- a/sleex-user-config/PKGBUILD +++ b/sleex-user-config/PKGBUILD @@ -5,10 +5,6 @@ pkgdesc="User configuration for Sleex desktop environment" arch=("x86_64") depend=("sleex") -provides=("axskel-hypr") -replaces=("axskel-hypr") -conflicts=("axskel-hypr") - package(){ mkdir -p ${pkgdir}/etc/skel/ cp -r ${srcdir}/.config/ ${pkgdir}/etc/skel/ diff --git a/sleex-user-config/README.md b/sleex-user-config/README.md deleted file mode 100644 index 93a38383..00000000 --- a/sleex-user-config/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# axskel-hypr - -`axskel-hypr` is a custom desktop environment setup, featuring the **Hyprland** window manager and pre-configured settings. This package includes configurations for **Hyprland**, alongside several enhancements for usability, aesthetics, and functionality. - -
- -After installation, the package deploys configuration files to `/etc/skel`. Customize these files as needed to tailor your desktop environment. - diff --git a/sleex-user-config/src/.config/fht/apps.toml b/sleex-user-config/src/.config/fht/apps.toml new file mode 100644 index 00000000..bc76b458 --- /dev/null +++ b/sleex-user-config/src/.config/fht/apps.toml @@ -0,0 +1,16 @@ +# Terminal +Super-Return = { action = "run-command", arg = "foot" } +# Code +Super-c = { action = "run-command", arg = "code --enable-features=UseOzonePlatform --ozone-platform=wayland" } +# Browser +Super-w = { action = "run-command", arg = "firefox" } +# File manager +Super-e = { action = "run-command", arg = "pcmanfm-qt" } +# Text editor +Super-x = { action = "run-command", arg = "kwrite" } +# Sleex settings +Super-i = { action = "run-command", arg = "qs -p /usr/share/sleex/settings.qml" } +# Volume mixer +Ctrl-Super-v = { action = "run-command", arg = "pavucontrol" } +# System monitor +Ctrl-Shift-Esc = { action = "run-command", arg = "missioncenter" } \ No newline at end of file diff --git a/sleex-user-config/src/.config/hypr/custom/env.conf b/sleex-user-config/src/.config/fht/custom/env.toml similarity index 58% rename from sleex-user-config/src/.config/hypr/custom/env.conf rename to sleex-user-config/src/.config/fht/custom/env.toml index bc21f238..b6a2a503 100644 --- a/sleex-user-config/src/.config/hypr/custom/env.conf +++ b/sleex-user-config/src/.config/fht/custom/env.toml @@ -1,3 +1,3 @@ # This is a part of the Sleex's compositor user configuration # You can put extra environment variables here -# https://wiki.hyprland.org/Configuring/Environment-variables/ +# https://nferhat.github.io/fht-compositor/configuration/introduction.html#env \ No newline at end of file diff --git a/sleex-user-config/src/.config/fht/custom/execs.toml b/sleex-user-config/src/.config/fht/custom/execs.toml new file mode 100644 index 00000000..b452a4c0 --- /dev/null +++ b/sleex-user-config/src/.config/fht/custom/execs.toml @@ -0,0 +1,3 @@ +# This is a part of the Sleex's compositor user configuration +# You can put extra executables here +# https://nferhat.github.io/fht-compositor/configuration/introduction.html#autostart \ No newline at end of file diff --git a/sleex-user-config/src/.config/hypr/custom/general.conf b/sleex-user-config/src/.config/fht/custom/general.toml similarity index 100% rename from sleex-user-config/src/.config/hypr/custom/general.conf rename to sleex-user-config/src/.config/fht/custom/general.toml diff --git a/sleex-user-config/src/.config/hypr/custom/keybinds.conf b/sleex-user-config/src/.config/fht/custom/keybinds.toml similarity index 58% rename from sleex-user-config/src/.config/hypr/custom/keybinds.conf rename to sleex-user-config/src/.config/fht/custom/keybinds.toml index 107ff446..affa2121 100644 --- a/sleex-user-config/src/.config/hypr/custom/keybinds.conf +++ b/sleex-user-config/src/.config/fht/custom/keybinds.toml @@ -1,4 +1,4 @@ # This is a part of the Sleex's compositor user configuration # You can put your preferred keybinds here -# https://wiki.hyprland.org/Configuring/Binds/ +# https://nferhat.github.io/fht-compositor/configuration/keybindings.html diff --git a/sleex-user-config/src/.config/fht/custom/rules.toml b/sleex-user-config/src/.config/fht/custom/rules.toml new file mode 100644 index 00000000..b65778c7 --- /dev/null +++ b/sleex-user-config/src/.config/fht/custom/rules.toml @@ -0,0 +1,4 @@ +# This is a part of the Sleex's compositor user configuration +# You can put custom rules here +# https://nferhat.github.io/fht-compositor/configuration/window-rules.html +# https://nferhat.github.io/fht-compositor/configuration/layer-rules.html \ No newline at end of file diff --git a/sleex-user-config/src/.config/fht/monitors.toml b/sleex-user-config/src/.config/fht/monitors.toml new file mode 100644 index 00000000..78917d63 --- /dev/null +++ b/sleex-user-config/src/.config/fht/monitors.toml @@ -0,0 +1,9 @@ +# Output configuration (examples) +# https://nferhat.github.io/fht-compositor/configuration/outputs.html + +# [outputs.HDMI-A-1] +# mode = "1920x1080@144" + +# [outputs.DP-1] +# mode = "1920x1080@75" +# position.x = 1920 \ No newline at end of file diff --git a/sleex-user-config/src/.config/fish/auto-Hypr.fish b/sleex-user-config/src/.config/fish/auto-Hypr.fish deleted file mode 100644 index c3c1890f..00000000 --- a/sleex-user-config/src/.config/fish/auto-Hypr.fish +++ /dev/null @@ -1,5 +0,0 @@ -# Auto start Hyprland on tty1 -if test -z "$DISPLAY" ;and test "$XDG_VTNR" -eq 1 - mkdir -p ~/.cache - exec Hyprland > ~/.cache/hyprland.log ^&1 -end diff --git a/sleex-user-config/src/.config/hypr/apps.conf b/sleex-user-config/src/.config/hypr/apps.conf deleted file mode 100644 index 716a58d1..00000000 --- a/sleex-user-config/src/.config/hypr/apps.conf +++ /dev/null @@ -1,15 +0,0 @@ -# This is a part of the Sleex's compositor user configuration -# This file is permanent, you can edit it safely to add your preferred app keybinds -# Be just careful to not duplicate existing keybinds, including those in other config files - -#! -##! Apps -bind = Super, Return, exec, # Launch foot (terminal) -bind = Super, C, exec, code --password-store=gnome --enable-features=UseOzonePlatform --ozone-platform=wayland # Launch VSCode (editor) -bind = Super, E, exec, pcmanfm-qt # Launch file manager -bind = Super, W, exec, firefox # launch Firefox -bind = Super, X, exec, kwrite # Launch Text Editor -bind = Super, I, exec, qs -p /usr/share/sleex/settings.qml # Launch Sleex Settings -bind = Ctrl+Super, V, exec, pavucontrol # Launch pavucontrol (volume mixer) -bind = Ctrl+Super+Shift, V, exec, easyeffects # Launch EasyEffects (equalizer & other audio effects) -bind = Ctrl+Shift, Escape, exec, missioncenter # Launch System monitor diff --git a/sleex-user-config/src/.config/hypr/custom/execs.conf b/sleex-user-config/src/.config/hypr/custom/execs.conf deleted file mode 100644 index 5969a35c..00000000 --- a/sleex-user-config/src/.config/hypr/custom/execs.conf +++ /dev/null @@ -1,3 +0,0 @@ -# This is a part of the Sleex's compositor user configuration -# You can make apps auto-start here -# Relevant Hyprland wiki section: https://wiki.hyprland.org/Configuring/Keywords/#executing diff --git a/sleex-user-config/src/.config/hypr/custom/rules.conf b/sleex-user-config/src/.config/hypr/custom/rules.conf deleted file mode 100644 index c4bda6b4..00000000 --- a/sleex-user-config/src/.config/hypr/custom/rules.conf +++ /dev/null @@ -1,5 +0,0 @@ -# This is a part of the Sleex's compositor user configuration -# You can put custom rules here -# Window/layer rules: https://wiki.hyprland.org/Configuring/Window-Rules/ -# Workspace rules: https://wiki.hyprland.org/Configuring/Workspace-Rules/ - diff --git a/sleex-user-config/src/.config/hypr/custom/scripts/__restore_video_wallpaper.sh b/sleex-user-config/src/.config/hypr/custom/scripts/__restore_video_wallpaper.sh deleted file mode 100644 index ea4b4fbd..00000000 --- a/sleex-user-config/src/.config/hypr/custom/scripts/__restore_video_wallpaper.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -# The content of this script will be generated by switchwall.sh - Don't modify it by yourself. diff --git a/sleex-user-config/src/.config/hypr/hypridle.conf b/sleex-user-config/src/.config/hypr/hypridle.conf deleted file mode 100755 index 98f11f27..00000000 --- a/sleex-user-config/src/.config/hypr/hypridle.conf +++ /dev/null @@ -1,36 +0,0 @@ -general { - lock_cmd = qs -p /usr/share/sleex ipc call lock lock - before_sleep_cmd = qs -p /usr/share/sleex ipc call lock lock # lock before suspend. - after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display. -} - -listener { - timeout = 120 # 2min. - on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor. - on-resume = brightnessctl -r # monitor backlight restore. -} - -# turn off keyboard backlight if present (auto-detect device) -listener { - timeout = 300 # 5min. - on-timeout = KBD=$(ls /sys/class/leds | grep -i kbd_backlight | head -n1) && \ - [ -n "$KBD" ] && brightnessctl -sd "$KBD" set 0 - on-resume = KBD=$(ls /sys/class/leds | grep -i kbd_backlight | head -n1) && \ - [ -n "$KBD" ] && brightnessctl -rd "$KBD" -} - -listener { - timeout = 120 # 2min. - on-timeout = qs -p /usr/share/sleex ipc call lock lock # lock screen when timeout has passed -} - -# listener { -# timeout = 220 # 3min, 40sec -# on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed -# on-resume = hyprctl dispatch dpms on && brightnessctl -r # screen on when activity is detected after timeout has fired. -# } - -listener { - timeout = 600 # 10min - on-timeout = systemctl suspend # suspend pc -} diff --git a/sleex-user-config/src/.config/hypr/monitors.conf b/sleex-user-config/src/.config/hypr/monitors.conf deleted file mode 100644 index a9851bb1..00000000 --- a/sleex-user-config/src/.config/hypr/monitors.conf +++ /dev/null @@ -1,3 +0,0 @@ -# Generated by nwg-displays on 2025-06-09 at 12:05:41. Do not edit manually, use `nwg-displays` instead. - -monitor=,preferred,auto,1 diff --git a/sleex-user-config/src/.config/hypr/workspaces.conf b/sleex-user-config/src/.config/hypr/workspaces.conf deleted file mode 100644 index e69de29b..00000000 diff --git a/sleex-user-config/src/.config/matugen/config.toml b/sleex-user-config/src/.config/matugen/config.toml index 0f081295..eec40b84 100644 --- a/sleex-user-config/src/.config/matugen/config.toml +++ b/sleex-user-config/src/.config/matugen/config.toml @@ -5,14 +5,6 @@ version_check = false input_path = '~/.config/matugen/templates/colors.json' output_path = '~/.local/state/sleex/user/generated/colors.json' -[templates.hyprland] -input_path = '~/.config/matugen/templates/hyprland/colors.conf' -output_path = '~/.config/hypr/hyprland/colors.conf' - -[templates.hyprlock] -input_path = '~/.config/matugen/templates/hyprland/hyprlock.conf' -output_path = '~/.config/hypr/hyprlock.conf' - [templates.fuzzel] input_path = '~/.config/matugen/templates/fuzzel/fuzzel_theme.ini' output_path = '~/.config/fuzzel/fuzzel_theme.ini' diff --git a/sleex-user-config/src/.config/matugen/templates/hyprland/colors.conf b/sleex-user-config/src/.config/matugen/templates/hyprland/colors.conf deleted file mode 100644 index 67fdaea7..00000000 --- a/sleex-user-config/src/.config/matugen/templates/hyprland/colors.conf +++ /dev/null @@ -1,32 +0,0 @@ -general { - col.active_border = rgba({{colors.on_surface.default.hex_stripped}}39) - col.inactive_border = rgba({{colors.outline.default.hex_stripped}}30) -} - -misc { - background_color = rgba({{colors.surface.dark.hex_stripped}}FF) -} - -plugin { - hyprbars { - # Honestly idk if it works like css, but well, why not - bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif - bar_height = 30 - bar_padding = 10 - bar_button_padding = 5 - bar_precedence_over_border = true - bar_part_of_window = true - - bar_color = rgba({{colors.background.default.hex_stripped}}FF) - col.text = rgba({{colors.on_background.default.hex_stripped}}FF) - - - # example buttons (R -> L) - # hyprbars-button = color, size, on-click - hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖭, hyprctl dispatch killactive - hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖯, hyprctl dispatch fullscreen 1 - hyprbars-button = rgb({{colors.on_background.default.hex_stripped}}), 13, 󰖰, hyprctl dispatch movetoworkspacesilent special - } -} - -windowrulev2 = bordercolor rgba({{colors.primary.default.hex_stripped}}AA) rgba({{colors.primary.default.hex_stripped}}77),pinned:1 diff --git a/sleex-user-config/src/.config/matugen/templates/hyprland/hyprlock.conf b/sleex-user-config/src/.config/matugen/templates/hyprland/hyprlock.conf deleted file mode 100644 index eab16719..00000000 --- a/sleex-user-config/src/.config/matugen/templates/hyprland/hyprlock.conf +++ /dev/null @@ -1,93 +0,0 @@ -$text_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF) -$entry_background_color = rgba({{colors.on_primary_fixed.default.hex_stripped}}11) -$entry_border_color = rgba({{colors.outline.default.hex_stripped}}55) -$entry_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF) -$font_family = Rubik Light -$font_family_clock = Rubik Light -$font_material_symbols = Material Symbols Rounded - -background { - color = rgba(181818FF) - - path = {{image}} - blur_size = 15 - blur_passes = 4 - brightness = 0.33 -} -input-field { - monitor = - size = 250, 50 - outline_thickness = 2 - dots_size = 0.1 - dots_spacing = 0.3 - outer_color = $entry_border_color - inner_color = $entry_background_color - font_color = $entry_color - fade_on_empty = true - - position = 0, 20 - halign = center - valign = center -} - -label { # Caps Lock Warning - monitor = - text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh - color = $text_color - font_size = 13 - font_family = $font_family - position = 0, -25 - halign = center - valign = center -} - - -label { # Clock - monitor = - text = $TIME - color = $text_color - font_size = 65 - font_family = $font_family_clock - - position = 0, 300 - halign = center - valign = center -} -label { # Date - monitor = - text = cmd[update:5000] date +"%A, %B %d" - color = $text_color - font_size = 17 - font_family = $font_family - - position = 0, 240 - halign = center - valign = center -} - -label { # User - monitor = - text =  $USER - color = $text_color - outline_thickness = 2 - dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8 - dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0 - dots_center = true - font_size = 20 - font_family = $font_family - position = 0, 50 - halign = center - valign = bottom -} - -label { # Status - monitor = - text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh - color = $text_color - font_size = 14 - font_family = $font_family - - position = 30, -30 - halign = left - valign = top -} \ No newline at end of file diff --git a/sleex-user-config/src/.config/xdg-desktop-portal/hyprland-portals.conf b/sleex-user-config/src/.config/xdg-desktop-portal/hyprland-portals.conf deleted file mode 100644 index 62f3d568..00000000 --- a/sleex-user-config/src/.config/xdg-desktop-portal/hyprland-portals.conf +++ /dev/null @@ -1,3 +0,0 @@ -[preferred] -default = hyprland;gtk -org.freedesktop.impl.portal.FileChooser = kde diff --git a/sleex-user-config/src/.config/zshrc.d/auto-Hypr.sh b/sleex-user-config/src/.config/zshrc.d/auto-Hypr.sh deleted file mode 100644 index 55eca5e0..00000000 --- a/sleex-user-config/src/.config/zshrc.d/auto-Hypr.sh +++ /dev/null @@ -1,5 +0,0 @@ -# Auto start Hyprland on tty1 -if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" -eq 1 ]; then - mkdir -p ~/.cache - exec Hyprland > ~/.cache/hyprland.log 2>&1 -fi diff --git a/src/bin/rubyshot b/src/bin/rubyshot deleted file mode 100755 index 8431bd69..00000000 --- a/src/bin/rubyshot +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/bash - -WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')" -WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))' )" -GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp -f '%x %y %w %h') -wayshot -s "$GEOM" --stdout ${#:+"$@"} \ No newline at end of file diff --git a/src/bin/sleex b/src/bin/sleex index ee2218d0..3640c400 100755 --- a/src/bin/sleex +++ b/src/bin/sleex @@ -1,10 +1,9 @@ #!/bin/sh export XDG_CURRENT_DESKTOP="Sleex" -export HYPRLAND_INSTANCE_SIGNATURE="" systemctl --user import-environment XDG_CURRENT_DESKTOP # Check if config dirs/files exist, if not copy them from /etc/skel -for dir in fish hypr anyrun fuzzel Kvantum matugen vdirsyncer kde-material-you-colors mpv wlogout qt5ct xdg-desktop-portal fontconfig khal qt6ct zshrc.d foot; do +for dir in fish fht anyrun fuzzel Kvantum matugen vdirsyncer kde-material-you-colors mpv wlogout qt5ct xdg-desktop-portal fontconfig khal qt6ct zshrc.d foot; do if [ ! -d ~/.config/$dir ]; then cp -r /etc/skel/.config/$dir ~/.config/$dir chown -R $USER:$USER ~/.config/$dir @@ -12,9 +11,9 @@ for dir in fish hypr anyrun fuzzel Kvantum matugen vdirsyncer kde-material-you-c done # Create Sleex custom files -if [ ! -d ~/.config/hypr/custom ]; then - mkdir -p ~/.config/hypr/custom - cp -r /etc/skel/.config/hypr/custom/* ~/.config/hypr/custom +if [ ! -d ~/.config/fht/custom ]; then + mkdir -p ~/.config/fht/custom + cp -r /etc/skel/.config/fht/custom/* ~/.config/fht/custom fi if [ ! -d ~/.local/share/khal/calendars/default ]; then @@ -32,17 +31,19 @@ done # First run setup if [ ! -f ~/.local/state/sleex/user/first_run.txt ]; then # Sets up the user's environment to use Sleex. - CONFIG_FILE="$HOME/.config/hypr/custom/general.conf" + CONFIG_FILE="$HOME/.config/fht/custom/general.conf" VC_KEYMAP=$(grep -i KEYMAP /etc/vconsole.conf | cut -d= -f2 | tr -d '"') LAYOUT=$(echo "$VC_KEYMAP" | awk '{print tolower($0)}') echo "Setting keyboard layout to '$LAYOUT' in $CONFIG_FILE." - if grep -q "^input:kb_layout" "$CONFIG_FILE"; then - sed -i "s/^input:kb_layout.*/input:kb_layout = $LAYOUT/" "$CONFIG_FILE" + if grep -q "^\[input\.keyboard\]" "$CONFIG_FILE"; then + sed -i "/^\[input\.keyboard\]/,/^\[/ s/^layout = .*/layout = \"$LAYOUT\"/" "$CONFIG_FILE" else - echo "input:kb_layout = $LAYOUT" >> "$CONFIG_FILE" + echo "" >> "$CONFIG_FILE" + echo "[input.keyboard]" >> "$CONFIG_FILE" + echo "layout = \"$LAYOUT\"" >> "$CONFIG_FILE" fi if lsmod | grep -q '^nvidia'; then @@ -50,7 +51,7 @@ if [ ! -f ~/.local/state/sleex/user/first_run.txt ]; then { echo "env = LIBVA_DRIVER_NAME,nvidia" echo "env = __GLX_VENDOR_LIBRARY_NAME,nvidia" - } >> ~/.config/hypr/custom/env.conf + } >> ~/.config/fht/custom/env.conf fi fi @@ -60,5 +61,5 @@ if [ ! -f ~/.config/user-dirs.dirs ]; then fi -# Start Hyprland with the Sleex config -start-hyprland -- --config /etc/sleex/hyprland.conf > /dev/null 2>&1 +# Start with the Sleex config +fht-compositor -c /etc/sleex/compositor.toml > /dev/null 2>&1 \ No newline at end of file diff --git a/src/etc/sleex/compositor.toml b/src/etc/sleex/compositor.toml new file mode 100644 index 00000000..457348a5 --- /dev/null +++ b/src/etc/sleex/compositor.toml @@ -0,0 +1,10 @@ +imports = [ + "/etc/sleex/compositor/general.toml", + "/etc/sleex/compositor/keybinds.toml", + "/etc/sleex/compositor/rules.toml", + "/etc/sleex/compositor/execs.toml", + "/etc/sleex/compositor/envs.toml", + + "~/.config/fht/monitors.toml", + "~/.config/fht/apps.toml", +] diff --git a/src/etc/sleex/compositor/envs.toml b/src/etc/sleex/compositor/envs.toml new file mode 100644 index 00000000..bf8bf27e --- /dev/null +++ b/src/etc/sleex/compositor/envs.toml @@ -0,0 +1,19 @@ +[env] +# Themes +QT_QPA_PLATFORM = "wayland" +QT_QPA_PLATFORMTHEME = "kde" +QT_STYLE_OVERRIDE = "kvantum" +WLR_NO_HARDWARE_CURSORS = "1" + +XDG_SESSION_TYPE = "wayland" +GDK_BACKEND = "wayland" +SDL_VIDEODRIVER = "wayland" +CLUTTER_BACKEND = "wayland" +MOZ_ENABLE_WAYLAND = "1" + +# Screen tearing +# WLR_DRM_NO_ATOMIC, 1 + +# Others + +SLEEX_VIRTUAL_ENV = "~/.local/state/sleex/.venv" diff --git a/src/etc/sleex/compositor/execs.toml b/src/etc/sleex/compositor/execs.toml new file mode 100644 index 00000000..9cc51af4 --- /dev/null +++ b/src/etc/sleex/compositor/execs.toml @@ -0,0 +1,12 @@ +# Autostart programs. +autostart = [ + #"swww-daemon", + "qs -p /usr/share/sleex/", + "/usr/lib/geoclue-2.0/demos/agent & gammastep", + "gnome-keyring-daemon --start --components=secrets", + "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 || /usr/libexec/polkit-gnome-authentication-agent-1", + "dbus-update-activation-environment --all", + "sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP", + "wl-paste --type text --watch cliphist store", + "wl-paste --type image --watch cliphist store" +] diff --git a/src/etc/sleex/compositor/general.toml b/src/etc/sleex/compositor/general.toml new file mode 100644 index 00000000..d7a189ed --- /dev/null +++ b/src/etc/sleex/compositor/general.toml @@ -0,0 +1,64 @@ +# You can find everything you want by reading the wiki! +# https://nferhat.github.io/fht-compositor + +# Input configuration. +# SEE: man 7 xkeyboard-config +[input.keyboard] +layout = "us" +rules = "" +repeat-rate = 50 +repeat-delay = 250 +options = "numpad:mac" # This makes the numpad keys work as expected when numlock is off. + +[input.touchpad] +natural-scrolling = true + +[cursor] +name = "Bibata-Modern-Classic" +size = 24 + +# --------------------------------------------------------- + +# General compositor behaviour +[general] +cursor-warps = true # Move the cursor to newly opened windows +focus-new-windows = true # Give newly inserted windows keyboard focus +focus-follows-mouse = true # When true, hovering over a window will focus it without clicking + +layouts = ["tile", "floating"] +nmaster = 1 # Only one master client +mwfact = 0.5 # And share the screen size between the master and slave equally +inner-gaps = 7 # gaps between tiles +outer-gaps = 7 # gaps around the screen + +# --------------------------------------------------------- + +# Decorations: I.E the pretty +# All the decorations can be overriden on a per-window basis using window rules. +[decorations] +decoration-mode = "force-server-side" + +# Border around windows! +[decorations.border] +thickness = 0 +radius = 10 +focused-color = "#ffffff" +normal-color = "#222230" + +# Shadows! Very useful for floating windows. +[decorations.shadow] +color = "black" # by default its rgba(black, 0.5) +floating-only = false # Up to you, I don't judge! + +# Blur, because yes! +[decorations.blur] +disable = false +passes = 4 +radius = 5 +noise = 0.05 + +# --------------------------------------------------------- + +# Animations. Makes everything smooooooooooooooth! +[animations] +disable = false diff --git a/src/etc/sleex/compositor/keybinds.toml b/src/etc/sleex/compositor/keybinds.toml new file mode 100644 index 00000000..ca356d53 --- /dev/null +++ b/src/etc/sleex/compositor/keybinds.toml @@ -0,0 +1,106 @@ +# Lines ending with `# [hidden]` won't be shown on cheatsheet +# Lines starting with #! are section headings + +[keybinds] + +#! +##! Basic +Super-Shift-s = { action = "run-command", arg = 'grim -g "$(slurp -o)" - | wl-copy --type image/png' } # Screenshot a selected area and copy to clipboard + +#! +##! Multimedia +XF86AudioRaiseVolume = { action = "run-command", arg = "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+", allow-while-locked = true } # Increase volume by 5% +XF86AudioLowerVolume = { action = "run-command", arg = "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%-", allow-while-locked = true } # Decrease volume by 5% +XF86AudioMute = { action = "run-command", arg = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", allow-while-locked = true } # Toggle mute + +#! +##! Focus and window management +Super-Right = "focus-next-window" # Focus the next window in the current workspace +Super-Left = "focus-previous-window" # Focus the previous window in the current workspace + +Super-m = "maximize-focused-window" # Toggle maximize the focused window +Super-f = "fullscreen-focused-window" # Toggle fullscreen the focused window +Super-q = "close-focused-window" # Close the focused window +Super-Ctrl-Space = "float-focused-window" # Toggle floating the focused window +Super-Shift-Right = "swap-with-next-window" # Swap the focused window with the next window in the current workspace +Super-Shift-Left = "swap-with-previous-window" # Swap the focused window with the previous window in the current workspace +# Super-Ctrl-p = "pin-focused-window" + +# Transient layout changes. +Super-Space = "select-next-layout" # Cycle to the next layout in the current workspace +Super-Ctrl-Left = { action = "change-mwfact", arg = -0.1 } # Decrease the master area size by 10% +Super-Ctrl-Right = { action = "change-mwfact", arg = 0.1 } # Increase the master area size by 10% +Super-Shift-h = { action = "change-nmaster", arg = 1 } # Increase the number of windows in the master area by 1 +Super-Shift-l = { action = "change-nmaster", arg = -1 } # Decrease the number of windows in the master area by 1 +Super-i = { action = "change-window-proportion", arg = 0.5 } # Increase the size of the focused window +Super-o = { action = "change-window-proportion", arg = -0.5 } # Decrease the size of the focused window + +# Workspaces +#/# Super-[1-9] = { action = "focus-workspace", arg = N } # Focus workspace N (0-indexed) +Super-ampersand = { action = "focus-workspace", arg = 0 } # [hidden] +Super-eacute = { action = "focus-workspace", arg = 1 } # [hidden] +Super-quotedbl = { action = "focus-workspace", arg = 2 } # [hidden] +Super-apostrophe = { action = "focus-workspace", arg = 3 } # [hidden] +Super-parenleft = { action = "focus-workspace", arg = 4 } # [hidden] +Super-minus = { action = "focus-workspace", arg = 5 } # [hidden] +Super-egrave = { action = "focus-workspace", arg = 6 } # [hidden] +Super-underscore = { action = "focus-workspace", arg = 7 } # [hidden] +Super-agrave = { action = "focus-workspace", arg = 8 } # [hidden] +# For QWERTY users +Super-1 = { action = "focus-workspace", arg = 0 } # [hidden] +Super-2 = { action = "focus-workspace", arg = 1 } # [hidden] +Super-3 = { action = "focus-workspace", arg = 2 } # [hidden] +Super-4 = { action = "focus-workspace", arg = 3 } # [hidden] +Super-5 = { action = "focus-workspace", arg = 4 } # [hidden] +Super-6 = { action = "focus-workspace", arg = 5 } # [hidden] +Super-7 = { action = "focus-workspace", arg = 6 } # [hidden] +Super-8 = { action = "focus-workspace", arg = 7 } # [hidden] +Super-9 = { action = "focus-workspace", arg = 8 } # [hidden] + +# Sending windows to workspaces +#/#! Super-Shift-[1-9] = { action = "send-to-workspace", arg = N } # Send the focused window to workspace N (0-indexed) +Super-Ctrl-ampersand = { action = "send-to-workspace", arg = 0 } # [hidden] +Super-Ctrl-eacute = { action = "send-to-workspace", arg = 1 } # [hidden] +Super-Ctrl-quotedbl = { action = "send-to-workspace", arg = 2 } # [hidden] +Super-Ctrl-apostrophe = { action = "send-to-workspace", arg = 3 } # [hidden] +Super-Ctrl-parenleft = { action = "send-to-workspace", arg = 4 } # [hidden] +Super-Ctrl-minus = { action = "send-to-workspace", arg = 5 } # [hidden] +Super-Ctrl-egrave = { action = "send-to-workspace", arg = 6 } # [hidden] +Super-Ctrl-underscore = { action = "send-to-workspace", arg = 7 } # [hidden] +Super-Ctrl-agrave = { action = "send-to-workspace", arg = 8 } # [hidden] +# For QWERTY users +Super-Ctrl-1 = { action = "send-to-workspace", arg = 0 } # [hidden] +Super-Ctrl-2 = { action = "send-to-workspace", arg = 1 } # [hidden] +Super-Ctrl-3 = { action = "send-to-workspace", arg = 2 } # [hidden] +Super-Ctrl-4 = { action = "send-to-workspace", arg = 3 } # [hidden] +Super-Ctrl-5 = { action = "send-to-workspace", arg = 4 } # [hidden] +Super-Ctrl-6 = { action = "send-to-workspace", arg = 5 } # [hidden] +Super-Ctrl-7 = { action = "send-to-workspace", arg = 6 } # [hidden] +Super-Ctrl-8 = { action = "send-to-workspace", arg = 7 } # [hidden] +Super-Ctrl-9 = { action = "send-to-workspace", arg = 8 } # [hidden] + +#! +##! Shell +Super-d = { action = "global-shortcut", arg = "quickshell:dashboardToggle" } # Toggle the dashboard +Super-Super_L = { action = "global-shortcut", arg = "quickshell:overviewToggleReleaseInterrupt" } # Toggle the overview +Super-v = { action = "global-shortcut", arg = "quickshell:overviewClipboardToggle" } # Toggle the clipboard overview +Super-semicolon = { action = "global-shortcut", arg = "quickshell:overviewEmojiToggle" } # Toggle the emoji overview +Super-f1 = { action = "global-shortcut", arg = "quickshell:cheatsheetToggle" } # Toggle the cheatsheet +Ctrl-Alt-Delete = { action = "global-shortcut", arg = "quickshell:sessionToggle" } # Toggle the session overview +Super-t = { action = "global-shortcut", arg = "quickshell:wppselectorToggle" } # Toggle the wpp selector +Super-l = { action = "global-shortcut", arg = "quickshell:lockScreen" } # Lock the screen + +# --------------------------------------------------------- + +## [hidden] +[mousebinds] +Super-Left = "swap-tile" +Super-Right = "resize-tile" +Super-scrollup = "focus-next-workspace" +Super-scrolldown = "focus-previous-workspace" + +[gesturebinds] +focus-next-workspace = { fingers = 4, direction = "left" } +focus-previous-workspace = { fingers = 4, direction = "right" } +fullscreen-focused-window = { fingers = 4, direction = "up" } +float-focused-window = { fingers = 4, direction = "down" } diff --git a/src/etc/sleex/compositor/rules.toml b/src/etc/sleex/compositor/rules.toml new file mode 100644 index 00000000..38feb13e --- /dev/null +++ b/src/etc/sleex/compositor/rules.toml @@ -0,0 +1,52 @@ +# Window rules +[[rules]] +match-app-id = [ + "blueberry.py", + "com.axos-project.axinstall", + "steam", + "org.pulseaudio.pavucontrol", + "cheese", + "timeshift-gtk", + "org.gnome.PowerStats", + "lstopo", + "org.gnome.ColorProfileViewer", + "qt5ct", + "org.kde.plasmawindowed", + "kcm.networkmanagement", + "gcr-prompter" + ] +match-title = [ + "Open File", + "Select a File", + "Choose wallpaper", + "Open Folder", + "Save File", + "Save As", + "Library", + "File Upload", + "Sleex Settings", + "Calculator", + "BoxBuddy", + "Popsicle", + "Trayscale", + "Choose Files", + "Save Image", + "Save File" +] +floating = true +centered = true + +[[rules]] +is-floating = true +ontop = true + +[[rules]] +match-app-id = ["org.gnome.Calculator"] +# pinned = true +floating = true +centered = true + +# Layer-shell rules +#[[layer-rules]] +#match-namespace = ["quickshell:*"] +#blur = { disable = false, noise = 0, passes = 4, radius = 1 } diff --git a/src/etc/sleex/hyprland.conf b/src/etc/sleex/hyprland.conf deleted file mode 100644 index ef63b2f2..00000000 --- a/src/etc/sleex/hyprland.conf +++ /dev/null @@ -1,23 +0,0 @@ -# This file sources other files in `hyprland` and `custom` folders -# You wanna add your stuff in file in `custom` -exec = hyprctl dispatch submap global -submap = global -# Defaults -source=/etc/sleex/hyprland/env.conf -source=/etc/sleex/hyprland/execs.conf -source=/etc/sleex/hyprland/general.conf -source=/etc/sleex/hyprland/rules.conf -source=/etc/sleex/hyprland/colors.conf -source=/etc/sleex/hyprland/keybinds.conf - -# Custom -source=~/.config/hypr/custom/env.conf -source=~/.config/hypr/custom/execs.conf -source=~/.config/hypr/custom/general.conf -source=~/.config/hypr/custom/rules.conf -source=~/.config/hypr/custom/keybinds.conf - -source=~/.config/hypr/monitors.conf - -# Applications bindings -source=~/.config/hypr/apps.conf \ No newline at end of file diff --git a/src/etc/sleex/hyprland/colors.conf b/src/etc/sleex/hyprland/colors.conf deleted file mode 100644 index eb97b9e5..00000000 --- a/src/etc/sleex/hyprland/colors.conf +++ /dev/null @@ -1,32 +0,0 @@ -general { - col.active_border = rgba(dfe3e739) - col.inactive_border = rgba(8b919830) -} - -misc { - background_color = rgba(101417FF) -} - -plugin { - hyprbars { - # Honestly idk if it works like css, but well, why not - bar_text_font = Rubik, Geist, AR One Sans, Reddit Sans, Inter, Roboto, Ubuntu, Noto Sans, sans-serif - bar_height = 30 - bar_padding = 10 - bar_button_padding = 5 - bar_precedence_over_border = true - bar_part_of_window = true - - bar_color = rgba(101417FF) - col.text = rgba(dfe3e7FF) - - - # example buttons (R -> L) - # hyprbars-button = color, size, on-click - hyprbars-button = rgb(dfe3e7), 13, 󰖭, hyprctl dispatch killactive - hyprbars-button = rgb(dfe3e7), 13, 󰖯, hyprctl dispatch fullscreen 1 - hyprbars-button = rgb(dfe3e7), 13, 󰖰, hyprctl dispatch movetoworkspacesilent special - } -} - -windowrule = border_color rgba(93cdf6AA) rgba(93cdf677), match:pin true diff --git a/src/etc/sleex/hyprland/env.conf b/src/etc/sleex/hyprland/env.conf deleted file mode 100644 index 938e0605..00000000 --- a/src/etc/sleex/hyprland/env.conf +++ /dev/null @@ -1,28 +0,0 @@ -# ######### Input method ########## -# See https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland -env = QT_IM_MODULE, fcitx -env = XMODIFIERS, @im=fcitx -# env = GTK_IM_MODULE, wayland # Crashes electron apps in xwayland -env = SDL_IM_MODULE, fcitx -env = GLFW_IM_MODULE, ibus -env = INPUT_METHOD, fcitx - -# ############ Themes ############# -env = QT_QPA_PLATFORM, wayland -env = QT_QPA_PLATFORMTHEME, qt6ct -env = QT_STYLE_OVERRIDE, kvantum -# env = WLR_NO_HARDWARE_CURSORS, 1 - -env = XDG_SESSION_TYPE, wayland -env = GDK_BACKEND, wayland -env = QT_QPA_PLATFORM, wayland -env = SDL_VIDEODRIVER, wayland -env = CLUTTER_BACKEND, wayland -env = MOZ_ENABLE_WAYLAND, 1 - -# ######## Screen tearing ######### -# env = WLR_DRM_NO_ATOMIC, 1 - -# ############ Others ############# - -env = SLEEX_VIRTUAL_ENV, ~/.local/state/sleex/.venv diff --git a/src/etc/sleex/hyprland/execs.conf b/src/etc/sleex/hyprland/execs.conf deleted file mode 100644 index b8119262..00000000 --- a/src/etc/sleex/hyprland/execs.conf +++ /dev/null @@ -1,18 +0,0 @@ -# Bar, wallpaper -# exec-once = swww-daemon -exec-once = qs -p /usr/share/sleex & - -# Core components (authentication, lock screen, notification daemon) -exec-once = dbus-update-activation-environment --all -exec-once = sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Some fix idk - -# Audio -exec-once = easyeffects --gapplication-service - -# Clipboard: history -# exec-once = wl-paste --watch cliphist store & -exec-once = wl-paste --type text --watch cliphist store -exec-once = wl-paste --type image --watch cliphist store - -# Cursor -exec-once = hyprctl setcursor Bibata-Modern-Classic 24 diff --git a/src/etc/sleex/hyprland/general.conf b/src/etc/sleex/hyprland/general.conf deleted file mode 100644 index e1cbeb17..00000000 --- a/src/etc/sleex/hyprland/general.conf +++ /dev/null @@ -1,144 +0,0 @@ -input { - # Keyboard: Add a layout and uncomment kb_options for Win+Space switching shortcut - kb_layout = us - # kb_options = grp:win_space_toggle - numlock_by_default = true - repeat_delay = 250 - repeat_rate = 35 - - touchpad { - natural_scroll = yes - disable_while_typing = true - clickfinger_behavior = true - scroll_factor = 0.5 - } - special_fallthrough = true - follow_mouse = 1 -} - -binds { - # focus_window_on_workspace_c# For Auto-run stuff see execs.confhange = true - scroll_event_delay = 0 -} - -gestures { - workspace_swipe_distance = 700 - workspace_swipe_cancel_ratio = 0.2 - workspace_swipe_min_speed_to_force = 5 - workspace_swipe_direction_lock = true - workspace_swipe_direction_lock_threshold = 10 - workspace_swipe_create_new = true -} - -gesture = 4, horizontal, workspace -gesture = 4, vertical, fullscreen - -general { - # Gaps and border - gaps_in = 2 - gaps_out = 4 - gaps_workspaces = 50 - border_size = 1 - - # Fallback colors - col.active_border = rgba(0DB7D4FF) - col.inactive_border = rgba(31313600) - - resize_on_border = true - no_focus_fallback = true - layout = dwindle - - #focus_to_other_workspaces = true # ahhhh i still haven't properly implemented this - allow_tearing = true # This just allows the `immediate` window rule to work -} - -dwindle { - preserve_split = true - # no_gaps_when_only = 1 - smart_split = false - smart_resizing = false -} - -decoration { - rounding = 7 - - blur { - enabled = true - xray = true - special = false - new_optimizations = true - size = 5 - passes = 4 - brightness = 1 - noise = 0.01 - contrast = 1 - popups = true - popups_ignorealpha = 0.6 - } - - # Dim - dim_inactive = false - dim_strength = 0.1 - dim_special = 0 -} - -animations { - enabled = true - # Animation curves - - bezier = linear, 0, 0, 1, 1 - bezier = md3_standard, 0.2, 0, 0, 1 - bezier = md3_decel, 0.05, 0.7, 0.1, 1 - bezier = md3_accel, 0.3, 0, 0.8, 0.15 - bezier = overshot, 0.05, 0.9, 0.1, 1.1 - bezier = crazyshot, 0.1, 1.5, 0.76, 0.92 - bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0 - bezier = menu_decel, 0.1, 1, 0, 1 - bezier = menu_accel, 0.38, 0.04, 1, 0.07 - bezier = easeInOutCirc, 0.85, 0, 0.15, 1 - bezier = easeOutCirc, 0, 0.55, 0.45, 1 - bezier = easeOutExpo, 0.16, 1, 0.3, 1 - bezier = softAcDecel, 0.26, 0.26, 0.15, 1 - bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration - # Animation configs - animation = windows, 1, 3, md3_decel, popin 60% - animation = windowsIn, 1, 3, md3_decel, popin 60% - animation = windowsOut, 1, 3, md3_accel, popin 60% - animation = border, 1, 10, default - animation = fade, 1, 3, md3_decel - # animation = layers, 1, 2, md3_decel, slide - animation = layersIn, 1, 3, menu_decel, slide - animation = layersOut, 1, 1.6, menu_accel - animation = fadeLayersIn, 1, 2, menu_decel - animation = fadeLayersOut, 1, 4.5, menu_accel - animation = workspaces, 1, 7, menu_decel, slide - # animation = workspaces, 1, 2.5, softAcDecel, slide - # animation = workspaces, 1, 7, menu_decel, slidefade 15% - # animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15% - animation = specialWorkspace, 1, 3, md3_decel, slidevert -} - -misc { - vfr = 1 - vrr = 1 - animate_manual_resizes = false - animate_mouse_windowdragging = false - enable_swallow = false - swallow_regex = (foot|kitty|allacritty|Alacritty) - - disable_hyprland_logo = true - force_default_wallpaper = 0 - on_focus_under_fullscreen = 2 - allow_session_lock_restore = true - - initial_workspace_tracking = false - - disable_xdg_env_checks = true # Because Hyprland doesn't seems to like Sleex - - session_lock_xray = true -} - -ecosystem { - no_update_news = true - no_donation_nag = true -} diff --git a/src/etc/sleex/hyprland/keybinds.conf b/src/etc/sleex/hyprland/keybinds.conf deleted file mode 100644 index 5a87f549..00000000 --- a/src/etc/sleex/hyprland/keybinds.conf +++ /dev/null @@ -1,155 +0,0 @@ -# Lines ending with `# [hidden]` won't be shown on cheatsheet -# Lines starting with #! are section headings - - -#! -##! Shell -# These absolutely need to be on top, or they won't work consistently -bindid = Super, Super_L, Toggle overview, global, quickshell:overviewToggleRelease # Toggle overview/launcher -binditn = Super, catchall, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Ctrl, Super_L, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:272, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:273, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:274, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:275, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:276, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse:277, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt # [hidden] -bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden] - -bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden], -bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard -bindd = Super, semicolon, Emoji >> clipboard, global, quickshell:overviewEmojiToggle # Emoji >> clipboard -bindd = Super, D, Toggle dashboard, global, quickshell:dashboardToggle # Toggle dashboard -bindd = Super, F1, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet -bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu -bindd = Super, T, Toggle wallpaper selector, global, quickshell:wppselectorToggle # Toggle wallpaper selector - -binde = Ctrl+Super, Minus, exec, qs -p /usr/share/sleex/ ipc call zoom zoomOut # Zoom out -binde = Ctrl+Super, Equal, exec, qs -p /usr/share/sleex/ ipc call zoom zoomIn # Zoom in - -# Debug -bind = Super+Alt, F12, exec, notify-send 'Test notification' "Here's a really long message to test truncation and wrapping\nYou can middle click or flick this notification to dismiss it!" -a 'Shell' -A "Test1=I got it!" -A "Test2=Another action" -t 5000 # [hidden] -bind = Super+Alt, Equal, exec, notify-send "Urgent notification" "Ah hell no" -u critical -a 'Hyprland keybind' # [hidden] - -bindl = Super ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_SOURCE@ toggle # Mute the mic -bindl = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle # Mute the sink -bindle=, XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ # Increase the volume -bindle=, XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- # Decrease the volume -bindle=, XF86MonBrightnessUp, exec, qs -p /usr/share/sleex/ ipc call brightness increment # [hidden] -bindle=, XF86MonBrightnessDown, exec, qs -p /usr/share/sleex/ ipc call brightness decrement # [hidden] - -# bindle = , XF86KbdBrightnessUp, exec, brightnessctl -d 'white:kbd_backlight' set '1+' -# bindle = , XF86KbdBrightnessDown, exec, brightnessctl -d 'white:kbd_backlight' set '1-' -# bindl = , XF86KbLightOnOff, exec, brightnessctl -d 'white:kbd_backlight' set 0 - -#! -##! Essentials for beginners - -bind = Super, Return, exec, foot # Launch foot (terminal) -bind = , Super, exec, true # Open app launcher -bind = Ctrl+Super, T, exec, /usr/share/sleex/scripts/colors/switchwall.sh # Change wallpaper - -##! Actions - -# OCR -bind = Super+Shift,T,exec,grim -g "$(slurp $SLURP_ARGS)" "tmp.png" && tesseract "tmp.png" - | wl-copy && rm "tmp.png" # Screen snip to text >> clipboard - -# Color picker -bind = Super+Shift, X, exec, hyprpicker -a # Pick color (Hex) >> clipboard - -# Screenshot -bindl=,Print,exec,grim - | wl-copy # Screenshot >> clipboard -bindl= Ctrl,Print, exec, mkdir -p ~/Pictures/Screenshots && /usr/share/sleex/scripts/grimblast.sh copysave screen ~/Pictures/Screenshots/Screenshot_"$(date '+%Y-%m-%d_%H.%M.%S')".png # Screenshot >> clipboard & file -bind = Super+Shift+Alt, S, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip >> edit -bind = Super+Shift, S, exec, /usr/share/sleex/scripts/grimblast.sh --freeze copy area # Screen snip - -# Recording stuff -bind = Super+Alt, R, exec, /usr/share/sleex/scripts/record-script.sh # Record region (no sound) -bind = Ctrl+Alt, R, exec, /usr/share/sleex/scripts/record-script.sh --fullscreen # Record screen (no sound) -bind = Super+Shift+Alt, R, exec, /usr/share/sleex/scripts/record-script.sh --fullscreen-sound # Record screen (with sound) - -##! Session -# bind = Super, L, exec, hyprlock # Lock session -bind = Super, L, global, quickshell:lockScreen # Lock session -bind = Super, End, exec, pkill qs && qs -p /usr/share/sleex/shell.qml # Restart Shell -bind = Super+Ctrl, End, exec, qs -p /usr/share/sleex/shell.qml # Start Shell - -#! -##! Window management -# Focusing -#/# bind = Super, ←/↑/→/↓,, # Move focus in direction -bind = Super, Left, movefocus, l # [hidden] -bind = Super, Right, movefocus, r # [hidden] -bind = Super, Up, movefocus, u # [hidden] -bind = Super, Down, movefocus, d # [hidden] -bind = Super, Q, killactive, # Close an app -bind = Super+Shift+Alt, Q, exec, hyprctl kill # Pick and kill a window - -##! Window arrangement -# Positioning mode -bind = Super+Alt, Space, togglefloating, # Toggle floating -bind = Super, F, fullscreen, -bindm = Super, mouse:272, movewindow -bindm = Super, mouse:273, resizewindow - -# Toggle focused window split -$d=[$wm] -bindd = Super, J, $d toggle split, togglesplit # Toggle split - -#! -##! Workspace navigation -# Switching -#/# bind = Super, Hash,, # Focus workspace # (1, 2, 3, 4, ...) -bind = Super, code:10, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 1 # [hidden] -bind = Super, code:11, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 2 # [hidden] -bind = Super, code:12, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 3 # [hidden] -bind = Super, code:13, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 4 # [hidden] -bind = Super, code:14, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 5 # [hidden] -bind = Super, code:15, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 6 # [hidden] -bind = Super, code:16, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 7 # [hidden] -bind = Super, code:17, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 8 # [hidden] -bind = Super, code:18, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 9 # [hidden] -bind = Super, code:19, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh workspace 10 # [hidden] - - -#/# bind = Ctrl+Super, ←/→,, # Workspace: focus left/right -bind = Ctrl+Super, Right, workspace, +1 # [hidden] -bind = Ctrl+Super, Left, workspace, -1 # [hidden] - -#/# bind = Super, Scroll ↑/↓,, # Workspace: focus left/right -bind = Super, mouse_up, workspace, +1 # [hidden] -bind = Super, mouse_down, workspace, -1 # [hidden] - -#/# bind = Super, Page_↑/↓,, # Workspace: focus left/right -bind = Super, Page_Down, workspace, +1 # [hidden] -bind = Super, Page_Up, workspace, -1 # [hidden] - -##! Workspace management -# Move window to workspace Super + Ctrl + [0-9] -#/# bind = Super+Ctrl, Hash,, # Window: move to workspace # (1, 2, 3, 4, ...) -bind = Super+Ctrl, code:10, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 1 # [hidden] -bind = Super+Ctrl, code:11, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 2 # [hidden] -bind = Super+Ctrl, code:12, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 3 # [hidden] -bind = Super+Ctrl, code:13, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 4 # [hidden] -bind = Super+Ctrl, code:14, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 5 # [hidden] -bind = Super+Ctrl, code:15, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 6 # [hidden] -bind = Super+Ctrl, code:16, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 7 # [hidden] -bind = Super+Ctrl, code:17, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 8 # [hidden] -bind = Super+Ctrl, code:18, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 9 # [hidden] -bind = Super+Ctrl, code:19, exec, /usr/share/sleex/scripts/hyprland/workspace_action.sh movetoworkspacesilent 10 # [hidden] - - -bind = Ctrl+Super+Shift, Right, movetoworkspace, +1 # [hidden] -bind = Ctrl+Super+Shift, Left, movetoworkspace, -1 # [hidden] - -#/# bind = Super+Shift, Scroll ↑/↓,, # Window: move to workspace left/right -bind = Super+Shift, mouse_down, movetoworkspace, -1 # [hidden] -bind = Super+Shift, mouse_up, movetoworkspace, +1 # [hidden] - - -##! Media -bindl= ,XF86AudioNext, exec, playerctl next || playerctl position `bc <<< "100 * $(playerctl metadata mpris:length) / 1000000 / 100"` # Next track -bindl= ,XF86AudioPrev, exec, playerctl previous # Previous track -bindl= ,XF86AudioPlay, exec, playerctl play-pause # Media play/pause -bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] diff --git a/src/etc/sleex/hyprland/rules.conf b/src/etc/sleex/hyprland/rules.conf deleted file mode 100644 index 73214511..00000000 --- a/src/etc/sleex/hyprland/rules.conf +++ /dev/null @@ -1,104 +0,0 @@ -# ######## Window rules ######## -# windowrule = noblur,.* -windowrule = match:class ^(blueberry.py)$, float on -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, float on -windowrule = match:class ^(com.axos-project.axinstall)$, float on -windowrule = match:title ^(Open File)(.*)$, center on -windowrule = match:title ^(Select a File)(.*)$, center on -windowrule = match:title ^(Choose wallpaper)(.*)$, center on -windowrule = match:title ^(Open Folder)(.*)$, center on -windowrule = match:title ^(Save As)(.*)$, center on -windowrule = match:title ^(Library)(.*)$, center on -windowrule = match:title ^(File Upload)(.*)$, center on - -# Dialogs -windowrule=match:title ^(Open File)(.*)$, float on -windowrule=match:title ^(Select a File)(.*)$, float on -windowrule=match:title ^(Choose wallpaper)(.*)$, float on -windowrule=match:title ^(Open Folder)(.*)$, float on -windowrule=match:title ^(Save As)(.*)$, float on -windowrule=match:title ^(Library)(.*)$, float on -windowrule=match:title ^(File Upload)(.*)$, float on -#windowrule=match:title ^(Sleex Settings)$, float on -windowrule=match:title ^(Calculator)$, float on -windowrule=match:title ^(BoxBuddy)$, float on -windowrule=match:title ^(Popsicle)$, float on -windowrule=match:title ^(Trayscale)$, float on -windowrule=match:class ^(org.pulseaudio.pavucontrol)$, float on -windowrule=match:class ^(org.pulseaudio.pavucontrol)$, size (monitor_w*.45) (monitor_h*.45) -windowrule=match:class ^(org.pulseaudio.pavucontrol)$, center on -windowrule=match:class ^(cheese)$, float on -windowrule=match:class ^(timeshift-gtk)$, float on -windowrule=match:class ^(lstopo)$, float on -windowrule=match:class ^(org.gnome.ColorProfileViewer)$, float on -windowrule=match:class ^(qt5ct)$, float on -windowrule=match:class ^(rustdesk)$, float on -windowrule=match:title ^(Choose Files)$, float on -windowrule=match:title ^(Save Image)$, float on -windowrule=match:title ^(Save File)$, float on -windowrule=match:class ^(org.kde.plasmawindowed)$, float on -windowrule=match:class ^(kcm.networkmanagement)$, float on - -# Tearing -windowrule = match:title .*\.exe, immediate on -windowrule = match:title .*minecraft.*, immediate on -windowrule = match:class ^(steam_app).*, immediate on - -# No shadow for tiled windows -windowrule = match:float 0, no_shadow on - -# ######## Layer rules ######## -layerrule = match:namespace .*, xray on -# layerrule = match:namespace .*, no_anim on -layerrule = match:namespace walker, no_anim on -layerrule = match:namespace selection, no_anim on -layerrule = match:namespace overview, no_anim on -layerrule = match:namespace anyrun, no_anim on -layerrule = match:namespace indicator.*, no_anim on -layerrule = match:namespace osk, no_anim on -layerrule = match:namespace hyprpicker, no_anim on - -layerrule = match:namespace noanim, no_anim on -layerrule = match:namespace gtk-layer-shell, blur on -layerrule = match:namespace gtk-layer-shell, ignore_alpha 0 -layerrule = match:namespace launcher, blur on -layerrule = match:namespace launcher, ignore_alpha 0.5 -layerrule = match:namespace notifications, blur on -layerrule = match:namespace notifications, ignore_alpha 0.69 -layerrule = match:namespace logout_dialog # wlogout, blur on - -# Picture-in-Picture -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, float on -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, keep_aspect_ratio on -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, move (monitor_w*.73) (monitor_h*.72) -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, size (monitor_w*.25) (monitor_h*.25) -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, float on -windowrule = match:title ^([Pp]icture[-\s]?[Ii]n[-\s]?[Pp]icture)(.*)$, pin on - - -# Shell -layerrule = match:namespace quickshell:.*, blur_popups on -layerrule = match:namespace quickshell:.*, blur on -layerrule = match:namespace quickshell:.*, ignore_alpha 0.2 -layerrule = match:namespace quickshell:bar, animation slide -layerrule = match:namespace quickshell:cheatsheet, animation slide bottom -layerrule = match:namespace quickshell:dock, animation slide bottom -layerrule = match:namespace quickshell:lockWindowPusher, no_anim on -layerrule = match:namespace quickshell:notificationPopup, animation fade -layerrule = match:namespace quickshell:overlay, no_anim on -layerrule = match:namespace quickshell:overlay, ignore_alpha 1 -layerrule = match:namespace quickshell:overview, animation slide top -layerrule = match:namespace quickshell:osk, animation slide bottom -layerrule = match:namespace quickshell:polkit, no_anim on -layerrule = match:namespace quickshell:popup, xray off # No weird color for bar tooltips (this in theory should suffice) -layerrule = match:namespace quickshell:popup, ignore_alpha 1 # No weird color for bar tooltips (but somehow this is necessary) -layerrule = match:namespace quickshell:reloadPopup, animation slide -layerrule = match:namespace quickshell:regionSelector, no_anim on -layerrule = match:namespace quickshell:screenshot, no_anim on -layerrule = match:namespace quickshell:session, blur on -layerrule = match:namespace quickshell:session, ignore_alpha 0 -layerrule = match:namespace quickshell:dashboard, animation slide bottom -layerrule = match:namespace quickshell:wallpaperSelector, animation slide top -layerrule = match:namespace quickshell:batterywarning, blur off -layerrule = match:namespace quickshell:batterywarning, ignore_alpha 1 -layerrule = match:namespace quickshell:batterywarning, no_anim on diff --git a/src/share/libalpm/hooks/sleex-hyprctl-reload.hook b/src/share/libalpm/hooks/sleex-hyprctl-reload.hook deleted file mode 100644 index a1351b4d..00000000 --- a/src/share/libalpm/hooks/sleex-hyprctl-reload.hook +++ /dev/null @@ -1,12 +0,0 @@ -## Copyright (C) 2025 Ardox - -[Trigger] -Operation = Install -Operation = Upgrade -Type = Package -Target = sleex - -[Action] -Description = reloading hyprctl configuration -When = PostTransaction -Exec = /usr/bin/sleep 5 && /usr/bin/hyprctl reload & diff --git a/src/share/sleex/GlobalStates.qml b/src/share/sleex/GlobalStates.qml index 68c1a47a..779cf39b 100644 --- a/src/share/sleex/GlobalStates.qml +++ b/src/share/sleex/GlobalStates.qml @@ -18,13 +18,13 @@ Singleton { property bool wppselectorOpen: false property bool screenLocked: false - property real screenZoom: 1 - onScreenZoomChanged: { - Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", `${root.screenZoom.toString()}`]); - } - Behavior on screenZoom { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } + // property real screenZoom: 1 + // onScreenZoomChanged: { + // Quickshell.execDetached(["hyprctl", "keyword", "cursor:zoom_factor", `${root.screenZoom.toString()}`]); + // } + // Behavior on screenZoom { + // animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + // } // When user is not reluctant while pressing super, they probably don't need to see workspace numbers onSuperReleaseMightTriggerChanged: { @@ -54,17 +54,17 @@ Singleton { } } - IpcHandler { - target: "zoom" + // IpcHandler { + // target: "zoom" - function zoomIn() { - screenZoom = Math.min(screenZoom + 0.4, 3.0) - } + // function zoomIn() { + // screenZoom = Math.min(screenZoom + 0.4, 3.0) + // } - function zoomOut() { - screenZoom = Math.max(screenZoom - 0.4, 1) - } - } + // function zoomOut() { + // screenZoom = Math.max(screenZoom - 0.4, 1) + // } + // } IpcHandler { diff --git a/src/share/sleex/greetd.qml b/src/share/sleex/greetd.qml index 71ebd7f5..5f09972b 100755 --- a/src/share/sleex/greetd.qml +++ b/src/share/sleex/greetd.qml @@ -100,18 +100,17 @@ ShellRoot { color: "transparent" WlrLayershell.exclusionMode: ExclusionMode.Ignore - Image { + WallpaperDisplay { id: backgroundImage anchors.fill: parent source: Config.options.background.wallpaperPath - fillMode: Image.PreserveAspectCrop - GaussianBlur { - anchors.fill: parent - source: backgroundImage - radius: 10 - opacity: parent.opacity - } + // GaussianBlur { + // anchors.fill: parent + // source: backgroundImage + // radius: 10 + // opacity: parent.opacity + // } } RippleButton { diff --git a/src/share/sleex/modules/background/Background.qml b/src/share/sleex/modules/background/Background.qml index 37a2870a..c9955149 100644 --- a/src/share/sleex/modules/background/Background.qml +++ b/src/share/sleex/modules/background/Background.qml @@ -10,6 +10,7 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Wayland +import Sleex.Utils Scope { id: root @@ -184,6 +185,8 @@ Scope { Watermark { visibleWatermark: Config.options.background.showWatermark } Quote { visibleQuote: Config.options.background.enableQuote } + PostIt { id: postItManager; z: 50 } + } } } diff --git a/src/share/sleex/modules/background/BackgroundContextMenu.qml b/src/share/sleex/modules/background/BackgroundContextMenu.qml index 58b1d3fb..7efb9713 100755 --- a/src/share/sleex/modules/background/BackgroundContextMenu.qml +++ b/src/share/sleex/modules/background/BackgroundContextMenu.qml @@ -2,7 +2,6 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets -import Quickshell.Hyprland import qs.modules.common import qs.modules.common.widgets import qs.modules.common.functions @@ -112,7 +111,7 @@ Item { } onClicked: { - Hyprland.dispatch("global quickshell:sessionOpen") + Quickshell.execDetached(["qs", "-p", "/usr/share/sleex", "ipc", "call", "session", "toggle"]) bgMenu.close() } } @@ -142,6 +141,32 @@ Item { bgMenu.close() } } + + Rectangle { + Layout.fillWidth: true + implicitHeight: 1 + color: Appearance.m3colors.m3outlineVariant + Layout.topMargin: 4 + Layout.bottomMargin: 4 + } + + RippleButton { + Layout.fillWidth: true + buttonRadius: popupBackground.radius - popupBackground.padding + + contentItem: RowLayout { + spacing: 8 + anchors.fill: parent + anchors.margins: 12 + MaterialSymbol { text: "note_add"; iconSize: 20 } + StyledText { text: "New Post-it"; Layout.fillWidth: true } + } + + onClicked: { + postItManager.createNote(bgMenu.menuX, bgMenu.menuY) + bgMenu.close() + } + } } } diff --git a/src/share/sleex/modules/background/DesktopIcons.qml b/src/share/sleex/modules/background/DesktopIcons.qml index 10cbe8c7..14ef13c6 100644 --- a/src/share/sleex/modules/background/DesktopIcons.qml +++ b/src/share/sleex/modules/background/DesktopIcons.qml @@ -24,6 +24,7 @@ Item { DesktopModel { id: desktopModel + rows: Math.max(1, Math.floor(gridArea.height / cellHeight)) Component.onCompleted: loadDirectory(FileUtils.trimFileProtocol(Directories.desktop)) } diff --git a/src/share/sleex/modules/background/PostIt.qml b/src/share/sleex/modules/background/PostIt.qml new file mode 100644 index 00000000..c3ef1fe2 --- /dev/null +++ b/src/share/sleex/modules/background/PostIt.qml @@ -0,0 +1,71 @@ +import QtQuick +import Sleex.Utils +import qs.services + +Item { + id: postItManager + anchors.fill: parent + z: 50 + + ListModel { + id: postItModel + Component.onCompleted: { + let notes = DesktopStateManager.getPostIts() + for (let i = 0; i < notes.length; i++) { + postItModel.append(notes[i]) + } + } + } + + function saveNotes() { + let arr = [] + for(let i = 0; i < postItModel.count; i++) { + let item = postItModel.get(i) + arr.push({ + posX: item.posX, + posY: item.posY, + text: item.text + }) + } + DesktopStateManager.savePostIts(arr) + } + + function createNote(spawnX, spawnY) { + postItModel.append({ + "posX": spawnX, + "posY": spawnY, + "text": "" + }) + saveNotes() + } + + Repeater { + model: postItModel + delegate: PostItDelegate { + required property real posX + required property real posY + required property string text + required property int index + + x: posX + y: posY + noteText: text + + onDeleteRequested: { + postItModel.remove(index) + postItManager.saveNotes() + } + + onPositionUpdated: (newX, newY) => { + postItModel.setProperty(index, "posX", newX) + postItModel.setProperty(index, "posY", newY) + postItManager.saveNotes() + } + + onTextUpdated: (newText) => { + postItModel.setProperty(index, "text", newText) + postItManager.saveNotes() + } + } + } +} \ No newline at end of file diff --git a/src/share/sleex/modules/background/PostItDelegate.qml b/src/share/sleex/modules/background/PostItDelegate.qml new file mode 100644 index 00000000..6a3cfab5 --- /dev/null +++ b/src/share/sleex/modules/background/PostItDelegate.qml @@ -0,0 +1,78 @@ +import QtQuick +import QtQuick.Controls +import qs.modules.common.widgets +import qs.modules.common + +Rectangle { + id: postitRoot + width: 200 + height: 200 + color: "#fdfd96" + border.color: "#d9d97c" + border.width: 1 + radius: 4 + + property string noteText: "" + signal deleteRequested() + signal textUpdated(string newText) + signal positionUpdated(real newX, real newY) + + DragHandler { + target: postitRoot + cursorShape: active ? Qt.ClosedHandCursor : Qt.OpenHandCursor + + onActiveChanged: { + if (!active) { + postitRoot.positionUpdated(postitRoot.x, postitRoot.y) + } + } + } + + TextEdit { + id: editor + anchors.fill: parent + anchors.margins: 12 + text: postitRoot.noteText + wrapMode: TextEdit.Wrap + font.pointSize: 12 + color: "#222" + readOnly: true + + Keys.onPressed: function (event) { + if (event.key === Qt.Key_Escape) { + readOnly = true + postitRoot.textUpdated(text) + readOnly = true + event.accepted = true + } + } + } + + RippleButton { + id: deleteButton + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 4 + width: 24 + height: 24 + buttonRadius: width / 2 + z: 100 + + MaterialSymbol { + anchors.centerIn: parent; + text: "close"; + iconSize: 16 + color: Appearance.colors.colOnError + } + + onClicked: postitRoot.deleteRequested() + } + + MouseArea { + anchors.fill: parent + onDoubleClicked: { + editor.readOnly = false + editor.forceActiveFocus() + } + } +} \ No newline at end of file diff --git a/src/share/sleex/modules/bar/ActiveWindow.qml b/src/share/sleex/modules/bar/ActiveWindow.qml index 8262dbfb..dd93452c 100644 --- a/src/share/sleex/modules/bar/ActiveWindow.qml +++ b/src/share/sleex/modules/bar/ActiveWindow.qml @@ -1,15 +1,14 @@ import qs.modules.common import qs.modules.common.widgets +import qs.services import QtQuick import QtQuick.Layouts import Quickshell.Wayland -import Quickshell.Hyprland +import Sleex.Fhtc Item { id: root required property var bar - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) - readonly property Toplevel activeWindow: ToplevelManager.activeToplevel implicitWidth: colLayout.implicitWidth @@ -26,7 +25,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colSubtext elide: Text.ElideRight - text: root.activeWindow?.activated ? root.activeWindow?.appId : qsTr("Desktop") + text: FhtcWorkspaces.focusedWindow !== undefined ? FhtcWorkspaces.focusedWindow['app-id'] : qsTr("Desktop") } StyledText { @@ -34,7 +33,7 @@ Item { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer0 elide: Text.ElideRight - text: root.activeWindow?.activated ? root.activeWindow?.title : `${qsTr("Workspace")} ${monitor.activeWorkspace?.id}` + text: FhtcWorkspaces.focusedWindow !== undefined ? FhtcWorkspaces.focusedWindow.title : `${qsTr("Workspace")} ${FhtcWorkspaces.activeWorkspaceId + 1}` } } diff --git a/src/share/sleex/modules/bar/AudioOsd.qml b/src/share/sleex/modules/bar/AudioOsd.qml index d1a909de..b310f3e7 100644 --- a/src/share/sleex/modules/bar/AudioOsd.qml +++ b/src/share/sleex/modules/bar/AudioOsd.qml @@ -1,5 +1,4 @@ import Quickshell -import Quickshell.Hyprland import QtQuick.Layouts import QtQuick import qs.modules.common diff --git a/src/share/sleex/modules/bar/Bar.qml b/src/share/sleex/modules/bar/Bar.qml index 60f1e109..ddc0aaa5 100644 --- a/src/share/sleex/modules/bar/Bar.qml +++ b/src/share/sleex/modules/bar/Bar.qml @@ -507,10 +507,10 @@ Scope { NotifIndicator {} } - HyprlandXkbIndicator { - Layout.alignment: Qt.AlignVCenter - Layout.rightMargin: indicatorsRowLayout.realSpacing - } + // HyprlandXkbIndicator { + // Layout.alignment: Qt.AlignVCenter + // Layout.rightMargin: indicatorsRowLayout.realSpacing + // } Revealer { diff --git a/src/share/sleex/modules/bar/BrightnessOsd.qml b/src/share/sleex/modules/bar/BrightnessOsd.qml index 952cd601..4ffccde2 100644 --- a/src/share/sleex/modules/bar/BrightnessOsd.qml +++ b/src/share/sleex/modules/bar/BrightnessOsd.qml @@ -1,16 +1,16 @@ import Quickshell -import Quickshell.Hyprland import QtQuick import qs.modules.common import qs.modules.common.widgets import qs.services +import Sleex.Fhtc Revealer { id: root reveal: showOsdValues property bool showOsdValues: false - property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName) property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) Connections { diff --git a/src/share/sleex/modules/bar/SysTray.qml b/src/share/sleex/modules/bar/SysTray.qml index 4b911182..58f63943 100644 --- a/src/share/sleex/modules/bar/SysTray.qml +++ b/src/share/sleex/modules/bar/SysTray.qml @@ -3,7 +3,6 @@ import qs.modules.common.widgets import QtQuick import QtQuick.Layouts import Quickshell -import Quickshell.Hyprland import Quickshell.Services.SystemTray Item { @@ -26,20 +25,20 @@ Item { } function grabFocus() { - focusGrab.active = true; + // focusGrab.active = true; } function setExtraWindowAndGrabFocus(window) { root.activeMenu = window; - root.grabFocus(); + // root.grabFocus(); } function releaseFocus() { - focusGrab.active = false; + // focusGrab.active = false; } function closeOverflowMenu() { - focusGrab.active = false; + // focusGrab.active = false; } onTrayOverflowOpenChanged: { @@ -48,19 +47,6 @@ Item { } } - HyprlandFocusGrab { - id: focusGrab - active: false - windows: [trayOverflowLayout.QsWindow?.window, root.activeMenu] - onCleared: { - root.trayOverflowOpen = false; - if (root.activeMenu) { - root.activeMenu.close(); - root.activeMenu = null; - } - } - } - GridLayout { id: gridLayout columns: root.vertical ? 1 : -1 diff --git a/src/share/sleex/modules/bar/Workspaces.qml b/src/share/sleex/modules/bar/Workspaces.qml index 75c0806a..bc923bb8 100644 --- a/src/share/sleex/modules/bar/Workspaces.qml +++ b/src/share/sleex/modules/bar/Workspaces.qml @@ -8,28 +8,41 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland import Quickshell.Io import Quickshell.Widgets import Qt5Compat.GraphicalEffects +import Sleex.Fhtc Item { id: root required property var bar - property bool borderless: Config.options.bar.borderless - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) - readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + readonly property string screenName: bar.screen?.name ?? "" + + // Get workspaces for this screen only, sorted by ID + readonly property var screenWorkspaces: { + return Object.values(FhtcWorkspaces.workspaces) + .filter(ws => ws.output === screenName) + .sort((a, b) => a.id - b.id); + } + + // Active workspace index within this screen (0-based) + readonly property int activeWorkspaceIndex: { + if (!FhtcWorkspaces.activeWorkspace) return -1; + if (FhtcWorkspaces.activeWorkspace.output !== screenName) return -1; + // Find the index of the active workspace in our sorted screen workspaces + const idx = screenWorkspaces.findIndex(ws => ws.id === FhtcWorkspaces.activeWorkspace.id); + // Return -1 if the workspace is beyond the shown limit + if (idx >= Config.options.bar.workspaces.shown) return -1; + return idx; + } - readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / Config.options.bar.workspaces.shown) property list workspaceOccupied: [] - property int widgetPadding: 0 property int horizontalPadding: 5 property int workspaceButtonWidth: 30 property real workspaceIconSize: workspaceButtonWidth * 0.6 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconOpacityShrinked: 1 property real workspaceIconMarginShrinked: -4 - property int workspaceIndexInGroup: (monitor.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown property bool useMaterialIcons: Config.options.bar.workspaces.useMaterialIcons @@ -37,17 +50,27 @@ Item { // Function to update workspaceOccupied function updateWorkspaceOccupied() { workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => { - return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * Config.options.bar.workspaces.shown + i + 1); + // Get the workspace at this index for this screen + const ws = screenWorkspaces[i]; + if (!ws) return false; + // Check if the workspace has any windows + return ws.windows && ws.windows.length > 0; }) } // Initialize workspaceOccupied when the component is created Component.onCompleted: updateWorkspaceOccupied() - // Listen for changes in Hyprland.workspaces.values + // Listen for changes in Fhtc.workspaces and windows Connections { - target: Hyprland.workspaces - function onValuesChanged() { + target: FhtcWorkspaces + function onWorkspacesChanged() { + updateWorkspaceOccupied(); + } + function onWindowsChanged() { + updateWorkspaceOccupied(); + } + function onActiveWorkspaceChanged() { updateWorkspaceOccupied(); } } @@ -59,24 +82,15 @@ Item { // Scroll to switch workspaces WheelHandler { onWheel: (event) => { - if (event.angleDelta.y < 0) - Hyprland.dispatch(`workspace +1`); - else if (event.angleDelta.y > 0) - Hyprland.dispatch(`workspace -1`); + if (event.angleDelta.y < 0) { + FhtcIpc.dispatch("focus-next-workspace", {}); + } else if (event.angleDelta.y > 0) { + FhtcIpc.dispatch("focus-previous-workspace", {}); + } } acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.BackButton - onPressed: (event) => { - if (event.button === Qt.BackButton) { - Hyprland.dispatch(`togglespecialworkspace`); - } - } - } - Item { anchors.fill: parent anchors.leftMargin: horizontalPadding @@ -99,8 +113,8 @@ Item { implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth radius: 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 leftOccupied: (workspaceOccupied[index-1]) + property var rightOccupied: (workspaceOccupied[index+1]) property var radiusLeft: leftOccupied ? 0 : Appearance.rounding.full property var radiusRight: rightOccupied ? 0 : Appearance.rounding.full @@ -110,7 +124,7 @@ Item { bottomRightRadius: radiusRight color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) - opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 + opacity: (workspaceOccupied[index]) ? 1 : 0 Behavior on opacity { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) @@ -132,6 +146,7 @@ Item { // Active workspace Rectangle { z: 2 + visible: activeWorkspaceIndex >= 0 // Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight property real activeWorkspaceMargin: 2 implicitHeight: workspaceButtonWidth - activeWorkspaceMargin * 2 @@ -139,8 +154,8 @@ Item { color: Appearance.colors.colPrimary anchors.verticalCenter: parent.verticalCenter - property real idx1: workspaceIndexInGroup - property real idx2: workspaceIndexInGroup + property real idx1: activeWorkspaceIndex >= 0 ? activeWorkspaceIndex : 0 + property real idx2: activeWorkspaceIndex >= 0 ? activeWorkspaceIndex : 0 x: Math.min(idx1, idx2) * workspaceButtonWidth + activeWorkspaceMargin implicitWidth: Math.abs(idx1 - idx2) * workspaceButtonWidth + workspaceButtonWidth - activeWorkspaceMargin * 2 @@ -175,29 +190,38 @@ Item { Button { id: button - property int workspaceValue: workspaceGroup * Config.options.bar.workspaces.shown + index + 1 + property var workspace: screenWorkspaces[index] ?? null + property int workspaceId: workspace?.id ?? -1 Layout.fillHeight: true - onPressed: Hyprland.dispatch(`workspace ${workspaceValue}`) + onPressed: { + if (button.workspaceId >= 0) { + FhtcIpc.dispatch("focus-workspace-by-index", { "workspace_idx": button.workspaceId }); + } + } width: workspaceButtonWidth background: Item { id: workspaceButtonBackground implicitWidth: workspaceButtonWidth implicitHeight: workspaceButtonWidth + + // Get the biggest window from the workspace's window list property var biggestWindow: { - const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == button.workspaceValue) + if (!button.workspace || !button.workspace.windows || button.workspace.windows.length === 0) return null; + const windowIds = button.workspace.windows; + const windowsInThisWorkspace = windowIds.map(id => FhtcWorkspaces.windows[id]).filter(w => w != null); return windowsInThisWorkspace.reduce((maxWin, win) => { const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0) const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0) return winArea > maxArea ? win : maxWin }, null) } - property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") + property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.["app-id"]), "image-missing") property string materialIconName: { if (!biggestWindow) return "" - const winClass = biggestWindow.class.toLowerCase() + const winClass = biggestWindow?.["app-id"] const map = { "language": ["firefox", "chromium", "google-chrome", "brave", "edge", "vivaldi", "qutebrowser", "librewolf", "zen-browser"], "terminal": ["foot", "kitty", "alacritty", "wezterm", "gnome-terminal", "konsole", "xfce4-terminal", "xterm"], @@ -230,9 +254,9 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2) - text: `${button.workspaceValue}` + text: `${index + 1}` elide: Text.ElideRight - color: (monitor.activeWorkspace?.id == button.workspaceValue) ? + color: (activeWorkspaceIndex == index) ? Appearance.m3colors.m3onPrimary : (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) @@ -252,9 +276,9 @@ Item { width: workspaceButtonWidth * 0.18 height: width radius: width / 2 - color: (monitor.activeWorkspace?.id == button.workspaceValue) ? - Appearance.m3colors.m3onPrimary : - (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : + color: (activeWorkspaceIndex == index) ? + Appearance.m3colors.m3onPrimary : + (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1Inactive) Behavior on opacity { @@ -268,7 +292,7 @@ Item { opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 - visible: opacity > 0 + visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom @@ -308,8 +332,8 @@ Item { (workspaceButtonWidth - workspaceIconSize) / 2 - 2 : workspaceIconMarginShrinked anchors.rightMargin: (!GlobalStates.workspaceShowNumbers) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - color: (monitor.activeWorkspace?.id == button.workspaceValue) ? - Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3onSecondaryContainer + color: (activeWorkspaceIndex == index) ? + Appearance.m3colors.m3onPrimary : Appearance.colors.colOnSecondaryContainer Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) diff --git a/src/share/sleex/modules/cheatsheet/Cheatsheet.qml b/src/share/sleex/modules/cheatsheet/Cheatsheet.qml index 2146fb23..e07a06b1 100644 --- a/src/share/sleex/modules/cheatsheet/Cheatsheet.qml +++ b/src/share/sleex/modules/cheatsheet/Cheatsheet.qml @@ -38,23 +38,13 @@ Scope { // Scope implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2 implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2 WlrLayershell.namespace: "quickshell:cheatsheet" - // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab - // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: "transparent" mask: Region { item: cheatsheetBackground } - HyprlandFocusGrab { // Click outside to close - id: grab - windows: [ cheatsheetRoot ] - active: cheatsheetRoot.visible - onCleared: () => { - if (!active) cheatsheetRoot.hide() - } - } - // Background StyledRectangularShadow { @@ -161,5 +151,4 @@ Scope { // Scope cheatsheetLoader.active = false; } } - } diff --git a/src/share/sleex/modules/cheatsheet/CheatsheetKeybinds.qml b/src/share/sleex/modules/cheatsheet/CheatsheetKeybinds.qml index f39e27ab..97fbf480 100644 --- a/src/share/sleex/modules/cheatsheet/CheatsheetKeybinds.qml +++ b/src/share/sleex/modules/cheatsheet/CheatsheetKeybinds.qml @@ -9,11 +9,10 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets -import Quickshell.Hyprland Item { id: root - readonly property var keybinds: HyprlandKeybinds.keybinds + readonly property var keybinds: FhtcKeybinds.keybinds property real spacing: 16 property real titleSpacing: 8 width: 800 diff --git a/src/share/sleex/modules/common/Appearance.qml b/src/share/sleex/modules/common/Appearance.qml index c65992bc..ee38636a 100644 --- a/src/share/sleex/modules/common/Appearance.qml +++ b/src/share/sleex/modules/common/Appearance.qml @@ -346,7 +346,7 @@ Singleton { sizes: QtObject { property real baseBarHeight: 40 property real barHeight: Config.options.bar.cornerStyle === 1 ? - (baseBarHeight + root.sizes.hyprlandGapsOut * 2) : baseBarHeight + (baseBarHeight + root.sizes.gapsOut * 2) : baseBarHeight property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140 property real barCenterSideModuleWidthShortened: 280 property real barCenterSideModuleWidthHellaShortened: 190 @@ -355,7 +355,7 @@ Singleton { property real elevationMargin: 10 property real fabShadowRadius: 5 property real fabHoveredShadowRadius: 7 - property real hyprlandGapsOut: 5 + property real gapsOut: 5 property real mediaControlsWidth: 440 property real mediaControlsHeight: 160 property real notificationPopupWidth: 410 @@ -366,7 +366,7 @@ Singleton { property real sidebarWidthExtended: 750 property real baseVerticalBarWidth: 46 property real verticalBarWidth: Config.options.bar.cornerStyle === 1 ? - (baseVerticalBarWidth + root.sizes.hyprlandGapsOut * 2) : baseVerticalBarWidth + (baseVerticalBarWidth + root.sizes.gapsOut * 2) : baseVerticalBarWidth property real wallpaperSelectorWidth: 1200 property real wallpaperSelectorHeight: 690 property real wallpaperSelectorItemMargins: 8 diff --git a/src/share/sleex/modules/common/Config.qml b/src/share/sleex/modules/common/Config.qml index e385a7a3..5518d91b 100644 --- a/src/share/sleex/modules/common/Config.qml +++ b/src/share/sleex/modules/common/Config.qml @@ -156,7 +156,7 @@ Singleton { property bool background: false property bool borderless: false // true for no grouping of items property bool verbose: true - property list screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command + property list screenList: [] // List of names, like "eDP-1" property JsonObject workspaces: JsonObject { property int shown: 10 property bool showAppIcons: false diff --git a/src/share/sleex/modules/common/Directories.qml b/src/share/sleex/modules/common/Directories.qml index 5f68e9cc..8651bbdd 100644 --- a/src/share/sleex/modules/common/Directories.qml +++ b/src/share/sleex/modules/common/Directories.qml @@ -5,7 +5,6 @@ import qs.modules.common.functions import Qt.labs.platform import QtQuick import Quickshell -import Quickshell.Hyprland Singleton { // XDG Dirs, with "file://" diff --git a/src/share/sleex/modules/common/widgets/ConfigSelectionArray.qml b/src/share/sleex/modules/common/widgets/ConfigSelectionArray.qml index b1c279f7..04f6480b 100644 --- a/src/share/sleex/modules/common/widgets/ConfigSelectionArray.qml +++ b/src/share/sleex/modules/common/widgets/ConfigSelectionArray.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io -import Quickshell.Hyprland import qs.services import qs.modules.common import qs.modules.common.widgets diff --git a/src/share/sleex/modules/common/widgets/Favicon.qml b/src/share/sleex/modules/common/widgets/Favicon.qml index 7eae7fb6..29cb7689 100644 --- a/src/share/sleex/modules/common/widgets/Favicon.qml +++ b/src/share/sleex/modules/common/widgets/Favicon.qml @@ -10,7 +10,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell.Io import Quickshell.Widgets -import Quickshell.Hyprland IconImage { id: root diff --git a/src/share/sleex/modules/common/widgets/LightDarkPreferenceButton.qml b/src/share/sleex/modules/common/widgets/LightDarkPreferenceButton.qml index d380aaf3..dad9d367 100644 --- a/src/share/sleex/modules/common/widgets/LightDarkPreferenceButton.qml +++ b/src/share/sleex/modules/common/widgets/LightDarkPreferenceButton.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Io -import Quickshell.Hyprland GroupButton { id: lightDarkButtonRoot diff --git a/src/share/sleex/modules/common/widgets/NotificationGroup.qml b/src/share/sleex/modules/common/widgets/NotificationGroup.qml index 826ba294..310c49f4 100644 --- a/src/share/sleex/modules/common/widgets/NotificationGroup.qml +++ b/src/share/sleex/modules/common/widgets/NotificationGroup.qml @@ -11,7 +11,6 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets -import Quickshell.Hyprland import Quickshell.Services.Notifications /** diff --git a/src/share/sleex/modules/common/widgets/NotificationItem.qml b/src/share/sleex/modules/common/widgets/NotificationItem.qml index 4e526ae5..007e855f 100644 --- a/src/share/sleex/modules/common/widgets/NotificationItem.qml +++ b/src/share/sleex/modules/common/widgets/NotificationItem.qml @@ -11,7 +11,6 @@ import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Widgets -import Quickshell.Hyprland import Quickshell.Services.Notifications Item { // Notification item area diff --git a/src/share/sleex/modules/common/widgets/NotificationListView.qml b/src/share/sleex/modules/common/widgets/NotificationListView.qml index 6727b33c..4a3d18c8 100644 --- a/src/share/sleex/modules/common/widgets/NotificationListView.qml +++ b/src/share/sleex/modules/common/widgets/NotificationListView.qml @@ -7,7 +7,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland StyledListView { // Scrollable window id: root diff --git a/src/share/sleex/modules/common/widgets/SelectionGroupButton.qml b/src/share/sleex/modules/common/widgets/SelectionGroupButton.qml index d6ba9091..d7a1196c 100644 --- a/src/share/sleex/modules/common/widgets/SelectionGroupButton.qml +++ b/src/share/sleex/modules/common/widgets/SelectionGroupButton.qml @@ -2,7 +2,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io -import Quickshell.Hyprland import qs.services import qs.modules.common import qs.modules.common.widgets diff --git a/src/share/sleex/modules/common/widgets/WindowDialog.qml b/src/share/sleex/modules/common/widgets/WindowDialog.qml index 58bb577c..54529331 100644 --- a/src/share/sleex/modules/common/widgets/WindowDialog.qml +++ b/src/share/sleex/modules/common/widgets/WindowDialog.qml @@ -33,7 +33,7 @@ Rectangle { dialogBackground.implicitHeight = show ? backgroundHeight : 0 } - radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 + radius: Appearance.rounding.screenRounding - Appearance.sizes.gapsOut + 1 MouseArea { // Clicking outside the dialog should dismiss anchors.fill: parent diff --git a/src/share/sleex/modules/dashboard/Dashboard.qml b/src/share/sleex/modules/dashboard/Dashboard.qml index a0fd60a7..8c4b32b1 100644 --- a/src/share/sleex/modules/dashboard/Dashboard.qml +++ b/src/share/sleex/modules/dashboard/Dashboard.qml @@ -14,6 +14,7 @@ import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Bluetooth +import Sleex.Fhtc Scope { id: dashboardScope @@ -28,8 +29,8 @@ Scope { PanelWindow { id: dashboardRoot required property var modelData - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + required property ShellScreen screen + property bool monitorIsFocused: (FhtcMonitors.activeMonitorName === screen.name) screen: modelData visible: GlobalStates.dashboardOpen && monitorIsFocused @@ -38,20 +39,13 @@ Scope { implicitHeight: Screen.height WlrLayershell.namespace: "quickshell:dashboard" WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive color: "transparent" mask: GlobalStates.dashboardOpen ? null : emptyRegion Region { id: emptyRegion } - HyprlandFocusGrab { - id: grab - windows: [ dashboardRoot ] - property bool canBeActive: dashboardRoot.monitorIsFocused - active: false - onCleared: () => { if (!active) ipc.close() } - } - Connections { target: GlobalStates function onDashboardOpenChanged() { @@ -158,10 +152,6 @@ Scope { 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 } focus: GlobalStates.dashboardOpen && dashboardRoot.monitorIsFocused Keys.onPressed: (event) => { @@ -180,10 +170,10 @@ Scope { Rectangle { id: dashboardBackground anchors.fill: parent - implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2 - implicitWidth: dashboardWidth - Appearance.sizes.hyprlandGapsOut * 2 + implicitHeight: parent.height + implicitWidth: dashboardWidth color: Appearance.colors.colLayer0 - radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 + radius: Appearance.rounding.screenRounding ColumnLayout { spacing: dashboardPadding @@ -229,10 +219,10 @@ Scope { toggled: false buttonIcon: "restart_alt" onClicked: { - Hyprland.dispatch("reload") + FhtcIpc.dispatch("reload-config") Quickshell.reload(true) } - StyledToolTip { text: qsTr("Reload Hyprland & Quickshell") } + StyledToolTip { text: qsTr("Reload Fhtc & Quickshell") } } QuickToggleButton { toggled: false @@ -246,7 +236,10 @@ Scope { QuickToggleButton { toggled: false buttonIcon: "power_settings_new" - onClicked: Hyprland.dispatch("global quickshell:sessionOpen") + onClicked: () => { + Quickshell.execDetached(["qs", "-p", "/usr/share/sleex/", "ipc", "call", "session", "open"]) + ipc.close() + } StyledToolTip { text: qsTr("Session") } } } diff --git a/src/share/sleex/modules/dashboard/ai/aiChat/AnnotationSourceButton.qml b/src/share/sleex/modules/dashboard/ai/aiChat/AnnotationSourceButton.qml index bd75a5d4..8746a59c 100644 --- a/src/share/sleex/modules/dashboard/ai/aiChat/AnnotationSourceButton.qml +++ b/src/share/sleex/modules/dashboard/ai/aiChat/AnnotationSourceButton.qml @@ -4,7 +4,6 @@ import qs.modules.common.functions import qs.services import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland RippleButton { id: root diff --git a/src/share/sleex/modules/dashboard/ai/aiChat/MessageTextBlock.qml b/src/share/sleex/modules/dashboard/ai/aiChat/MessageTextBlock.qml index d68a260c..e74b45d7 100644 --- a/src/share/sleex/modules/dashboard/ai/aiChat/MessageTextBlock.qml +++ b/src/share/sleex/modules/dashboard/ai/aiChat/MessageTextBlock.qml @@ -8,7 +8,6 @@ import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell.Hyprland ColumnLayout { id: root diff --git a/src/share/sleex/modules/dashboard/ai/aiChat/SearchQueryButton.qml b/src/share/sleex/modules/dashboard/ai/aiChat/SearchQueryButton.qml index 4ad60ac9..699f8a03 100644 --- a/src/share/sleex/modules/dashboard/ai/aiChat/SearchQueryButton.qml +++ b/src/share/sleex/modules/dashboard/ai/aiChat/SearchQueryButton.qml @@ -5,7 +5,6 @@ import qs.services import qs.modules.common.functions import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland RippleButton { id: root diff --git a/src/share/sleex/modules/dashboard/quickToggles/BluetoothToggle.qml b/src/share/sleex/modules/dashboard/quickToggles/BluetoothToggle.qml index 17591002..5c6c4fbf 100644 --- a/src/share/sleex/modules/dashboard/quickToggles/BluetoothToggle.qml +++ b/src/share/sleex/modules/dashboard/quickToggles/BluetoothToggle.qml @@ -8,8 +8,6 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Bluetooth -import Quickshell.Hyprland - import Sleex.Services QuickToggleButton { diff --git a/src/share/sleex/modules/dashboard/quickToggles/GameMode.qml b/src/share/sleex/modules/dashboard/quickToggles/GameMode.qml deleted file mode 100644 index bc0f5497..00000000 --- a/src/share/sleex/modules/dashboard/quickToggles/GameMode.qml +++ /dev/null @@ -1,26 +0,0 @@ -import qs.modules.common -import qs.modules.common.widgets -import "../" -import Quickshell -import Quickshell.Io -import Quickshell.Hyprland - -QuickToggleButton { - property bool enabled: false - buttonIcon: "gamepad" - toggled: enabled - - onClicked: { - enabled = !enabled - if (enabled) { - // gameModeOn.running = true - Quickshell.execDetached(["hyprctl", "--batch", "keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1"]) - } else { - Quickshell.execDetached(["hyprctl reload"]) - } - } - - StyledToolTip { - text: qsTr("Game mode") - } -} diff --git a/src/share/sleex/modules/dashboard/quickToggles/NetworkToggle.qml b/src/share/sleex/modules/dashboard/quickToggles/NetworkToggle.qml index b99ec9be..4ef764ca 100644 --- a/src/share/sleex/modules/dashboard/quickToggles/NetworkToggle.qml +++ b/src/share/sleex/modules/dashboard/quickToggles/NetworkToggle.qml @@ -7,7 +7,6 @@ import "../" import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Sleex.Services diff --git a/src/share/sleex/modules/dock/Dock.qml b/src/share/sleex/modules/dock/Dock.qml index d96b82eb..f0aaa850 100644 --- a/src/share/sleex/modules/dock/Dock.qml +++ b/src/share/sleex/modules/dock/Dock.qml @@ -10,7 +10,6 @@ import Quickshell.Io import Quickshell import Quickshell.Widgets import Quickshell.Wayland -import Quickshell.Hyprland Scope { // Scope id: root @@ -27,7 +26,7 @@ Scope { // Scope property bool reveal: root.pinned || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow - || (!ToplevelManager.activeToplevel?.activated) + || (ToplevelManager.toplevels?.length === 0) anchors { bottom: true @@ -36,14 +35,14 @@ Scope { // Scope } exclusiveZone: root.pinned ? implicitHeight - - (Appearance.sizes.hyprlandGapsOut) - - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0 + - (Appearance.sizes.gapsOut) + - (Appearance.sizes.elevationMargin - Appearance.sizes.gapsOut) : 0 implicitWidth: dockBackground.implicitWidth WlrLayershell.namespace: "quickshell:dock" color: "transparent" - implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut + implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.gapsOut mask: Region { item: dockMouseArea @@ -80,7 +79,7 @@ Scope { // Scope } implicitWidth: dockRow.implicitWidth + 5 * 2 - height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut + height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.gapsOut StyledRectangularShadow { target: dockVisualBackground @@ -90,7 +89,7 @@ Scope { // Scope property real margin: Appearance.sizes.elevationMargin anchors.fill: parent anchors.topMargin: Appearance.sizes.elevationMargin - anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut + anchors.bottomMargin: Appearance.sizes.gapsOut color: Appearance.colors.colLayer0 border.width: 1 border.color: Appearance.m3colors.m3outlineVariant @@ -106,7 +105,7 @@ Scope { // Scope property real padding: 5 VerticalButtonGroup { - Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work + Layout.topMargin: Appearance.sizes.gapsOut // why does this work GroupButton { // Pin button baseWidth: 35 baseHeight: 35 diff --git a/src/share/sleex/modules/dock/DockApps.qml b/src/share/sleex/modules/dock/DockApps.qml index d8bb3351..dbe5244d 100644 --- a/src/share/sleex/modules/dock/DockApps.qml +++ b/src/share/sleex/modules/dock/DockApps.qml @@ -24,7 +24,7 @@ Item { property bool requestDockShow: previewPopup.show Layout.fillHeight: true - Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work + Layout.topMargin: Appearance.sizes.gapsOut // why does this work implicitWidth: listView.implicitWidth StyledListView { diff --git a/src/share/sleex/modules/dock/DockButton.qml b/src/share/sleex/modules/dock/DockButton.qml index 61655782..e7ec6a8f 100644 --- a/src/share/sleex/modules/dock/DockButton.qml +++ b/src/share/sleex/modules/dock/DockButton.qml @@ -7,10 +7,10 @@ import QtQuick.Layouts RippleButton { Layout.fillHeight: true - Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut + Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.gapsOut implicitWidth: implicitHeight - topInset - bottomInset buttonRadius: Appearance.rounding.normal - topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding - bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding + topInset: Appearance.sizes.gapsOut + dockRow.padding + bottomInset: Appearance.sizes.gapsOut + dockRow.padding } diff --git a/src/share/sleex/modules/dock/DockSeparator.qml b/src/share/sleex/modules/dock/DockSeparator.qml index 419b0fed..48460e9f 100644 --- a/src/share/sleex/modules/dock/DockSeparator.qml +++ b/src/share/sleex/modules/dock/DockSeparator.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts Rectangle { Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal - Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal + Layout.bottomMargin: Appearance.sizes.gapsOut + dockRow.padding + Appearance.rounding.normal Layout.fillHeight: true implicitWidth: 1 color: Appearance.colors.colOutlineVariant diff --git a/src/share/sleex/modules/lockscreen/LockSurface.qml b/src/share/sleex/modules/lockscreen/LockSurface.qml index 4659be66..8ace81b4 100644 --- a/src/share/sleex/modules/lockscreen/LockSurface.qml +++ b/src/share/sleex/modules/lockscreen/LockSurface.qml @@ -44,11 +44,10 @@ FocusScope { } // Wallpaper & Scrim - Image { + WallpaperDisplay { id: wallpaper anchors.fill: parent source: Config.options.background.wallpaperPath - fillMode: Image.PreserveAspectCrop opacity: root.visualsReady ? 1 : 0 Behavior on opacity { @@ -64,7 +63,7 @@ FocusScope { radius: 15 opacity: parent.opacity } - + MouseArea { anchors.fill: parent onClicked: passwordInput.forceActiveFocus() diff --git a/src/share/sleex/modules/mediaControls/MediaControls.qml b/src/share/sleex/modules/mediaControls/MediaControls.qml index 0f31b25e..0417b3e8 100644 --- a/src/share/sleex/modules/mediaControls/MediaControls.qml +++ b/src/share/sleex/modules/mediaControls/MediaControls.qml @@ -12,7 +12,6 @@ import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Widgets import Quickshell.Wayland -import Quickshell.Hyprland Item { id: root diff --git a/src/share/sleex/modules/mediaControls/PlayerControl.qml b/src/share/sleex/modules/mediaControls/PlayerControl.qml index 94ad7577..d0aa6c10 100644 --- a/src/share/sleex/modules/mediaControls/PlayerControl.qml +++ b/src/share/sleex/modules/mediaControls/PlayerControl.qml @@ -12,7 +12,6 @@ import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Widgets import Quickshell.Wayland -import Quickshell.Hyprland Item { // Player instance id: playerController diff --git a/src/share/sleex/modules/mediaControls/PlayerControlBlank.qml b/src/share/sleex/modules/mediaControls/PlayerControlBlank.qml index 493b8623..7d0f92e6 100644 --- a/src/share/sleex/modules/mediaControls/PlayerControlBlank.qml +++ b/src/share/sleex/modules/mediaControls/PlayerControlBlank.qml @@ -14,7 +14,6 @@ import Quickshell.Io import Quickshell.Services.Mpris import Quickshell.Widgets import Quickshell.Wayland -import Quickshell.Hyprland Item { id: playerControllerBlank diff --git a/src/share/sleex/modules/notificationPopup/NotificationPopup.qml b/src/share/sleex/modules/notificationPopup/NotificationPopup.qml index a302a45c..01aeec19 100644 --- a/src/share/sleex/modules/notificationPopup/NotificationPopup.qml +++ b/src/share/sleex/modules/notificationPopup/NotificationPopup.qml @@ -7,7 +7,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland +import Sleex.Fhtc Scope { id: notificationPopup @@ -17,7 +17,7 @@ Scope { PanelWindow { id: root visible: (Notifications.popupList.length > 0) - screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null + screen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName) ?? null WlrLayershell.namespace: "quickshell:notificationPopup" WlrLayershell.layer: WlrLayer.Overlay diff --git a/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index af6115cb..f396b3c7 100644 --- a/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -8,11 +8,12 @@ import Quickshell import Quickshell.Io import Quickshell.Hyprland import Quickshell.Wayland +import Sleex.Fhtc Scope { id: root property bool showOsdValues: false - property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName === screen.name) property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) function triggerOsd() { @@ -148,5 +149,4 @@ Scope { root.showOsdValues = false } } - } \ No newline at end of file diff --git a/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayVolume.qml index 5598f2ef..75ac9209 100644 --- a/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/src/share/sleex/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -8,12 +8,13 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland +import Sleex.Fhtc Scope { id: root property bool showOsdValues: false property string protectionMessage: "" - property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName === screen.name) function triggerOsd() { showOsdValues = true @@ -200,5 +201,4 @@ Scope { root.showOsdValues = false } } - } \ No newline at end of file diff --git a/src/share/sleex/modules/overview/Overview.qml b/src/share/sleex/modules/overview/Overview.qml index 5687357e..8a645eda 100644 --- a/src/share/sleex/modules/overview/Overview.qml +++ b/src/share/sleex/modules/overview/Overview.qml @@ -9,6 +9,7 @@ import Quickshell import Quickshell.Io import Quickshell.Wayland import Quickshell.Hyprland +import Sleex.Fhtc Scope { id: overviewScope @@ -20,23 +21,19 @@ Scope { id: root required property var modelData property string searchingText: "" - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + required property ShellScreen screen + property bool monitorIsFocused: (FhtcMonitors.activeMonitorName === screen.name) screen: modelData visible: GlobalStates.overviewOpen && monitorIsFocused WlrLayershell.namespace: "quickshell:overview" WlrLayershell.layer: WlrLayer.Overlay - // WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None color: "transparent" mask: Region { item: GlobalStates.overviewOpen ? columnLayout : null } - HyprlandWindow.visibleMask: Region { - item: GlobalStates.overviewOpen ? columnLayout : null - } - anchors { top: true @@ -45,16 +42,6 @@ Scope { bottom: true } - HyprlandFocusGrab { - id: grab - windows: [ root ] - property bool canBeActive: root.monitorIsFocused - active: false - onCleared: () => { - if (!active) GlobalStates.overviewOpen = false - } - } - Connections { target: GlobalStates function onOverviewOpenChanged() { @@ -65,21 +52,10 @@ Scope { if (!overviewScope.dontAutoCancelSearch) { searchWidget.cancelSearch() } - delayedGrabTimer.start() } } } - Timer { - id: delayedGrabTimer - interval: Config.options.hacks.arbitraryRaceConditionDelay - repeat: false - onTriggered: { - if (!grab.canBeActive) return - grab.active = GlobalStates.overviewOpen - } - } - implicitWidth: columnLayout.implicitWidth implicitHeight: columnLayout.implicitHeight @@ -101,9 +77,9 @@ Scope { if (event.key === Qt.Key_Escape) { GlobalStates.overviewOpen = false; } else if (event.key === Qt.Key_Left) { - if (!root.searchingText) Hyprland.dispatch("workspace -1"); + if (!root.searchingText) FhtcIpc.dispatch("focus-previous-workspace"); } else if (event.key === Qt.Key_Right) { - if (!root.searchingText) Hyprland.dispatch("workspace +1"); + if (!root.searchingText) FhtcIpc.dispatch("focus-next-workspace"); } } @@ -203,7 +179,7 @@ Scope { } for (let i = 0; i < overviewVariants.instances.length; i++) { let panelWindow = overviewVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + if (panelWindow.modelData.name == FhtcMonitors.activeMonitorName) { overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText( Config.options.search.prefix.clipboard @@ -226,7 +202,7 @@ Scope { } for (let i = 0; i < overviewVariants.instances.length; i++) { let panelWindow = overviewVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + if (panelWindow.modelData.name == FhtcMonitors.activeMonitorName) { overviewScope.dontAutoCancelSearch = true; panelWindow.setSearchingText( Config.options.search.prefix.emojis @@ -237,5 +213,4 @@ Scope { } } } - } diff --git a/src/share/sleex/modules/overview/OverviewWidget.qml b/src/share/sleex/modules/overview/OverviewWidget.qml index 0b8d73ca..a79e188b 100644 --- a/src/share/sleex/modules/overview/OverviewWidget.qml +++ b/src/share/sleex/modules/overview/OverviewWidget.qml @@ -10,32 +10,26 @@ import Quickshell import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland -import Quickshell.Hyprland +import Sleex.Fhtc Item { id: root required property var panelWindow - readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) - readonly property var toplevels: ToplevelManager.toplevels + readonly property var monitor: FhtcMonitors.activeMonitor + // readonly property var toplevels: ToplevelManager.toplevels readonly property int workspacesShown: Config.options.overview.numOfRows * Config.options.overview.numOfCols - readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) - property var windows: HyprlandData.windowList - property var windowByAddress: HyprlandData.windowByAddress - property var windowAddresses: HyprlandData.addresses - property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) + readonly property int workspaceGroup: Math.floor(((monitor["active-workspace-idx"] ?? 0)) / workspacesShown) + property bool monitorIsFocused: (FhtcMonitors.activeMonitorName === screen.name) + readonly property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName) property real scale: Config.options.overview.scale 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) : - ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) - property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? - ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : - ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) + property real workspaceImplicitWidth: (focusedScreen?.width ?? 0) * root.scale + property real workspaceImplicitHeight: ((focusedScreen?.height ?? 0) - Appearance.sizes.barHeight) * root.scale property real workspaceNumberMargin: 80 - property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale + property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * (panelWindow.screen.devicePixelRatio ?? 1) + property int workspaceZ: 0 property int windowZ: 1 property int windowDraggingZ: 99999 @@ -82,7 +76,7 @@ Item { Rectangle { // Workspace id: workspace property int colIndex: index - property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.numOfCols + colIndex + 1 + property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.numOfCols + colIndex property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredBorderColor: Appearance.colors.colLayer2Hover @@ -111,9 +105,8 @@ Item { acceptedButtons: Qt.LeftButton onClicked: { if (root.draggingTargetWorkspace === -1) { - // Hyprland.dispatch(`exec qs ipc call overview close`) GlobalStates.overviewOpen = false - Hyprland.dispatch(`workspace ${workspaceValue}`) + FhtcIpc.dispatch("focus-workspace-by-index", { "workspace_idx": workspaceValue }) } } } @@ -146,36 +139,29 @@ Item { Repeater { // Window repeater 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) + return Object.values(FhtcWorkspaces.windows).filter((win) => { + const wsId = win?.["workspace-id"] ?? -1 + return wsId >= root.workspaceGroup * root.workspacesShown && + wsId < (root.workspaceGroup + 1) * root.workspacesShown }) } } delegate: OverviewWindow { id: window required property var modelData - property var address: `0x${modelData.HyprlandToplevel.address}` - windowData: windowByAddress[address] - toplevel: modelData - monitorData: root.monitorData + windowData: modelData + // toplevel: not yet available with fhtc compositor + monitorData: root.focusedScreen 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) % Config.options.overview.numOfCols - property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.numOfCols) - xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex - (monitor?.x * root.scale) - yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex - (monitor?.y * root.scale) + property int workspaceColIndex: (modelData?.["workspace-id"] ?? 0) % Config.options.overview.numOfCols + property int workspaceRowIndex: Math.floor((modelData?.["workspace-id"] ?? 0) % root.workspacesShown / Config.options.overview.numOfCols) + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex Timer { id: updateWindowPosition @@ -183,12 +169,13 @@ Item { repeat: false running: false onTriggered: { - 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})`) + window.x = Math.round(Math.max(modelData?.location[0] * root.scale, 0) + xOffset) + window.y = Math.round(Math.max((modelData?.location[1] - Appearance.sizes.barHeight) * root.scale, 0) + yOffset) } } + Component.onCompleted: updateWindowPosition.restart() + z: atInitPosition ? root.windowZ : root.windowDraggingZ Drag.hotSpot.x: targetWindowWidth / 2 Drag.hotSpot.y: targetWindowHeight / 2 @@ -201,35 +188,32 @@ Item { acceptedButtons: Qt.LeftButton | Qt.MiddleButton drag.target: parent onPressed: { - root.draggingFromWorkspace = windowData?.workspace.id + root.draggingFromWorkspace = modelData?.["workspace-id"] 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 window.pressed = false window.Drag.active = false root.draggingFromWorkspace = -1 - if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { - Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) + if (targetWorkspace !== -1 && targetWorkspace !== modelData?.["workspace-id"]) { + FhtcIpc.dispatch("send-window-to-workspace", { "window-id": modelData?.id, "workspace-id": targetWorkspace }) updateWindowPosition.restart() - } - else { + } else { window.x = window.initX window.y = window.initY } } onClicked: (event) => { - if (!windowData) return; - + if (!modelData) return; if (event.button === Qt.LeftButton) { GlobalStates.overviewOpen = false - Hyprland.dispatch(`focuswindow address:${windowData.address}`) + FhtcIpc.dispatch("focus-window", { "window-id": modelData.id }) event.accepted = true } else if (event.button === Qt.MiddleButton) { - Hyprland.dispatch(`closewindow address:${windowData.address}`) + FhtcIpc.dispatch("close-window", { "window-id": modelData.id }) event.accepted = true } } @@ -237,7 +221,7 @@ Item { StyledToolTip { extraVisibleCondition: false alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active - text: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` + text: `${modelData.title}\n[${modelData["app-id"]}]\n` } } } @@ -245,9 +229,9 @@ Item { Rectangle { // Focused workspace indicator id: focusedWorkspaceIndicator - property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) - property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / Config.options.overview.numOfCols) - property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.numOfCols + property int activeWorkspaceInGroup: (monitor["active-workspace-idx"] ?? 0) - (root.workspaceGroup * root.workspacesShown) + property int activeWorkspaceRowIndex: Math.floor(activeWorkspaceInGroup / Config.options.overview.numOfCols) + property int activeWorkspaceColIndex: activeWorkspaceInGroup % Config.options.overview.numOfCols x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex z: root.windowZ diff --git a/src/share/sleex/modules/overview/OverviewWindow.qml b/src/share/sleex/modules/overview/OverviewWindow.qml index e75a6487..c8c6caf9 100644 --- a/src/share/sleex/modules/overview/OverviewWindow.qml +++ b/src/share/sleex/modules/overview/OverviewWindow.qml @@ -11,7 +11,6 @@ import Quickshell import Quickshell.Widgets import Quickshell.Io import Quickshell.Wayland -import Quickshell.Hyprland Item { // Window id: root @@ -22,8 +21,8 @@ Item { // Window property var availableWorkspaceWidth property var availableWorkspaceHeight property bool restrictToWorkspace: true - property real initX: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset - property real initY: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + property real initX: Math.max(windowData?.location[0] * root.scale, 0) + xOffset + property real initY: Math.max((windowData?.location[1] - Appearance.sizes.barHeight) * root.scale, 0) + yOffset property real xOffset: 0 property real yOffset: 0 @@ -35,10 +34,10 @@ Item { // Window property var iconToWindowRatio: 0.35 property var xwaylandIndicatorToIconRatio: 0.35 property var iconToWindowRatioCompact: 0.6 - property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") + property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.["app-id"]), "image-missing") property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth - property bool indicateXWayland: (Config.options.overview.showXwaylandIndicator && windowData?.xwayland) ?? false + // property bool indicateXWayland: (Config.options.overview.showXwaylandIndicator && windowData?.xwayland) ?? false x: initX y: initY @@ -70,7 +69,7 @@ Item { // Window ScreencopyView { id: windowPreview anchors.fill: parent - captureSource: GlobalStates.overviewOpen ? root.toplevel : null + captureSource: null // GlobalStates.overviewOpen ? root.toplevel : null live: true Rectangle { diff --git a/src/share/sleex/modules/overview/SearchItem.qml b/src/share/sleex/modules/overview/SearchItem.qml index 272907fd..f14f5430 100644 --- a/src/share/sleex/modules/overview/SearchItem.qml +++ b/src/share/sleex/modules/overview/SearchItem.qml @@ -8,7 +8,6 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets -import Quickshell.Hyprland RippleButton { id: root diff --git a/src/share/sleex/modules/polkit/Polkit.qml b/src/share/sleex/modules/polkit/Polkit.qml index 6a8ac3bf..85cbfac0 100644 --- a/src/share/sleex/modules/polkit/Polkit.qml +++ b/src/share/sleex/modules/polkit/Polkit.qml @@ -6,7 +6,6 @@ import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland Scope { id: root @@ -29,7 +28,7 @@ Scope { color: "transparent" WlrLayershell.namespace: "quickshell:polkit" - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive WlrLayershell.layer: WlrLayer.Overlay exclusionMode: ExclusionMode.Ignore diff --git a/src/share/sleex/modules/screenCorners/ScreenCorners.qml b/src/share/sleex/modules/screenCorners/ScreenCorners.qml index 91aeb21e..f487b412 100644 --- a/src/share/sleex/modules/screenCorners/ScreenCorners.qml +++ b/src/share/sleex/modules/screenCorners/ScreenCorners.qml @@ -5,7 +5,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland Scope { id: screenCorners @@ -26,20 +25,6 @@ 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" diff --git a/src/share/sleex/modules/session/Session.qml b/src/share/sleex/modules/session/Session.qml index 9987efca..0c937600 100644 --- a/src/share/sleex/modules/session/Session.qml +++ b/src/share/sleex/modules/session/Session.qml @@ -10,10 +10,11 @@ import Quickshell.Io import Quickshell.Widgets import Quickshell.Wayland import Quickshell.Hyprland +import Sleex.Fhtc Scope { id: root - property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName) Loader { id: sessionLoader @@ -111,7 +112,7 @@ Scope { id: sessionLogout buttonIcon: "logout" buttonText: qsTr("Logout") - onClicked: { Quickshell.execDetached(["pkill", "Hyprland"]); sessionRoot.hide() } + onClicked: { Quickshell.execDetached(["pkill", "sleex"]); sessionRoot.hide() } onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText } KeyNavigation.left: sessionSleep KeyNavigation.right: sessionTaskManager @@ -224,5 +225,4 @@ Scope { sessionLoader.active = true; } } - } diff --git a/src/share/sleex/modules/settings/Display.qml b/src/share/sleex/modules/settings/Display.qml index c06df541..a4dc4c63 100644 --- a/src/share/sleex/modules/settings/Display.qml +++ b/src/share/sleex/modules/settings/Display.qml @@ -5,14 +5,14 @@ import Quickshell import QtQuick import QtQuick.Layouts import QtQuick.Controls -import Quickshell.Hyprland +import Sleex.Fhtc import Sleex.Services import "displaySettings" as DS ContentPage { id: root - property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var focusedScreen: Quickshell.screens.find(s => s.name === FhtcMonitors.activeMonitorName === screen.name) property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) property int nlStartHour: parseInt(Config.options.display.nightLightFrom?.split(":")[0] ?? "20") diff --git a/src/share/sleex/modules/settings/Style.qml b/src/share/sleex/modules/settings/Style.qml index f7815cce..69990498 100644 --- a/src/share/sleex/modules/settings/Style.qml +++ b/src/share/sleex/modules/settings/Style.qml @@ -3,7 +3,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Quickshell.Widgets import Qt5Compat.GraphicalEffects import QtMultimedia diff --git a/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml b/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml index e154e08e..13164aa8 100644 --- a/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml +++ b/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml @@ -267,7 +267,7 @@ Item { target === "None" ? "" : target ) - // If un-mirroring, give Hyprland more time to re-expose the monitor + // If un-mirroring, give the compositor more time to re-expose the monitor if (target === "None") { Qt.callLater(() => { Qt.createQmlObject('import QtQuick 2.0; Timer { interval: 1000; running: true; onTriggered: Monitors.refresh() }', root) diff --git a/src/share/sleex/modules/sidebarLeft/SidebarLeft.qml b/src/share/sleex/modules/sidebarLeft/SidebarLeft.qml deleted file mode 100644 index fbdfe662..00000000 --- a/src/share/sleex/modules/sidebarLeft/SidebarLeft.qml +++ /dev/null @@ -1,203 +0,0 @@ -import qs -import qs.services -import qs.modules.common -import qs.modules.common.widgets -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Effects -import Qt5Compat.GraphicalEffects -import Quickshell.Io -import Quickshell -import Quickshell.Widgets -import Quickshell.Wayland -import Quickshell.Hyprland - -Scope { // Scope - id: root - property int sidebarPadding: 15 - property bool detach: false - property Component contentComponent: SidebarLeftContent {} - property Item sidebarContent - - Component.onCompleted: { - root.sidebarContent = contentComponent.createObject(null, { - "scopeRoot": root, - }); - sidebarLoader.item.contentParent.children = [root.sidebarContent]; - } - - onDetachChanged: { - if (root.detach) { - sidebarContent.parent = null; // Detach content from sidebar - sidebarLoader.active = false; // Unload sidebar - detachedSidebarLoader.active = true; // Load detached window - detachedSidebarLoader.item.contentParent.children = [sidebarContent]; - } else { - sidebarContent.parent = null; // Detach content from window - detachedSidebarLoader.active = false; // Unload detached window - sidebarLoader.active = true; // Load sidebar - sidebarLoader.item.contentParent.children = [sidebarContent]; - } - } - - Loader { - id: sidebarLoader - active: true - - sourceComponent: PanelWindow { // Window - id: sidebarRoot - visible: GlobalStates.sidebarLeftOpen - - property bool extend: false - property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth - property var contentParent: sidebarLeftBackground - - function hide() { - GlobalStates.sidebarLeftOpen = false - } - - exclusiveZone: 0 - implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin - WlrLayershell.namespace: "quickshell:sidebarLeft" - // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close - // WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand - color: "transparent" - - anchors { - top: true - left: true - bottom: true - } - - mask: Region { - item: sidebarLeftBackground - } - - HyprlandFocusGrab { // Click outside to close - id: grab - windows: [ sidebarRoot ] - active: sidebarRoot.visible - onCleared: () => { - if (!active) sidebarRoot.hide() - } - } - - // Content - StyledRectangularShadow { - target: sidebarLeftBackground - radius: sidebarLeftBackground.radius - visible: Config.options.appearance.transparency - } - Rectangle { - id: sidebarLeftBackground - anchors.top: parent.top - anchors.left: parent.left - anchors.topMargin: Appearance.sizes.hyprlandGapsOut - anchors.leftMargin: Appearance.sizes.hyprlandGapsOut - 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 - - Behavior on width { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Keys.onPressed: (event) => { - if (event.key === Qt.Key_Escape) { - sidebarRoot.hide(); - } - if (event.modifiers === Qt.ControlModifier) { - if (event.key === Qt.Key_O) { - sidebarRoot.extend = !sidebarRoot.extend; - } - else if (event.key === Qt.Key_P) { - root.detach = !root.detach; - } - event.accepted = true; - } - } - } - } - } - - Loader { - id: detachedSidebarLoader - active: false - - sourceComponent: FloatingWindow { - id: detachedSidebarRoot - visible: GlobalStates.sidebarLeftOpen - property var contentParent: detachedSidebarBackground - - Rectangle { - id: detachedSidebarBackground - anchors.fill: parent - color: Appearance.colors.colLayer0 - - Keys.onPressed: (event) => { - if (event.modifiers === Qt.ControlModifier) { - if (event.key === Qt.Key_P) { - root.detach = !root.detach; - } - event.accepted = true; - } - } - } - } - } - - IpcHandler { - target: "sidebarLeft" - - function toggle(): void { - GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen - } - - function close(): void { - GlobalStates.sidebarLeftOpen = false - } - - function open(): void { - GlobalStates.sidebarLeftOpen = true - } - } - - GlobalShortcut { - name: "sidebarLeftToggle" - description: qsTr("Toggles left sidebar on press") - - onPressed: { - GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen; - } - } - - GlobalShortcut { - name: "sidebarLeftOpen" - description: qsTr("Opens left sidebar on press") - - onPressed: { - GlobalStates.sidebarLeftOpen = true; - } - } - - GlobalShortcut { - name: "sidebarLeftClose" - description: qsTr("Closes left sidebar on press") - - onPressed: { - GlobalStates.sidebarLeftOpen = false; - } - } - - GlobalShortcut { - name: "sidebarLeftToggleDetach" - description: qsTr("Detach left sidebar into a window/Attach it back") - - onPressed: { - root.detach = !root.detach; - } - } - -} diff --git a/src/share/sleex/modules/sidebarLeft/SidebarLeftContent.qml b/src/share/sleex/modules/sidebarLeft/SidebarLeftContent.qml deleted file mode 100644 index a2559978..00000000 --- a/src/share/sleex/modules/sidebarLeft/SidebarLeftContent.qml +++ /dev/null @@ -1,44 +0,0 @@ -import qs -import qs.services -import qs.modules.common -import qs.modules.common.widgets -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Effects -import Qt5Compat.GraphicalEffects -import Quickshell.Io -import Quickshell -import Quickshell.Widgets -import Quickshell.Wayland -import Quickshell.Hyprland - -Item { - id: root - required property var scopeRoot - anchors.fill: parent - - ColumnLayout { - anchors.fill: parent - anchors.margins: sidebarPadding - - spacing: sidebarPadding - - SwipeView { // Content pages - id: swipeView - Layout.topMargin: 5 - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 10 - - contentChildren: [ - aiChat.createObject() - ] - } - - Component { - id: aiChat - AiChat {} - } - } -} \ No newline at end of file diff --git a/src/share/sleex/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml b/src/share/sleex/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml index bd75a5d4..8746a59c 100644 --- a/src/share/sleex/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml +++ b/src/share/sleex/modules/sidebarLeft/aiChat/AnnotationSourceButton.qml @@ -4,7 +4,6 @@ import qs.modules.common.functions import qs.services import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland RippleButton { id: root diff --git a/src/share/sleex/modules/sidebarLeft/aiChat/MessageTextBlock.qml b/src/share/sleex/modules/sidebarLeft/aiChat/MessageTextBlock.qml index d68a260c..e74b45d7 100644 --- a/src/share/sleex/modules/sidebarLeft/aiChat/MessageTextBlock.qml +++ b/src/share/sleex/modules/sidebarLeft/aiChat/MessageTextBlock.qml @@ -8,7 +8,6 @@ import qs.modules.common.functions import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell.Hyprland ColumnLayout { id: root diff --git a/src/share/sleex/modules/wallpaperSelector/WallpaperSelector.qml b/src/share/sleex/modules/wallpaperSelector/WallpaperSelector.qml index 4630d489..adbf63ed 100644 --- a/src/share/sleex/modules/wallpaperSelector/WallpaperSelector.qml +++ b/src/share/sleex/modules/wallpaperSelector/WallpaperSelector.qml @@ -30,19 +30,9 @@ Scope { implicitWidth: 1920 implicitHeight: 200 WlrLayershell.namespace: "quickshell:wppselector" - // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab - // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" - HyprlandFocusGrab { - id: grab - windows: [ wppselectorRoot ] - active: GlobalStates.wppselectorOpen - onCleared: () => { - if (!active) wppselectorRoot.hide() - } - } - anchors { top: true } @@ -54,13 +44,13 @@ Scope { bottom: parent.bottom right: parent.right left: parent.left - topMargin: Appearance.sizes.hyprlandGapsOut - rightMargin: Appearance.sizes.hyprlandGapsOut - bottomMargin: Appearance.sizes.hyprlandGapsOut + topMargin: Appearance.sizes.gapsOut + rightMargin: Appearance.sizes.gapsOut + bottomMargin: Appearance.sizes.gapsOut leftMargin: Appearance.sizes.elevationMargin } - width: wppselectorWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin - height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 + width: wppselectorWidth - Appearance.sizes.gapsOut - Appearance.sizes.elevationMargin + height: parent.height - Appearance.sizes.gapsOut * 2 focus: GlobalStates.wppselectorOpen Keys.onPressed: (event) => { @@ -77,10 +67,10 @@ Scope { id: wppselectorBackground anchors.fill: parent - implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2 - implicitWidth: wppselectorWidth - Appearance.sizes.hyprlandGapsOut * 2 + implicitHeight: parent.height - Appearance.sizes.gapsOut * 2 + implicitWidth: wppselectorWidth - Appearance.sizes.gapsOut * 2 color: Appearance.colors.colLayer0 - radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1 + radius: Appearance.rounding.screenRounding - Appearance.sizes.gapsOut + 1 Rectangle { id: flickableBg @@ -193,5 +183,4 @@ Scope { GlobalStates.wppselectorOpen = false; } } - } diff --git a/src/share/sleex/plugins/src/Sleex/CMakeLists.txt b/src/share/sleex/plugins/src/Sleex/CMakeLists.txt index ef3fd398..4c51fca7 100644 --- a/src/share/sleex/plugins/src/Sleex/CMakeLists.txt +++ b/src/share/sleex/plugins/src/Sleex/CMakeLists.txt @@ -36,4 +36,5 @@ endfunction() add_subdirectory(services) add_subdirectory(core) add_subdirectory(utils) -add_subdirectory(widgets) \ No newline at end of file +add_subdirectory(widgets) +add_subdirectory(fhtc) diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/CMakeLists.txt b/src/share/sleex/plugins/src/Sleex/fhtc/CMakeLists.txt new file mode 100644 index 00000000..50cb6f15 --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/CMakeLists.txt @@ -0,0 +1,16 @@ + +qml_module(sleex-fhtc + URI Sleex.Fhtc + SOURCES + ipc.hpp ipc.cpp + workspaces.hpp workspaces.cpp + monitors.hpp monitors.cpp + plugin.cpp +) + +target_link_libraries(sleex-fhtc + PRIVATE + Qt6::Core + Qt6::Qml + Qt6::Network +) \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/ipc.cpp b/src/share/sleex/plugins/src/Sleex/fhtc/ipc.cpp new file mode 100644 index 00000000..a2b4454f --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/ipc.cpp @@ -0,0 +1,220 @@ +#include "ipc.hpp" +#include +#include +#include +#include +#include + +Ipc::Ipc(QObject *parent) +: QObject(parent), +m_requestSocket(new QLocalSocket(this)), +m_eventSocket(new QLocalSocket(this)) +{ + m_socketPath = qEnvironmentVariable("FHTC_SOCKET_PATH"); + if (m_socketPath.isEmpty()) { + emit error("The environment variable FHTC_SOCKET_PATH is not set."); + qWarning() << "Ipc: FHTC_SOCKET_PATH is not set."; + } + + // Connections for the request socket + connect(m_requestSocket, &QLocalSocket::connected, this, &Ipc::onRequestConnected); + connect(m_requestSocket, &QLocalSocket::readyRead, this, &Ipc::onRequestReadyRead); + connect(m_requestSocket, &QLocalSocket::errorOccurred, this, &Ipc::onRequestError); + connect(m_requestSocket, &QLocalSocket::disconnected, this, &Ipc::onRequestDisconnected); + + // Connections for the event socket + connect(m_eventSocket, &QLocalSocket::connected, this, &Ipc::onEventConnected); + connect(m_eventSocket, &QLocalSocket::readyRead, this, &Ipc::onEventReadyRead); + connect(m_eventSocket, &QLocalSocket::errorOccurred, this, &Ipc::onEventError); + connect(m_eventSocket, &QLocalSocket::disconnected, this, &Ipc::onEventDisconnected); +} + +Ipc::~Ipc() +{ + // Sockets are automatically destroyed because 'this' is their parent +} + + +void Ipc::subscribe() +{ + if (m_socketPath.isEmpty()) return; + if (m_eventSocket->state() == QLocalSocket::UnconnectedState) { + qDebug() << "Ipc: Connecting to the event socket..."; + m_eventSocket->connectToServer(m_socketPath); + } +} + +void Ipc::dispatch(const QString &name, const QVariantMap &args) +{ + if (!args.isEmpty() || name.contains("workspace")) { + QVariantMap actionBody; + actionBody.insert(name, args); + sendAction(actionBody); + } + else { + sendAction(name); + } +} + +void Ipc::sendRequest(const QVariant &request) +{ + if (m_socketPath.isEmpty()) return; + + // qDebug() << "Ipc: Queuing request:" << request; + + m_pendingRequests.enqueue(request); + if (m_requestSocket->state() == QLocalSocket::UnconnectedState) { + qDebug() << "Ipc: Connecting to the request socket..."; + m_requestSocket->connectToServer(m_socketPath); + } else if (m_requestSocket->state() == QLocalSocket::ConnectedState) { + // If already connected, send the pending request + writeJson(m_requestSocket, m_pendingRequests.dequeue()); + } + // If 'Connecting', the request will be sent in onRequestConnected +} + +void Ipc::sendAction(const QVariant &action) +{ + QVariantMap request; + request.insert("action", action); + sendRequest(request); +} + + +void Ipc::onRequestConnected() +{ + qDebug() << "Ipc: Request socket connected."; + // Send the first pending request (if any) + if (!m_pendingRequests.isEmpty()) { + writeJson(m_requestSocket, m_pendingRequests.dequeue()); + } +} + +void Ipc::onRequestReadyRead() +{ + m_requestBuffer.append(m_requestSocket->readAll()); + + // The fhtc IPC guarantees one response per request on this socket. + // We do not loop, we just wait for a complete line. + QVariant response = parseLine(m_requestBuffer); + if (!response.isNull()) { + // qDebug() << "Ipc: Received response:" << response; + + emit requestResponse(response); + + // If there are other requests, send them + if (!m_pendingRequests.isEmpty()) { + writeJson(m_requestSocket, m_pendingRequests.dequeue()); + } + } +} + +void Ipc::onRequestError(QLocalSocket::LocalSocketError socketError) +{ + Q_UNUSED(socketError); + QString msg = "Ipc (Request): " + m_requestSocket->errorString(); + qWarning() << msg; + emit error(msg); + m_pendingRequests.clear(); // Clear pending requests +} + +void Ipc::onRequestDisconnected() +{ + qDebug() << "Ipc: Request socket disconnected."; + if (!m_requestBuffer.isEmpty()) { + qWarning() << "Ipc (Request): Disconnected with data in buffer:" << m_requestBuffer; + m_requestBuffer.clear(); + } +} + + +void Ipc::onEventConnected() +{ + qDebug() << "Ipc: Event socket connected. Sending 'subscribe' request."; + emit subscribed(); + + // Send the subscribe request + QVariantMap subRequest; + subRequest.insert("subscribe", QVariant()); // "subscribe": null + writeJson(m_eventSocket, subRequest); +} + +void Ipc::onEventReadyRead() +{ + m_eventBuffer.append(m_eventSocket->readAll()); + + // There may be multiple JSON events in the buffer + while (true) { + QVariant event = parseLine(m_eventBuffer); + if (event.isNull()) { + break; // Incomplete line, wait for more data + } + emit newEvent(event); + } +} + +void Ipc::onEventError(QLocalSocket::LocalSocketError socketError) +{ + Q_UNUSED(socketError); + QString msg = "Ipc (Event): " + m_eventSocket->errorString(); + qWarning() << msg; + emit error(msg); +} + +void Ipc::onEventDisconnected() +{ + QString msg = "Ipc: Event socket disconnected. Attempting to reconnect in 5s..."; + qWarning() << msg; + emit error(msg); + + // Attempt automatic reconnection for the event socket + QTimer::singleShot(5000, this, &Ipc::subscribe); +} + +// --- Utility functions --- +void Ipc::writeJson(QLocalSocket *socket, const QVariant &data) +{ + if (socket->state() != QLocalSocket::ConnectedState) { + qWarning() << "Ipc: Attempt to write to a socket that is not connected."; + return; + } + + QJsonDocument doc = QJsonDocument::fromVariant(data); + if (doc.isNull()) { + qWarning() << "Ipc: Failed to convert QVariant to JSON."; + return; + } + + QByteArray json = doc.toJson(QJsonDocument::Compact); + json.append('\n'); // fhtc IPC is delimited by newlines + + socket->write(json); +} + +QVariant Ipc::parseLine(QByteArray &buffer) +{ + int newlineIdx = buffer.indexOf('\n'); + if (newlineIdx == -1) { + return QVariant(); // Incomplete line + } + + // Extract the line and remove it from the buffer + QByteArray line = buffer.left(newlineIdx); + buffer = buffer.mid(newlineIdx + 1); + + if (line.isEmpty()) { + return QVariant(); // Empty line, ignore + } + + QJsonParseError parseError; + QJsonDocument doc = QJsonDocument::fromJson(line, &parseError); + + if (parseError.error != QJsonParseError::NoError) { + QString msg = "Ipc: JSON parse error: " + parseError.errorString() + " | Line: " + QString(line); + qWarning() << msg; + emit error(msg); + return QVariant(); + } + + return doc.toVariant(); +} diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/ipc.hpp b/src/share/sleex/plugins/src/Sleex/fhtc/ipc.hpp new file mode 100644 index 00000000..ca5abb6f --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/ipc.hpp @@ -0,0 +1,56 @@ +#ifndef IPC_H +#define IPC_H + +#include +#include +#include +#include +#include + +class Ipc : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(FhtcIpc) + QML_SINGLETON + +public: + explicit Ipc(QObject *parent = nullptr); + ~Ipc() override; + +signals: + void error(const QString &message); + void requestResponse(const QVariant &response); + void subscribed(); + void newEvent(const QVariant &event); + +public slots: + void subscribe(); + void sendRequest(const QVariant &request); + void sendAction(const QVariant &action); + void dispatch(const QString &name, const QVariantMap &args = {}); + +private slots: + void onRequestConnected(); + void onRequestReadyRead(); + void onRequestError(QLocalSocket::LocalSocketError socketError); + void onRequestDisconnected(); + + void onEventConnected(); + void onEventReadyRead(); + void onEventError(QLocalSocket::LocalSocketError socketError); + void onEventDisconnected(); + +private: + void writeJson(QLocalSocket* socket, const QVariant &data); + QVariant parseLine(QByteArray &buffer); + + QString m_socketPath; + QLocalSocket *m_requestSocket; + QLocalSocket *m_eventSocket; + + QByteArray m_eventBuffer; + QByteArray m_requestBuffer; + QQueue m_pendingRequests; +}; + +#endif // IPC_H \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/monitors.cpp b/src/share/sleex/plugins/src/Sleex/fhtc/monitors.cpp new file mode 100644 index 00000000..05db8968 --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/monitors.cpp @@ -0,0 +1,46 @@ +#include "monitors.hpp" +#include + +Monitors::Monitors(QObject *parent) : QObject(parent) +{ + m_ipc = new Ipc(this); + + connect(m_ipc, &Ipc::newEvent, this, &Monitors::handleEvent); + + m_ipc->subscribe(); +} + +void Monitors::handleEvent(const QVariant &eventVar) +{ + QVariantMap event = eventVar.toMap(); + QString type = event.value("event").toString(); + QVariant data = event.value("data"); + + if (type == "space") { + QVariant data = event.value("data"); + QVariantMap monitorsMap = data.toMap().value("monitors").toMap(); + + m_monitors = monitorsMap; + + bool activeFound = false; + QMapIterator i(m_monitors); + while (i.hasNext()) { + i.next(); + QVariantMap monitor = i.value().toMap(); + if (monitor.value("active").toBool()) { + m_activeMonitor = monitor; + m_activeMonitorName = monitor.value("output").toString(); + activeFound = true; + break; + } + } + + if (!activeFound) { + m_activeMonitor = QVariant(); + } + + emit monitorsChanged(); + emit activeMonitorChanged(); + emit activeMonitorNameChanged(); + } +} \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/monitors.hpp b/src/share/sleex/plugins/src/Sleex/fhtc/monitors.hpp new file mode 100644 index 00000000..fd0a3fc6 --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/monitors.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include "ipc.hpp" + +class Monitors : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(FhtcMonitors) + QML_SINGLETON + + Q_PROPERTY(QVariantMap monitors READ monitors NOTIFY monitorsChanged) + Q_PROPERTY(QVariant activeMonitor READ activeMonitor NOTIFY activeMonitorChanged) + Q_PROPERTY(QString activeMonitorName READ activeMonitorName NOTIFY activeMonitorNameChanged) + +public: + explicit Monitors(QObject *parent = nullptr); + + QVariantMap monitors() const { return m_monitors; } + QVariant activeMonitor() const { return m_activeMonitor; } + QString activeMonitorName() const { return m_activeMonitorName; } + +signals: + void monitorsChanged(); + void activeMonitorChanged(); + void activeMonitorNameChanged(); + +private slots: + void handleEvent(const QVariant &eventVar); + +private: + Ipc *m_ipc; + + QVariantMap m_monitors; + QVariant m_activeMonitor; + QString m_activeMonitorName; +}; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/plugin.cpp b/src/share/sleex/plugins/src/Sleex/fhtc/plugin.cpp new file mode 100644 index 00000000..aff6826b --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/plugin.cpp @@ -0,0 +1,14 @@ +#include + +class SleexFhtcPlugin : public QQmlExtensionPlugin { + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + void registerTypes(const char *uri) override { + // QML_ELEMENT and QML_SINGLETON macros handle registration automatically + Q_UNUSED(uri) + } +}; + +#include "plugin.moc" \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.cpp b/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.cpp new file mode 100644 index 00000000..145f2d5a --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.cpp @@ -0,0 +1,94 @@ +#include "workspaces.hpp" +#include + +Workspaces::Workspaces(QObject *parent) : QObject(parent) +{ + m_ipc = new Ipc(this); + + connect(m_ipc, &Ipc::newEvent, this, &Workspaces::handleEvent); + + m_ipc->subscribe(); +} + +void Workspaces::handleEvent(const QVariant &eventVar) +{ + QVariantMap event = eventVar.toMap(); + QString type = event.value("event").toString(); + QVariant data = event.value("data"); + + if (type == "windows") { + m_windows = data.toMap(); + emit windowsChanged(); + } + else if (type == "focused-window-changed") { + QVariantMap dataMap = data.toMap(); + QVariant idVar = dataMap.value("id"); + + if (idVar.isNull() || !idVar.isValid()) { + m_focusedWindowId = -1; + m_focusedWindow = QVariant(); + } else { + m_focusedWindowId = idVar.toInt(); + m_focusedWindow = m_windows.value(QString::number(m_focusedWindowId)); + } + emit focusedWindowIdChanged(); + emit focusedWindowChanged(); + } + else if (type == "window-closed") { + int id = data.toMap().value("id").toInt(); + m_windows.remove(QString::number(id)); + emit windowsChanged(); + } + else if (type == "window-changed") { + QVariantMap win = data.toMap(); + int id = win.value("id").toInt(); + + m_windows.insert(QString::number(id), win); + emit windowsChanged(); + + if (id == m_focusedWindowId) { + m_focusedWindow = win; + emit focusedWindowChanged(); + } + } + else if (type == "workspaces") { + m_workspaces = data.toMap(); + emit workspacesChanged(); + + if (m_activeWorkspaceId != -1) { + m_activeWorkspace = m_workspaces.value(QString::number(m_activeWorkspaceId)); + emit activeWorkspaceChanged(); + } + } + else if (type == "active-workspace-changed") { + QVariantMap dataMap = data.toMap(); + QVariant idVar = dataMap.value("id"); + + if (idVar.isNull() || !idVar.isValid()) { + m_activeWorkspaceId = -1; + m_activeWorkspace = QVariant(); + } else { + m_activeWorkspaceId = idVar.toInt(); + m_activeWorkspace = m_workspaces.value(QString::number(m_activeWorkspaceId)); + } + emit activeWorkspaceIdChanged(); + emit activeWorkspaceChanged(); + } + else if (type == "workspace-changed") { + QVariantMap ws = data.toMap(); + int id = ws.value("id").toInt(); + + m_workspaces.insert(QString::number(id), ws); + emit workspacesChanged(); + + if (id == m_activeWorkspaceId) { + m_activeWorkspace = ws; + emit activeWorkspaceChanged(); + } + } + else if (type == "workspace-removed") { + int id = data.toMap().value("id").toInt(); + m_workspaces.remove(QString::number(id)); + emit workspacesChanged(); + } +} \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.hpp b/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.hpp new file mode 100644 index 00000000..72fa77bc --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/fhtc/workspaces.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include "ipc.hpp" + +class Workspaces : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(FhtcWorkspaces) + QML_SINGLETON + + Q_PROPERTY(QVariantMap windows READ windows NOTIFY windowsChanged) + Q_PROPERTY(QVariantMap workspaces READ workspaces NOTIFY workspacesChanged) + + Q_PROPERTY(int focusedWindowId READ focusedWindowId NOTIFY focusedWindowIdChanged) + Q_PROPERTY(QVariant focusedWindow READ focusedWindow NOTIFY focusedWindowChanged) + + Q_PROPERTY(int activeWorkspaceId READ activeWorkspaceId NOTIFY activeWorkspaceIdChanged) + Q_PROPERTY(QVariant activeWorkspace READ activeWorkspace NOTIFY activeWorkspaceChanged) + +public: + explicit Workspaces(QObject *parent = nullptr); + + QVariantMap windows() const { return m_windows; } + QVariantMap workspaces() const { return m_workspaces; } + int focusedWindowId() const { return m_focusedWindowId; } + QVariant focusedWindow() const { return m_focusedWindow; } + int activeWorkspaceId() const { return m_activeWorkspaceId; } + QVariant activeWorkspace() const { return m_activeWorkspace; } + +signals: + void windowsChanged(); + void workspacesChanged(); + void focusedWindowIdChanged(); + void focusedWindowChanged(); + void activeWorkspaceIdChanged(); + void activeWorkspaceChanged(); + +private slots: + void handleEvent(const QVariant &eventVar); + +private: + Ipc *m_ipc; + + QVariantMap m_windows; + QVariantMap m_workspaces; + int m_focusedWindowId = -1; + QVariant m_focusedWindow; + int m_activeWorkspaceId = -1; + QVariant m_activeWorkspace; +}; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.cpp b/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.cpp index a28f5a64..b87dbb66 100644 --- a/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.cpp +++ b/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.cpp @@ -2,6 +2,7 @@ #include "DesktopStateManager.hpp" #include #include +#include DesktopModel::DesktopModel(QObject *parent) : QAbstractListModel(parent) {} @@ -34,7 +35,29 @@ QHash DesktopModel::roleNames() const { return roles; } +QPoint DesktopModel::getEmptySpot(const QSet &occupied) const { + for (int x = 0; ; ++x) { + for (int y = 0; y < m_rows; ++y) { + QString key = QString::number(x) + "," + QString::number(y); + if (!occupied.contains(key)) { + return QPoint(x, y); + } + } + } +} + void DesktopModel::loadDirectory(const QString &path) { + m_watchedPath = path; + + if (!m_watcher.directories().isEmpty()) + m_watcher.removePaths(m_watcher.directories()); + + m_watcher.addPath(path); + + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, + this, &DesktopModel::onDirectoryChanged, + Qt::UniqueConnection); + beginResetModel(); m_items.clear(); @@ -45,6 +68,14 @@ void DesktopModel::loadDirectory(const QString &path) { DesktopStateManager sm; QVariantMap savedLayout = sm.getLayout(); + QSet occupied; + for (const QFileInfo &fileInfo : list) { + if (savedLayout.contains(fileInfo.fileName())) { + QVariantMap pos = savedLayout[fileInfo.fileName()].toMap(); + occupied.insert(QString::number(pos["x"].toInt()) + "," + QString::number(pos["y"].toInt())); + } + } + for (const QFileInfo &fileInfo : list) { DesktopItem item; item.fileName = fileInfo.fileName(); @@ -56,15 +87,20 @@ void DesktopModel::loadDirectory(const QString &path) { item.gridX = pos["x"].toInt(); item.gridY = pos["y"].toInt(); } else { - // TODO: make getEmptySpot in C++ and call it here to get the initial position for new icons - item.gridX = 0; - item.gridY = 0; + QPoint spot = getEmptySpot(occupied); + item.gridX = spot.x(); + item.gridY = spot.y(); + occupied.insert(QString::number(item.gridX) + "," + QString::number(item.gridY)); } m_items.append(item); } endResetModel(); } +void DesktopModel::onDirectoryChanged() { + loadDirectory(m_watchedPath); +} + void DesktopModel::moveIcon(int index, int newX, int newY) { if (index < 0 || index >= m_items.size()) return; diff --git a/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.hpp b/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.hpp index eeebb2cf..a4090c34 100644 --- a/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.hpp +++ b/src/share/sleex/plugins/src/Sleex/utils/DesktopModel.hpp @@ -4,6 +4,7 @@ #include #include #include +#include struct DesktopItem { QString fileName; @@ -36,7 +37,23 @@ class DesktopModel : public QAbstractListModel { Q_INVOKABLE void moveIcon(int index, int newX, int newY); Q_INVOKABLE void massMove(const QVariantList &selectedPathsList, const QString &leaderPath, int targetX, int targetY, int maxCol, int maxRow); + Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) + +public: + int rows() const { return m_rows; } + void setRows(int r) { + if (m_rows != r) { m_rows = r; emit rowsChanged(); } + } + +signals: + void rowsChanged(); + private: + int m_rows = 1; QList m_items; + QString m_watchedPath; + QFileSystemWatcher m_watcher; void saveCurrentLayout(); + QPoint getEmptySpot(const QSet &occupied) const; + void onDirectoryChanged(); }; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.cpp b/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.cpp index be6993c4..88ef6ea2 100644 --- a/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.cpp +++ b/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.cpp @@ -5,6 +5,7 @@ #include #include #include +#include DesktopStateManager::DesktopStateManager(QObject *parent) : QObject(parent) {} @@ -26,7 +27,7 @@ void DesktopStateManager::saveLayout(const QVariantMap& layout) { file.write(doc.toJson(QJsonDocument::Indented)); file.close(); } else { - qWarning() << "Sleex: Impossible de sauvegarder le layout du bureau dans" << getConfigFilePath(); + qWarning() << "Sleex: Cannot save desktop layout to" << getConfigFilePath(); } } @@ -46,4 +47,43 @@ QVariantMap DesktopStateManager::getLayout() { } return QVariantMap(); +} + +QString DesktopStateManager::getPostItsFilePath() const { + QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/sleex"; + QDir dir(configDir); + if (!dir.exists()) dir.mkpath("."); + + return configDir + "/postits.json"; +} + +void DesktopStateManager::savePostIts(const QVariantList& postits) { + QJsonArray jsonArr = QJsonArray::fromVariantList(postits); + QJsonDocument doc(jsonArr); + QFile file(getPostItsFilePath()); + + if (file.open(QIODevice::WriteOnly)) { + file.write(doc.toJson(QJsonDocument::Indented)); + file.close(); + } else { + qWarning() << "Sleex: Cannot save post-its to" << getPostItsFilePath(); + } +} + +QVariantList DesktopStateManager::getPostIts() { + QFile file(getPostItsFilePath()); + + if (!file.open(QIODevice::ReadOnly)) { + return QVariantList(); + } + + QByteArray data = file.readAll(); + file.close(); + + QJsonDocument doc = QJsonDocument::fromJson(data); + if (doc.isArray()) { + return doc.array().toVariantList(); + } + + return QVariantList(); } \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.hpp b/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.hpp index a83271f9..55af1740 100644 --- a/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.hpp +++ b/src/share/sleex/plugins/src/Sleex/utils/DesktopStateManager.hpp @@ -2,6 +2,7 @@ #include #include +#include #include class DesktopStateManager : public QObject { @@ -14,7 +15,11 @@ class DesktopStateManager : public QObject { Q_INVOKABLE void saveLayout(const QVariantMap& layout); Q_INVOKABLE QVariantMap getLayout(); + + Q_INVOKABLE void savePostIts(const QVariantList& postits); + Q_INVOKABLE QVariantList getPostIts(); private: QString getConfigFilePath() const; + QString getPostItsFilePath() const; }; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/CMakeLists.txt b/src/share/sleex/plugins/src/Sleex/widgets/CMakeLists.txt index b0931ed5..eac681a7 100755 --- a/src/share/sleex/plugins/src/Sleex/widgets/CMakeLists.txt +++ b/src/share/sleex/plugins/src/Sleex/widgets/CMakeLists.txt @@ -1,13 +1,65 @@ +enable_language(C) + pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0) +find_package(Qt6 REQUIRED COMPONENTS GuiPrivate) + +pkg_check_modules(WAYLAND_CLIENT REQUIRED IMPORTED_TARGET wayland-client) +pkg_check_modules(WAYLAND_PROTOS REQUIRED IMPORTED_TARGET wayland-protocols) +pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm) +pkg_check_modules(DRM REQUIRED IMPORTED_TARGET libdrm) +pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl) +pkg_check_modules(GLESv2 REQUIRED IMPORTED_TARGET glesv2) + +find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) + +pkg_get_variable(WL_PROTO_DIR wayland-protocols pkgdatadir) +set(PROTO_DIR "${CMAKE_SOURCE_DIR}/protocols") + +set(WAYLAND_PROTO_SOURCES "") + +macro(wayland_add_protocol_sources _xml) + get_filename_component(_base "${_xml}" NAME_WE) + set(_header "${CMAKE_CURRENT_BINARY_DIR}/${_base}-client-protocol.h") + set(_source "${CMAKE_CURRENT_BINARY_DIR}/${_base}-client-protocol.c") + + add_custom_command( + OUTPUT "${_header}" + COMMAND "${WAYLAND_SCANNER}" client-header "${_xml}" "${_header}" + DEPENDS "${_xml}" + VERBATIM + ) + add_custom_command( + OUTPUT "${_source}" + COMMAND "${WAYLAND_SCANNER}" private-code "${_xml}" "${_source}" + DEPENDS "${_xml}" + VERBATIM + ) + list(APPEND WAYLAND_PROTO_SOURCES "${_header}" "${_source}") +endmacro() + +wayland_add_protocol_sources("${WL_PROTO_DIR}/staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml") +wayland_add_protocol_sources("${WL_PROTO_DIR}/staging/ext-image-capture-source/ext-image-capture-source-v1.xml") +wayland_add_protocol_sources("${WL_PROTO_DIR}/staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml") +wayland_add_protocol_sources("${WL_PROTO_DIR}/unstable/linux-dmabuf/linux-dmabuf-unstable-v1.xml") + +add_library(sleex_wayland_protocols STATIC ${WAYLAND_PROTO_SOURCES}) +target_link_libraries(sleex_wayland_protocols PUBLIC PkgConfig::WAYLAND_CLIENT) +target_include_directories(sleex_wayland_protocols PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +set_target_properties(sleex_wayland_protocols PROPERTIES POSITION_INDEPENDENT_CODE ON) + qml_module(sleex-widgets URI Sleex.Widgets SOURCES ContributionCalendar.cpp ContributionCalendar.hpp + ExtCaptureItem.cpp ExtCaptureItem.hpp + GbmBuffer.cpp GbmBuffer.hpp plugin.cpp ) target_include_directories(sleex-widgets PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNM_INCLUDE_DIRS} ${GLIB_INCLUDE_DIRS} ) @@ -15,6 +67,7 @@ target_include_directories(sleex-widgets PRIVATE target_link_libraries(sleex-widgets PRIVATE ${LIBNM_LIBRARIES} ${GLIB_LIBRARIES} + sleex_wayland_protocols ) target_compile_options(sleex-widgets PRIVATE @@ -25,6 +78,13 @@ target_compile_options(sleex-widgets PRIVATE target_link_libraries(sleex-widgets PRIVATE Qt6::Core + Qt6::Gui + Qt6::GuiPrivate Qt6::Qml Qt6::Quick + PkgConfig::WAYLAND_CLIENT + PkgConfig::GBM + PkgConfig::DRM + PkgConfig::EGL + PkgConfig::GLESv2 ) \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/ContributionCalendar.cpp b/src/share/sleex/plugins/src/Sleex/widgets/ContributionCalendar.cpp old mode 100644 new mode 100755 diff --git a/src/share/sleex/plugins/src/Sleex/widgets/ContributionCalendar.hpp b/src/share/sleex/plugins/src/Sleex/widgets/ContributionCalendar.hpp old mode 100644 new mode 100755 diff --git a/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.cpp b/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.cpp new file mode 100644 index 00000000..8c40b4b8 --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.cpp @@ -0,0 +1,575 @@ +#include "ExtCaptureItem.hpp" +#include "GbmBuffer.hpp" + +#include "ext-image-copy-capture-v1-client-protocol.h" +#include "ext-image-capture-source-v1-client-protocol.h" +#include "ext-foreign-toplevel-list-v1-client-protocol.h" +#include "linux-dmabuf-unstable-v1-client-protocol.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +static const wl_registry_listener s_registryListener = { + .global = &ExtCaptureItem::onRegistryGlobal, + .global_remove = &ExtCaptureItem::onRegistryGlobalRemove, +}; + +static const ext_foreign_toplevel_list_v1_listener s_toplevelListListener = { + .toplevel = &ExtCaptureItem::onToplevelListToplevel, + .finished = &ExtCaptureItem::onToplevelListFinished, +}; + +static const ext_foreign_toplevel_handle_v1_listener s_toplevelHandleListener = { + .closed = &ExtCaptureItem::onToplevelClosed, + .done = &ExtCaptureItem::onToplevelDone, + .title = &ExtCaptureItem::onToplevelTitle, + .app_id = &ExtCaptureItem::onToplevelAppId, + .identifier = &ExtCaptureItem::onToplevelIdentifier, +}; + +static const ext_image_copy_capture_session_v1_listener s_sessionListener = { + .buffer_size = &ExtCaptureItem::onSessionBufferSize, + .shm_format = &ExtCaptureItem::onSessionShmFormat, + .dmabuf_device = &ExtCaptureItem::onSessionDmabufDevice, + .dmabuf_format = &ExtCaptureItem::onSessionDmabufFormat, + .done = &ExtCaptureItem::onSessionDone, + .stopped = &ExtCaptureItem::onSessionStopped, +}; + +static const ext_image_copy_capture_frame_v1_listener s_frameListener = { + .transform = &ExtCaptureItem::onFrameTransform, + .damage = &ExtCaptureItem::onFrameDamage, + .presentation_time = &ExtCaptureItem::onFramePresentationTime, + .ready = &ExtCaptureItem::onFrameReady, + .failed = &ExtCaptureItem::onFrameFailed, +}; + +ExtCaptureItem::ExtCaptureItem(QQuickItem* parent) : QQuickItem(parent) +{ + setFlag(ItemHasContents, true); +} + +ExtCaptureItem::~ExtCaptureItem() +{ + stopCapture(); + + // Destroy protocol objects + if (m_toplevelList) ext_foreign_toplevel_list_v1_destroy(m_toplevelList); + if (m_sourceManager) ext_foreign_toplevel_image_capture_source_manager_v1_destroy(m_sourceManager); + if (m_captureManager) ext_image_copy_capture_manager_v1_destroy(m_captureManager); + if (m_dmabuf) zwp_linux_dmabuf_v1_destroy(m_dmabuf); + + // release GBM buffers before the device + for (auto& buf : m_buffers) buf.reset(); + + if (m_gbmDev) gbm_device_destroy(m_gbmDev); + if (m_drmFd >= 0) close(m_drmFd); +} + +void ExtCaptureItem::componentComplete() +{ + QQuickItem::componentComplete(); + initWayland(); + initGbm(); + if (m_active) tryStartCapture(); +} + +void ExtCaptureItem::setActive(bool active) +{ + if (m_active == active) return; + m_active = active; + emit activeChanged(); + + if (m_waylandReady) { + if (active) tryStartCapture(); + else stopCapture(); + } +} + +void ExtCaptureItem::initWayland() +{ + // QPlatformNativeInterface is available on all Qt 5/6 Wayland QPA backends + // without pulling in any QtWaylandClient private headers. + QPlatformNativeInterface* ni = qGuiApp->platformNativeInterface(); + if (!ni) { + qWarning() << "[ExtCapture] Not running on a Wayland QPA backend"; + return; + } + m_display = static_cast( + ni->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); + if (!m_display) { + qWarning() << "[ExtCapture] wl_display not available via QPlatformNativeInterface"; + return; + } + + // registry + m_registry = wl_display_get_registry(m_display); + wl_registry_add_listener(m_registry, &s_registryListener, this); + + // Blocking roundtrip so that all globals are bound synchronously before + // we proceed. A production implementation may prefer a deferred approach. + wl_display_roundtrip(m_display); + wl_display_roundtrip(m_display); // second roundtrip picks up any late events + + m_waylandReady = true; +} + +// Sometimes, even I don't understand my own code +void ExtCaptureItem::onRegistryGlobal(void* data, wl_registry* registry, uint32_t name, const char* iface, uint32_t version) +{ + auto* self = static_cast(data); + + if (strcmp(iface, ext_image_copy_capture_manager_v1_interface.name) == 0) { + self->m_captureManager = static_cast(wl_registry_bind(registry, name, &ext_image_copy_capture_manager_v1_interface, qMin(version, 1u))); + } + else if (strcmp(iface, ext_foreign_toplevel_image_capture_source_manager_v1_interface.name) == 0) { + self->m_sourceManager = static_cast( wl_registry_bind(registry, name, &ext_foreign_toplevel_image_capture_source_manager_v1_interface, qMin(version, 1u))); + } + else if (strcmp(iface, ext_foreign_toplevel_list_v1_interface.name) == 0) { + self->m_toplevelList = static_cast(wl_registry_bind(registry, name, &ext_foreign_toplevel_list_v1_interface, qMin(version, 1u))); + ext_foreign_toplevel_list_v1_add_listener(self->m_toplevelList, &s_toplevelListListener, self); + } + else if (strcmp(iface, zwp_linux_dmabuf_v1_interface.name) == 0) { + if (version >= 3) { + self->m_dmabuf = static_cast(wl_registry_bind(registry, name, &zwp_linux_dmabuf_v1_interface, qMin(version, 4u))); + } + } +} + +void ExtCaptureItem::onRegistryGlobalRemove(void* /*data*/, wl_registry*, uint32_t /*name*/) {} + + +void ExtCaptureItem::onToplevelListToplevel(void* data, ext_foreign_toplevel_list_v1*, ext_foreign_toplevel_handle_v1* handle) +{ + auto* self = static_cast(data); + ToplevelInfo info; + info.handle = handle; + self->m_pendingToplevels.append(info); + ext_foreign_toplevel_handle_v1_add_listener(handle, &s_toplevelHandleListener, self); +} + +void ExtCaptureItem::onToplevelListFinished(void* data, ext_foreign_toplevel_list_v1*) { + // The list won't send more toplevels. We're watching ongoing events via + // onToplevelDone / onToplevelClosed. + Q_UNUSED(data) +} + +void ExtCaptureItem::onToplevelAppId(void* data, ext_foreign_toplevel_handle_v1* handle, const char* app_id) +{ + auto* self = static_cast(data); + for (auto& t : self->m_pendingToplevels) { + if (t.handle == handle) { + t.appId = QString::fromUtf8(app_id); + return; + } + } +} + +void ExtCaptureItem::onToplevelTitle(void* data, ext_foreign_toplevel_handle_v1* handle, const char* title) +{ + auto* self = static_cast(data); + for (auto& t : self->m_pendingToplevels) { + if (t.handle == handle) { + t.title = QString::fromUtf8(title); + emit self->windowsChanged(); + return; + } + } +} + +void ExtCaptureItem::onToplevelIdentifier(void* data, ext_foreign_toplevel_handle_v1* handle, const char* identifier) +{ + auto* self = static_cast(data); + for (auto& t : self->m_pendingToplevels) { + if (t.handle == handle) { + t.identifier = QString::fromUtf8(identifier); + emit self->windowsChanged(); + return; + } + } +} + +void ExtCaptureItem::setWindowId(const QString& id) +{ + if (m_windowId == id) return; + m_windowId = id; + emit windowIdChanged(); + + if (m_waylandReady) { + stopCapture(); + m_targetHandle = nullptr; + + for (const auto& t : m_pendingToplevels) { + if (t.identifier == m_windowId) { + m_targetHandle = t.handle; + break; + } + } + + if (m_active && m_targetHandle) tryStartCapture(); + } +} + +void ExtCaptureItem::onToplevelDone(void* data, ext_foreign_toplevel_handle_v1* handle) +{ + auto* self = static_cast(data); + for (auto& t : self->m_pendingToplevels) { + if (t.handle == handle && !t.matched) { + if (!self->m_windowId.isEmpty() && t.identifier == self->m_windowId) { + t.matched = true; + self->m_targetHandle = handle; + if (self->m_active && self->m_waylandReady) + self->tryStartCapture(); + } + return; + } + } +} + +void ExtCaptureItem::onToplevelClosed(void* data, ext_foreign_toplevel_handle_v1* handle) +{ + auto* self = static_cast(data); + if (self->m_targetHandle == handle) { + self->stopCapture(); + self->m_targetHandle = nullptr; + } + // Remove from pending list + for (int i = self->m_pendingToplevels.size() - 1; i >= 0; --i) { + if (self->m_pendingToplevels[i].handle == handle) { + self->m_pendingToplevels.removeAt(i); + } + } +} + +void ExtCaptureItem::tryStartCapture() +{ + if (!m_captureManager || !m_sourceManager || !m_targetHandle) return; + if (m_session) return; // already running + + m_source = ext_foreign_toplevel_image_capture_source_manager_v1_create_source(m_sourceManager, m_targetHandle); + + m_session = ext_image_copy_capture_manager_v1_create_session(m_captureManager, m_source, 0u); + + ext_image_copy_capture_session_v1_add_listener(m_session, &s_sessionListener, this); + + // The session will fire buffer_size + dmabuf_format* + done events in a batch. Flush and dispatch. + wl_display_flush(m_display); +} + +void ExtCaptureItem::stopCapture() +{ + if (m_frame) { + ext_image_copy_capture_frame_v1_destroy(m_frame); + m_frame = nullptr; + } + if (m_session) { + ext_image_copy_capture_session_v1_destroy(m_session); + m_session = nullptr; + } + if (m_source) { + ext_image_capture_source_v1_destroy(m_source); + m_source = nullptr; + } + m_formatsReceived = false; + + if (m_running) { + m_running = false; + emit runningChanged(); + } +} + +void ExtCaptureItem::onSessionBufferSize(void* data, ext_image_copy_capture_session_v1*, uint32_t w, uint32_t h) +{ + auto* self = static_cast(data); + self->m_captureSize = QSize(static_cast(w), static_cast(h)); +} + +void ExtCaptureItem::onSessionShmFormat(void*, ext_image_copy_capture_session_v1*, uint32_t) { + // We only use DMA-BUF. ignore SHM formats. +} + +void ExtCaptureItem::onSessionDmabufDevice(void*, ext_image_copy_capture_session_v1*, wl_array* /*device*/) { + // The device hint is optional; we already opened the render node. +} + +void ExtCaptureItem::onSessionDmabufFormat(void* data, ext_image_copy_capture_session_v1*, uint32_t fmt, wl_array* modifiers) +{ + auto* self = static_cast(data); + const auto* modifierValues = static_cast(modifiers->data); + const int modifierCount = static_cast(modifiers->size / sizeof(uint64_t)); + for (int i = 0; i < modifierCount; ++i) { + self->m_dmabufFormats.append({ fmt, modifierValues[i] }); + } +} + +void ExtCaptureItem::onSessionDone(void* data, ext_image_copy_capture_session_v1*) +{ + // All constraints received. Allocate buffers and request the first frame. + auto* self = static_cast(data); + self->m_formatsReceived = true; + self->allocateBuffers(); + self->requestNextFrame(); +} + +void ExtCaptureItem::onSessionStopped(void* data, ext_image_copy_capture_session_v1*) +{ + auto* self = static_cast(data); + qWarning() << "[ExtCapture] Session stopped by compositor"; + self->stopCapture(); +} + +void ExtCaptureItem::initGbm() +{ + // Open the first available render node + for (int i = 128; i < 138 && m_drmFd < 0; ++i) { + QString path = QStringLiteral("/dev/dri/renderD%1").arg(i); + m_drmFd = open(path.toLocal8Bit().constData(), O_RDWR | O_CLOEXEC); + } + if (m_drmFd < 0) { + qWarning() << "[ExtCapture] Failed to open DRM render node"; + return; + } + m_gbmDev = gbm_create_device(m_drmFd); + if (!m_gbmDev) { + qWarning() << "[ExtCapture] gbm_create_device failed"; + close(m_drmFd); + m_drmFd = -1; + } +} + +void ExtCaptureItem::allocateBuffers() +{ + if (!m_gbmDev || m_captureSize.isEmpty()) return; + + uint32_t fmt = DRM_FORMAT_XRGB8888; + uint64_t modifier = DRM_FORMAT_MOD_LINEAR; + + for (const auto& fi : m_dmabufFormats) { + if (fi.format == DRM_FORMAT_XRGB8888) { + fmt = fi.format; + modifier = fi.modifier; + break; + } + } + + for (int i = 0; i < POOL; ++i) { + auto buf = std::make_unique(); + if (!buf->allocate(m_gbmDev, + m_captureSize.width(), + m_captureSize.height(), + fmt, modifier)) { + qWarning() << "[ExtCapture] Failed to allocate GBM buffer" << i; + return; + } + createWaylandBuffer(buf.get()); + m_buffers[i] = std::move(buf); + } +} + +void ExtCaptureItem::createWaylandBuffer(GbmBuffer* buf) +{ + if (!m_dmabuf) return; + + zwp_linux_buffer_params_v1* params = zwp_linux_dmabuf_v1_create_params(m_dmabuf); + + // Add the single plane + zwp_linux_buffer_params_v1_add(params, + buf->fd(), + 0, // plane index + buf->offset(), + buf->stride(), + static_cast(buf->modifier() >> 32), + static_cast(buf->modifier() & 0xFFFFFFFF) + ); + + wl_buffer* wlBuf = zwp_linux_buffer_params_v1_create_immed(params, buf->width(), buf->height(), buf->format(), 0 /* flags */); + + zwp_linux_buffer_params_v1_destroy(params); + buf->setWaylandBuffer(wlBuf); +} + +GbmBuffer* ExtCaptureItem::acquireCaptureBuffer() +{ + // Return a buffer that Qt is not currently reading from. + for (int i = 0; i < POOL; ++i) { + if (m_buffers[i] && !m_buffers[i]->inUse) { + m_captureIdx = i; + return m_buffers[i].get(); + } + } + return nullptr; +} + +void ExtCaptureItem::requestNextFrame() { + if (!m_session || m_frame || !m_formatsReceived) return; + + int target = -1; + for (int i = 0; i < POOL; ++i) { + if (!m_buffers[i]->inUse && !m_buffers[i]->ready && !m_buffers[i]->isCapturing) { + target = i; + break; + } + } + + if (target == -1) return; + + m_captureIdx = target; + m_buffers[target]->isCapturing = true; + + m_frame = ext_image_copy_capture_session_v1_create_frame(m_session); + ext_image_copy_capture_frame_v1_add_listener(m_frame, &s_frameListener, this); + ext_image_copy_capture_frame_v1_attach_buffer(m_frame, m_buffers[target]->waylandBuffer()); + ext_image_copy_capture_frame_v1_capture(m_frame); + wl_display_flush(m_display); +} + +void ExtCaptureItem::onFrameTransform(void*, ext_image_copy_capture_frame_v1*, uint32_t) {} + +void ExtCaptureItem::onFrameDamage(void*, ext_image_copy_capture_frame_v1*, int32_t, int32_t, int32_t, int32_t) {} + +void ExtCaptureItem::onFramePresentationTime(void*, ext_image_copy_capture_frame_v1*, uint32_t, uint32_t, uint32_t) {} + +void ExtCaptureItem::onFrameReady(void* data, ext_image_copy_capture_frame_v1* frame) { + auto* self = static_cast(data); + int idx = self->m_captureIdx; + + ext_image_copy_capture_frame_v1_destroy(frame); + self->m_frame = nullptr; + + self->m_buffers[idx]->isCapturing = false; + self->m_buffers[idx]->ready = true; + + { + QMutexLocker lk(&self->m_sgMutex); + self->m_pendingIdx = idx; + } + + self->update(); +} + +void ExtCaptureItem::onFrameFailed(void* data, ext_image_copy_capture_frame_v1* frame, uint32_t reason) +{ + auto* self = static_cast(data); + qWarning() << "[ExtCapture] Frame capture failed, reason:" << reason; + ext_image_copy_capture_frame_v1_destroy(frame); + self->m_frame = nullptr; + // Retry after a brief pause (avoid tight loop on persistent error) + QTimer::singleShot(200, self, &ExtCaptureItem::requestNextFrame); +} + +QSGNode* ExtCaptureItem::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) +{ + auto* node = static_cast(oldNode); + int readyIdx = -1; + + { + QMutexLocker lk(&m_sgMutex); + readyIdx = m_pendingIdx; + m_pendingBuffer = nullptr; + } + + if (m_displayIdx >= 0) { + m_buffers[m_displayIdx]->inUse = false; + } + + if (readyIdx != -1) { + m_displayIdx = readyIdx; + m_buffers[m_displayIdx]->ready = false; + m_buffers[m_displayIdx]->inUse = true; + } + + if (!m_frame && m_active) { + QMetaObject::invokeMethod(this, &ExtCaptureItem::requestNextFrame, Qt::QueuedConnection); + } + + if (m_displayIdx == -1) return node; + + GbmBuffer* buf = m_buffers[m_displayIdx].get(); + if (!buf->qtTexture()) { + if (!m_eglCreateImage) { + QPlatformNativeInterface* ni = qGuiApp->platformNativeInterface(); + m_eglDisplay = static_cast(ni->nativeResourceForIntegration(QByteArrayLiteral("egldisplay"))); + if (m_eglDisplay == EGL_NO_DISPLAY) m_eglDisplay = eglGetCurrentDisplay(); + + m_eglCreateImage = reinterpret_cast(eglGetProcAddress("eglCreateImageKHR")); + m_eglDestroyImage = reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR")); + m_glEGLImageTarget = reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + } + + auto fnCreateImage = reinterpret_cast(m_eglCreateImage); + auto fnEGLTarget = reinterpret_cast(m_glEGLImageTarget); + + EGLint attribs[] = { + EGL_WIDTH, buf->width(), + EGL_HEIGHT, buf->height(), + EGL_LINUX_DRM_FOURCC_EXT, static_cast(buf->format()), + EGL_DMA_BUF_PLANE0_FD_EXT, buf->fd(), + EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast(buf->stride()), + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, static_cast(buf->modifier() & 0xFFFFFFFF), + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, static_cast(buf->modifier() >> 32), + EGL_NONE + }; + + buf->eglImage = fnCreateImage(m_eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs); + + if (buf->eglImage == EGL_NO_IMAGE_KHR) { + qWarning() << "[ExtCapture] eglCreateImageKHR failed:" << eglGetError(); + return node; + } + + GLuint texId = 0; + glGenTextures(1, &texId); + glBindTexture(GL_TEXTURE_2D, texId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + fnEGLTarget(GL_TEXTURE_2D, static_cast(buf->eglImage)); + glBindTexture(GL_TEXTURE_2D, 0); + + // On crée la texture Qt qui "enveloppe" notre texture native + // On ne la détruira que lors de la destruction du GbmBuffer + QSGTexture* sgTex = QNativeInterface::QSGOpenGLTexture::fromNative( + texId, + window(), + QSize(buf->width(), buf->height()), + QQuickWindow::TextureHasAlphaChannel); + + buf->setQtTexture(sgTex); + } + + if (!node) node = new QSGSimpleTextureNode; + node->setTexture(buf->qtTexture()); + node->setRect(boundingRect()); + return node; +} + +QVariantList ExtCaptureItem::windows() const +{ + QVariantList list; + for (const auto& t : m_pendingToplevels) { + if (t.identifier.isEmpty()) continue; + QVariantMap map; + map["id"] = t.identifier; + map["appId"] = t.appId; + map["title"] = t.title.isEmpty() ? t.appId : t.title; + list << map; + } + return list; +} \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.hpp b/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.hpp new file mode 100644 index 00000000..0f6228d4 --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/widgets/ExtCaptureItem.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +struct ext_image_copy_capture_manager_v1; +struct ext_image_copy_capture_session_v1; +struct ext_image_copy_capture_frame_v1; +struct ext_image_capture_source_v1; +struct ext_image_copy_capture_source_v1; +struct ext_foreign_toplevel_list_v1; +struct ext_foreign_toplevel_handle_v1; +struct ext_foreign_toplevel_image_capture_source_manager_v1; +struct zwp_linux_dmabuf_v1; +struct zwp_linux_buffer_params_v1; + +class GbmBuffer; +class QSGTexture; + +// --------------------------------------------------------------------------- +// ExtCaptureItem +// +// QML usage: +// import Sleex.ExtCapture +// +// ExtCaptureItem { +// anchors.fill: parent +// appId: "firefox" +// active: true +// } +// --------------------------------------------------------------------------- +class ExtCaptureItem : public QQuickItem +{ + Q_OBJECT + QML_ELEMENT + + // The identifier of the window to capture, typically in the form "appId:instanceId". Updated when the session is active. + Q_PROPERTY(QString windowId READ windowId WRITE setWindowId NOTIFY windowIdChanged) + // App title + Q_PROPERTY(QVariantList windows READ windows NOTIFY windowsChanged) + // Set to true to start capturing. + Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged) + // Read-only: true once the session is live and frames are flowing. + Q_PROPERTY(bool running READ running NOTIFY runningChanged) + +public: + explicit ExtCaptureItem(QQuickItem* parent = nullptr); + ~ExtCaptureItem() override; + + QString windowId() const { return m_windowId; } + void setWindowId(const QString& id); + QVariantList windows() const; + QStringList availableAppIds() const; + bool active() const { return m_active; } + bool running() const { return m_running; } + + void setAppId(const QString& id); + void setActive(bool active); + +protected: + // called by the Scene Graph render thread. + QSGNode* updatePaintNode(QSGNode* old, UpdatePaintNodeData*) override; + void componentComplete() override; + +signals: + void windowIdChanged(); + void windowsChanged(); + void activeChanged(); + void runningChanged(); + +private: + void initWayland(); + void initGbm(); + void tryStartCapture(); + void stopCapture(); + void requestNextFrame(); + void allocateBuffers(); + void createWaylandBuffer(GbmBuffer* buf); + + // Returns the buffer that is not currently in use by Qt. + GbmBuffer* acquireCaptureBuffer(); + + QString m_windowId; + bool m_active = false; + bool m_running = false; + bool m_waylandReady = false; + + // Capture geometry (set by session::buffer_size event) + QSize m_captureSize; + + // Supported DMA-BUF formats reported by the session + struct FormatInfo { uint32_t format; uint64_t modifier; }; + QList m_dmabufFormats; + bool m_formatsReceived = false; + + wl_display* m_display = nullptr; + wl_registry* m_registry = nullptr; + + ext_image_copy_capture_manager_v1* m_captureManager = nullptr; + ext_foreign_toplevel_image_capture_source_manager_v1* m_sourceManager = nullptr; + ext_foreign_toplevel_list_v1* m_toplevelList = nullptr; + zwp_linux_dmabuf_v1* m_dmabuf = nullptr; + + ext_image_capture_source_v1* m_source = nullptr; + ext_image_copy_capture_session_v1* m_session = nullptr; + ext_image_copy_capture_frame_v1* m_frame = nullptr; + + // The toplevel handle we want to capture + ext_foreign_toplevel_handle_v1* m_targetHandle = nullptr; + // Temporary per-toplevel state during discovery + struct ToplevelInfo { + ext_foreign_toplevel_handle_v1* handle = nullptr; + QString appId; + QString title; + QString identifier; + bool matched = false; + }; + QList m_pendingToplevels; + + int m_drmFd = -1; + gbm_device* m_gbmDev = nullptr; + + int m_pendingIdx = -1; + + // Double-buffering: one buffer captured by compositor, one displayed by Qt. + static constexpr int POOL = 2; + std::array, POOL> m_buffers; + int m_captureIdx = 0; // index of the buffer we last sent to the compositor + int m_displayIdx = -1;// index of the buffer ready for Qt to display + + // Qt Scene Graph (render thread side) + // Protected by m_sgMutex. + QMutex m_sgMutex; + GbmBuffer* m_pendingBuffer = nullptr; // written by Wayland CB, read by SG + EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; + // EGL/GL extension function pointers, resolved once (stored as void* to + // avoid pulling gl2ext.h into a MOC-parsed header). + void* m_eglCreateImage = nullptr; // PFNEGLCREATEIMAGEKHRPROC + void* m_eglDestroyImage = nullptr; // PFNEGLDESTROYIMAGEKHRPROC + void* m_glEGLImageTarget = nullptr; // PFNGLEGLIMAGETARGETTEXTURE2DOESPROC + +public: + static void onRegistryGlobal(void*, wl_registry*, uint32_t name, + const char* iface, uint32_t version); + static void onRegistryGlobalRemove(void*, wl_registry*, uint32_t name); + + // ext_foreign_toplevel_list + static void onToplevelListToplevel(void*, ext_foreign_toplevel_list_v1*, + ext_foreign_toplevel_handle_v1* handle); + static void onToplevelListFinished(void*, ext_foreign_toplevel_list_v1*); + + // ext_foreign_toplevel_handle + static void onToplevelAppId(void*, ext_foreign_toplevel_handle_v1*, const char* app_id); + static void onToplevelTitle(void*, ext_foreign_toplevel_handle_v1*, const char* title); + static void onToplevelIdentifier(void*, ext_foreign_toplevel_handle_v1*, const char* id); + static void onToplevelDone(void*, ext_foreign_toplevel_handle_v1*); + static void onToplevelClosed(void*, ext_foreign_toplevel_handle_v1*); + + // ext_image_copy_capture_session + static void onSessionBufferSize(void*, ext_image_copy_capture_session_v1*, + uint32_t w, uint32_t h); + static void onSessionShmFormat(void*, ext_image_copy_capture_session_v1*, uint32_t fmt); + static void onSessionDmabufDevice(void*, ext_image_copy_capture_session_v1*, + wl_array* device); + static void onSessionDmabufFormat(void*, ext_image_copy_capture_session_v1*, + uint32_t fmt, wl_array* modifiers); + static void onSessionDone(void*, ext_image_copy_capture_session_v1*); + static void onSessionStopped(void*, ext_image_copy_capture_session_v1*); + + // ext_image_copy_capture_frame + static void onFrameTransform(void*, ext_image_copy_capture_frame_v1*, uint32_t transform); + static void onFrameDamage(void*, ext_image_copy_capture_frame_v1*, + int32_t x, int32_t y, int32_t w, int32_t h); + static void onFramePresentationTime(void*, ext_image_copy_capture_frame_v1*, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec); + static void onFrameReady(void*, ext_image_copy_capture_frame_v1*); + static void onFrameFailed(void*, ext_image_copy_capture_frame_v1*, uint32_t reason); +}; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.cpp b/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.cpp new file mode 100644 index 00000000..13c8617b --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.cpp @@ -0,0 +1,98 @@ +#include "GbmBuffer.hpp" +#include +#include +#include +#include +#include + +bool GbmBuffer::allocate(gbm_device* dev, + int width, + int height, + uint32_t format, + uint64_t modifier) +{ + release(); + + m_width = width; + m_height = height; + m_format = format; + m_modifier = modifier; + + if (modifier != DRM_FORMAT_MOD_INVALID && modifier != DRM_FORMAT_MOD_LINEAR) { + const uint64_t mods[] = { modifier }; + m_bo = gbm_bo_create_with_modifiers2(dev, width, height, format, mods, 1, + GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + } + if (!m_bo) { + m_bo = gbm_bo_create(dev, width, height, format, + GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR); + } + if (!m_bo) return false; + + m_fd = gbm_bo_get_fd(m_bo); + m_stride = gbm_bo_get_stride(m_bo); + + return (m_fd >= 0); +} + +GbmBuffer::~GbmBuffer() +{ + release(); +} + +GbmBuffer::GbmBuffer(GbmBuffer&& o) noexcept + : m_bo(o.m_bo), m_fd(o.m_fd), m_stride(o.m_stride), + m_format(o.m_format), m_modifier(o.m_modifier), + m_width(o.m_width), m_height(o.m_height), + m_wlBuffer(o.m_wlBuffer), m_qtTexture(o.m_qtTexture), + eglImage(o.eglImage), inUse(o.inUse), ready(o.ready) +{ + o.m_bo = nullptr; + o.m_fd = -1; + o.m_wlBuffer = nullptr; + o.m_qtTexture = nullptr; + o.eglImage = EGL_NO_IMAGE_KHR; +} + +GbmBuffer& GbmBuffer::operator=(GbmBuffer&& o) noexcept +{ + if (this != &o) { + release(); + m_bo = o.m_bo; o.m_bo = nullptr; + m_fd = o.m_fd; o.m_fd = -1; + m_stride = o.m_stride; + m_format = o.m_format; + m_modifier = o.m_modifier; + m_width = o.m_width; + m_height = o.m_height; + m_wlBuffer = o.m_wlBuffer; o.m_wlBuffer = nullptr; + m_qtTexture = o.m_qtTexture; o.m_qtTexture = nullptr; + eglImage = o.eglImage; o.eglImage = EGL_NO_IMAGE_KHR; + inUse = o.inUse; + ready = o.ready; + } + return *this; +} + +void GbmBuffer::release() +{ + if (m_qtTexture) { + delete m_qtTexture; + m_qtTexture = nullptr; + } + + if (m_wlBuffer) { + wl_buffer_destroy(m_wlBuffer); + m_wlBuffer = nullptr; + } + if (m_fd >= 0) { + close(m_fd); + m_fd = -1; + } + if (m_bo) { + gbm_bo_destroy(m_bo); + m_bo = nullptr; + } + inUse = false; + ready = false; +} \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.hpp b/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.hpp new file mode 100644 index 00000000..1cee75fd --- /dev/null +++ b/src/share/sleex/plugins/src/Sleex/widgets/GbmBuffer.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +struct wl_buffer; +class QSGTexture; // Forward declaration pour éviter d'inclure tout Qt ici + +class GbmBuffer { +public: + GbmBuffer() = default; + + bool allocate(gbm_device* dev, + int width, + int height, + uint32_t format, + uint64_t modifier); + + ~GbmBuffer(); + + // Non-copyable, moveable + GbmBuffer(const GbmBuffer&) = delete; + GbmBuffer& operator=(const GbmBuffer&) = delete; + GbmBuffer(GbmBuffer&&) noexcept; + GbmBuffer& operator=(GbmBuffer&&) noexcept; + + bool isValid() const { return m_bo != nullptr; } + int fd() const { return m_fd; } + uint32_t stride() const { return m_stride; } + uint32_t offset() const { return 0; } + uint32_t format() const { return m_format; } + uint64_t modifier() const { return m_modifier; } + int width() const { return m_width; } + int height() const { return m_height; } + + wl_buffer* waylandBuffer() const { return m_wlBuffer; } + void setWaylandBuffer(wl_buffer* b) { m_wlBuffer = b; } + + QSGTexture* qtTexture() const { return m_qtTexture; } + void setQtTexture(QSGTexture* tex) { m_qtTexture = tex; } + EGLImage eglImage = EGL_NO_IMAGE_KHR; + + // Ping-pong flags + bool isCapturing = false; + bool ready = false; + bool inUse = false; +private: + void release(); + + gbm_bo* m_bo = nullptr; + int m_fd = -1; + uint32_t m_stride = 0; + uint32_t m_format = 0; + uint64_t m_modifier = 0; + int m_width = 0; + int m_height = 0; + wl_buffer* m_wlBuffer = nullptr; + QSGTexture* m_qtTexture = nullptr; +}; \ No newline at end of file diff --git a/src/share/sleex/plugins/src/Sleex/widgets/plugin.cpp b/src/share/sleex/plugins/src/Sleex/widgets/plugin.cpp old mode 100644 new mode 100755 index 1114bd49..c67f993d --- a/src/share/sleex/plugins/src/Sleex/widgets/plugin.cpp +++ b/src/share/sleex/plugins/src/Sleex/widgets/plugin.cpp @@ -1,6 +1,6 @@ #include -class SleexUtilsPlugin : public QQmlExtensionPlugin { +class SleexWidgetsPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) diff --git a/src/share/sleex/scripts/colors/switchwall.sh b/src/share/sleex/scripts/colors/switchwall.sh index 0b206cdd..0fa5f7b1 100755 --- a/src/share/sleex/scripts/colors/switchwall.sh +++ b/src/share/sleex/scripts/colors/switchwall.sh @@ -51,8 +51,8 @@ post_process() { check_and_prompt_upscale() { local img="$1" - min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" - min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" + min_width_desired="$(fht-compositor ipc -j outputs | jq '[.[] | .size[0]] | max' | xargs)" + min_height_desired="$(fht-compositor ipc -j outputs | jq '[.[] | .size[1]] | max' | xargs)" if command -v identify &>/dev/null && [ -f "$img" ]; then local img_width img_height @@ -79,15 +79,15 @@ check_and_prompt_upscale() { is_video() { local extension="${1##*.}" - [[ "$extension" == "mp4" || "$extension" == "mkv" || "$extension" == "webm" ]] && return 0 || return 1 [cite: 4, 5] + [[ "$extension" == "mp4" || "$extension" == "mkv" || "$extension" == "webm" ]] && return 0 || return 1 } update_wallpaper_config() { local wallpaper_path="$1" if [[ -f "$DB_CONFIG" ]]; then - sqlite3 "$DB_CONFIG" "UPDATE sleex_settings SET config_json = json_set(config_json, '$.wallpaperPath', '$wallpaper_path') WHERE module='background';" [cite: 11] - qs -p /usr/share/sleex/ ipc call background forceWallpaperReload "$wallpaper_path" [cite: 21, 53] + sqlite3 "$DB_CONFIG" "UPDATE sleex_settings SET config_json = json_set(config_json, '$.wallpaperPath', '$wallpaper_path') WHERE module='background';" + qs -p /usr/share/sleex/ ipc call background forceWallpaperReload "$wallpaper_path" qs -p /usr/share/sleex/settings.qml ipc call settings reloadWallpaper "$wallpaper_path" fi } @@ -100,12 +100,7 @@ switch() { local color="$5" local actual_wallpaper_path="$imgpath" - read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs) - cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960 - cursorposx=$(bc <<< "scale=0; ($cursorposx - $screenx) * $scale / 1") - cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540 - cursorposy=$(bc <<< "scale=0; ($cursorposy - $screeny) * $scale / 1") - cursorposy_inverted=$((screensizey - cursorposy)) + read scale screenx screeny screensizey < <(fht-compositor ipc -j outputs | jq --arg act "$(fht-compositor ipc -j space | jq -r '.monitors[] | select(.active).output')" '.[$act] | .scale, .position[0], .position[1], .size[1]' | xargs) if [[ "$color_flag" == "1" ]]; then matugen_args=(color hex "$color") @@ -163,8 +158,8 @@ switch() { > "$STATE_DIR"/user/generated/material_colors.scss "$SCRIPT_DIR"/applycolor.sh - max_width_desired="$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)" - max_height_desired="$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)" + max_width_desired="$(fht-compositor ipc -j outputs | jq '[.[] | .size[0]] | min' | xargs)" + max_height_desired="$(fht-compositor ipc -j outputs | jq '[.[] | .size[1]] | min' | xargs)" post_process "$max_width_desired" "$max_height_desired" "$actual_wallpaper_path" } diff --git a/src/share/sleex/scripts/fht/get_keybinds.py b/src/share/sleex/scripts/fht/get_keybinds.py new file mode 100755 index 00000000..fff53775 --- /dev/null +++ b/src/share/sleex/scripts/fht/get_keybinds.py @@ -0,0 +1,91 @@ +#!/usr/bin/env -S _/bin/sh _-c "source $(eval echo $SLEEX_VIRTUAL_ENV)/bin/activate && exec python -E "$0" "$@"" +import argparse +import re +import os +import json +import toml +from typing import Dict, List, Any + +TITLE_REGEX = r"^#+!" +HIDE_COMMENT = "[hidden]" + +parser = argparse.ArgumentParser(description='Sleex TOML keybind reader') +parser.add_argument('--path', type=str, default="/etc/sleex/compositor/keybinds.toml", help='path to config file') +args = parser.parse_args() + +class Section(dict): + def __init__(self, name: str): + self["name"] = name + self["children"] = [] + self["keybinds"] = [] + +def parse_toml_config(path: str): + expanded_path = os.path.expanduser(os.path.expandvars(path)) + if not os.access(expanded_path, os.R_OK): + return "error" + + with open(expanded_path, "r") as f: + lines = f.readlines() + + with open(expanded_path, "r") as f: + full_data = toml.load(f) + + keybinds_data = full_data.get("keybinds", {}) + + root = Section("") + stack = [(0, root)] # (level, section_object) + + for line in lines: + clean_line = line.strip() + + if re.match(TITLE_REGEX, clean_line): + level = clean_line.count('#') + name = clean_line.replace('!', '').replace('#', '').strip() + new_section = Section(name) + + while stack and stack[-1][0] >= level: + stack.pop() + + stack[-1][1]["children"].append(new_section) + stack.append((level, new_section)) + continue + + if HIDE_COMMENT in line or not clean_line or clean_line.startswith("["): + continue + + if "=" in clean_line: + key_part = clean_line.split("=")[0].strip() + base_key = key_part.split(".")[0] + + if base_key in keybinds_data: + data = keybinds_data[base_key] + if not any(kb["key"] == base_key for kb in stack[-1][1]["keybinds"]): + + comment = "" + if "#" in line and HIDE_COMMENT not in line: + comment = line.split("#")[-1].strip() + + mods = base_key.split("-") + actual_key = mods.pop() + + if isinstance(data, dict): + dispatcher = data.get("action", "") + params = str(data.get("arg", "")) + else: + dispatcher = data + params = "" + + stack[-1][1]["keybinds"].append({ + "mods": mods, + "key": actual_key, + "dispatcher": dispatcher, + "params": params, + "comment": comment + }) + # keybinds_data.pop(base_key) + + return root + +if __name__ == "__main__": + result = parse_toml_config(args.path) + print(json.dumps(result)) \ No newline at end of file diff --git a/src/share/sleex/scripts/grimblast.sh b/src/share/sleex/scripts/grimblast.sh deleted file mode 100755 index 8c9c54f9..00000000 --- a/src/share/sleex/scripts/grimblast.sh +++ /dev/null @@ -1,277 +0,0 @@ -#!/usr/bin/env bash -## Grimblast: a helper for screenshots within hyprland -## Requirements: -## - `grim`: screenshot utility for wayland -## - `slurp`: to select an area -## - `hyprctl`: to read properties of current window (provided by Hyprland) -## - `hyprpicker`: to freeze the screen when selecting area -## - `wl-copy`: clipboard utility (provided by wl-clipboard) -## - `jq`: json utility to parse hyprctl output -## - `notify-send`: to show notifications (provided by libnotify) -## Those are needed to be installed, if unsure, run `grimblast check` -## -## See `man 1 grimblast` or `grimblast usage` for further details. - -## Author: Misterio (https://github.com/misterio77) - -## This tool is based on grimshot, with swaymsg commands replaced by their -## hyprctl equivalents. -## https://github.com/swaywm/sway/blob/master/contrib/grimshot -getTargetDirectory() { - test -f "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" && - . "${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs" - - echo "${XDG_SCREENSHOTS_DIR:-${XDG_PICTURES_DIR:-$HOME}}" -} - -tmp_editor_directory() { - echo "/tmp" -} - -#Detect if $GRIMBLAST_EDITOR env exist -env_editor_confirm() { - if [ -n "$GRIMBLAST_EDITOR" ]; then - echo "GRIMBLAST_EDITOR is set. Continuing..." - else - echo "GRIMBLAST_EDITOR is not set. Defaulting to gimp" - GRIMBLAST_EDITOR=gimp - fi -} - -NOTIFY=no -CURSOR= -FREEZE= -WAIT=no -SCALE= -HYPRPICKER_PID=-1 - -while [ $# -gt 0 ]; do - key="$1" - - case $key in - -n | --notify) - NOTIFY=yes - shift # past argument - ;; - -c | --cursor) - CURSOR=yes - shift # past argument - ;; - -f | --freeze) - FREEZE=yes - shift # past argument - ;; - -w | --wait) - shift - WAIT=$1 - if echo "$WAIT" | grep "[^0-9]" -q; then - echo "Invalid value for wait '$WAIT'" >&2 - exit 3 - fi - shift - ;; - -s | --scale) - shift # past argument - if [ $# -gt 0 ]; then - SCALE="$1" # assign the next argument to SCALE - shift # past argument - else - echo "Error: Missing argument for --scale option." - exit 1 - fi - ;; - *) # unknown option - break # done with parsing --flags - ;; - esac -done - -ACTION=${1:-usage} -SUBJECT=${2:-screen} -FILE=${3:-$(getTargetDirectory)/$(date -Ins).png} -FILE_EDITOR=${3:-$(tmp_editor_directory)/$(date -Ins).png} - -if [ "$ACTION" != "save" ] && [ "$ACTION" != "copy" ] && [ "$ACTION" != "edit" ] && [ "$ACTION" != "copysave" ] && [ "$ACTION" != "check" ]; then - echo "Usage:" - echo " grimblast [--notify] [--cursor] [--freeze] [--wait N] [--scale ] (copy|save|copysave|edit) [active|screen|output|area] [FILE|-]" - echo " grimblast check" - echo " grimblast usage" - echo "" - echo "Commands:" - echo " copy: Copy the screenshot data into the clipboard." - echo " save: Save the screenshot to a regular file or '-' to pipe to STDOUT." - echo " copysave: Combine the previous 2 options." - echo " edit: Open screenshot in the image editor of your choice (default is gimp). See man page for info." - echo " check: Verify if required tools are installed and exit." - echo " usage: Show this message and exit." - echo "" - echo "Targets:" - echo " active: Currently active window." - echo " screen: All visible outputs." - echo " output: Currently active output." - echo " area: Manually select a region or window." - exit -fi - -notify() { - notify-send -t 3000 -a grimblast "$@" -} - -notifyOk() { - [ "$NOTIFY" = "no" ] && return - - notify "$@" -} - -notifyError() { - if [ $NOTIFY = "yes" ]; then - TITLE=${2:-"Screenshot"} - MESSAGE=${1:-"Error taking screenshot with grim"} - notify -u critical "$TITLE" "$MESSAGE" - else - echo "$1" - fi -} - -resetFade() { - if [[ -n $FADE && -n $FADEOUT ]]; then - hyprctl keyword animation "$FADE" >/dev/null - hyprctl keyword animation "$FADEOUT" >/dev/null - fi -} - -killHyprpicker() { - if [ ! $HYPRPICKER_PID -eq -1 ]; then - kill $HYPRPICKER_PID - fi -} - -die() { - killHyprpicker - MSG=${1:-Bye} - notifyError "Error: $MSG" - exit 2 -} - -check() { - COMMAND=$1 - if command -v "$COMMAND" >/dev/null 2>&1; then - RESULT="OK" - else - RESULT="NOT FOUND" - fi - echo " $COMMAND: $RESULT" -} - -takeScreenshot() { - FILE=$1 - GEOM=$2 - OUTPUT=$3 - if [ -n "$OUTPUT" ]; then - grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -o "$OUTPUT" "$FILE" || die "Unable to invoke grim" - elif [ -z "$GEOM" ]; then - grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} "$FILE" || die "Unable to invoke grim" - else - grim ${CURSOR:+-c} ${SCALE:+-s "$SCALE"} -g "$GEOM" "$FILE" || die "Unable to invoke grim" - resetFade - fi -} - -wait() { - if [ "$WAIT" != "no" ]; then - sleep "$WAIT" - fi -} - -if [ "$ACTION" = "check" ]; then - echo "Checking if required tools are installed. If something is missing, install it to your system and make it available in PATH..." - check grim - check slurp - check hyprctl - check hyprpicker - check wl-copy - check jq - check notify-send - exit -elif [ "$SUBJECT" = "active" ]; then - wait - FOCUSED=$(hyprctl activewindow -j) - GEOM=$(echo "$FOCUSED" | jq -r '"\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"') - APP_ID=$(echo "$FOCUSED" | jq -r '.class') - WHAT="$APP_ID window" -elif [ "$SUBJECT" = "screen" ]; then - wait - GEOM="" - WHAT="Screen" -elif [ "$SUBJECT" = "output" ]; then - wait - GEOM="" - OUTPUT=$(hyprctl monitors -j | jq -r '.[] | select(.focused == true)' | jq -r '.name') - WHAT="$OUTPUT" -elif [ "$SUBJECT" = "area" ]; then - if [ "$FREEZE" = "yes" ] && [ "$(command -v "hyprpicker")" ] >/dev/null 2>&1; then - hyprpicker -r -z & - sleep 0.2 - HYPRPICKER_PID=$! - fi - - # get fade & fadeOut animation and unset it - # this removes the black border seen around screenshots - FADE="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fade") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" - FADEOUT="$(hyprctl -j animations | jq -jr '.[0][] | select(.name == "fadeOut") | .name, ",", (if .enabled == true then "1" else "0" end), ",", (.speed|floor), ",", .bezier')" - hyprctl keyword animation 'fade,0,1,default' >/dev/null - hyprctl keyword animation 'fadeOut,0,1,default' >/dev/null - - WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')" - WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))')" - # shellcheck disable=2086 # if we don't split, spaces mess up slurp - GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp $SLURP_ARGS) - - # Check if user exited slurp without selecting the area - if [ -z "$GEOM" ]; then - killHyprpicker - resetFade - exit 1 - fi - WHAT="Area" - wait -elif [ "$SUBJECT" = "window" ]; then - die "Subject 'window' is now included in 'area'" -else - die "Unknown subject to take a screen shot from" "$SUBJECT" -fi - -if [ "$ACTION" = "copy" ]; then - takeScreenshot - "$GEOM" "$OUTPUT" | wl-copy --type image/png || die "Clipboard error" - notifyOk "$WHAT copied to buffer" -elif [ "$ACTION" = "save" ]; then - if takeScreenshot "$FILE" "$GEOM" "$OUTPUT"; then - TITLE="Screenshot of $SUBJECT" - MESSAGE=$(basename "$FILE") - notifyOk "$TITLE" "$MESSAGE" -i "$FILE" - echo "$FILE" - else - notifyError "Error taking screenshot with grim" - fi -elif [ "$ACTION" = "edit" ]; then - env_editor_confirm - if takeScreenshot "$FILE_EDITOR" "$GEOM" "$OUTPUT"; then - TITLE="Screenshot of $SUBJECT" - MESSAGE="Open screenshot in image editor" - notifyOk "$TITLE" "$MESSAGE" -i "$FILE_EDITOR" - $GRIMBLAST_EDITOR "$FILE_EDITOR" - echo "$FILE_EDITOR" - else - notifyError "Error taking screenshot" - fi -else - if [ "$ACTION" = "copysave" ]; then - takeScreenshot - "$GEOM" "$OUTPUT" | tee "$FILE" | wl-copy --type image/png || die "Clipboard error" - notifyOk "$WHAT copied to buffer and saved to $FILE" -i "$FILE" - echo "$FILE" - else - notifyError "Error taking screenshot with grim" - fi -fi - -killHyprpicker \ No newline at end of file diff --git a/src/share/sleex/scripts/grimshot.sh b/src/share/sleex/scripts/grimshot.sh new file mode 100755 index 00000000..cffdb3c4 --- /dev/null +++ b/src/share/sleex/scripts/grimshot.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +default_extention="png" +directory="$HOME/Images/Screenshots/Grimshot" +# TODO:Make automatical check for Images/... or Pictures/... and automatical mkdir +function copy_only(){ + + name="$(date +"%d-%m-%Y_%H:%M").$default_extention" + output="/tmp/$name" + + grim $output + wl-copy < $output + + notify-send "Grimshot" "Screenshot has been made using:\nCopy only mode" + exit +} + +function normalScreenshot() { + output="$directory/$name" + + sleep 0.7 + grim $output + wl-copy < $output + + notify-send "Grimshot" "Screenshot $name has been made and copied to clipboard" +} + +if [ "$1" == "--copy_only" ]; then + copy_only +fi + +choice=$(printf "$(date +"%d-%m-%Y_%H:%M").$default_extention\nCustom name\nCopy only" | rofi -dmenu -p "Grimshot") + +case "$choice" in + "$(date +"%d-%m-%Y_%H:%M").$default_extention") name="$(date +"%d-%m-%Y_%H:%M").png";; + "Custom name") name=$(rofi -dmenu -p "Screenshot name").$default_extention;; + "Copy only") sleep 0.7 && copy_only ;; + *) exit +esac + +normalScreenshot \ No newline at end of file diff --git a/src/share/sleex/scripts/hyprland/get_keybinds.py b/src/share/sleex/scripts/hyprland/get_keybinds.py deleted file mode 100755 index 054b982d..00000000 --- a/src/share/sleex/scripts/hyprland/get_keybinds.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env -S\_/bin/sh\_-c\_"source\_\$(eval\_echo\_\$SLEEX_VIRTUAL_ENV)/bin/activate&&exec\_python\_-E\_"\$0"\_"\$@"" -import argparse -import re -import os -from os.path import expandvars as os_expandvars -from typing import Dict, List - -TITLE_REGEX = "#+!" -HIDE_COMMENT = "[hidden]" -MOD_SEPARATORS = ['+', ' '] -COMMENT_BIND_PATTERN = "#/#" - -parser = argparse.ArgumentParser(description='Hyprland keybind reader') -parser.add_argument('--path', type=str, default="/etc/sleex/hyprland/keybinds.conf", help='path to keybind file (sourcing isn\'t supported)') -args = parser.parse_args() -content_lines = [] -reading_line = 0 - -# Little Parser made for hyprland keybindings conf file -Variables: Dict[str, str] = {} - - -class KeyBinding(dict): - def __init__(self, mods, key, dispatcher, params, comment) -> None: - self["mods"] = mods - self["key"] = key - self["dispatcher"] = dispatcher - self["params"] = params - self["comment"] = comment - -class Section(dict): - def __init__(self, children, keybinds, name) -> None: - self["children"] = children - self["keybinds"] = keybinds - self["name"] = name - - -def read_content(path: str) -> str: - if (not os.access(os.path.expanduser(os.path.expandvars(path)), os.R_OK)): - return ("error") - with open(os.path.expanduser(os.path.expandvars(path)), "r") as file: - return file.read() - - -def autogenerate_comment(dispatcher: str, params: str = "") -> str: - match dispatcher: - - case "resizewindow": - return "Resize window" - - case "movewindow": - if(params == ""): - return "Move window" - else: - return "Window: move in {} direction".format({ - "l": "left", - "r": "right", - "u": "up", - "d": "down", - }.get(params, "null")) - - case "pin": - return "Window: pin (show on all workspaces)" - - case "splitratio": - return "Window split ratio {}".format(params) - - case "togglefloating": - return "Float/unfloat window" - - case "resizeactive": - return "Resize window by {}".format(params) - - case "killactive": - return "Close window" - - case "fullscreen": - return "Toggle {}".format( - { - "0": "fullscreen", - "1": "maximization", - "2": "fullscreen on Hyprland's side", - }.get(params, "null") - ) - - case "fakefullscreen": - return "Toggle fake fullscreen" - - case "workspace": - if params == "+1": - return "Workspace: focus right" - elif params == "-1": - return "Workspace: focus left" - return "Focus workspace {}".format(params) - - case "movefocus": - return "Window: move focus {}".format( - { - "l": "left", - "r": "right", - "u": "up", - "d": "down", - }.get(params, "null") - ) - - case "swapwindow": - return "Window: swap in {} direction".format( - { - "l": "left", - "r": "right", - "u": "up", - "d": "down", - }.get(params, "null") - ) - - case "movetoworkspace": - if params == "+1": - return "Window: move to right workspace (non-silent)" - elif params == "-1": - return "Window: move to left workspace (non-silent)" - return "Window: move to workspace {} (non-silent)".format(params) - - case "movetoworkspacesilent": - if params == "+1": - return "Window: move to right workspace" - elif params == "-1": - return "Window: move to right workspace" - return "Window: move to workspace {}".format(params) - - case "togglespecialworkspace": - return "Workspace: toggle special" - - case "exec": - return "Execute: {}".format(params) - - case _: - return "" - -def get_keybind_at_line(line_number, line_start = 0): - global content_lines - line = content_lines[line_number] - _, keys = line.split("=", 1) - keys, *comment = keys.split("#", 1) - - mods, key, dispatcher, *params = list(map(str.strip, keys.split(",", 4))) - params = "".join(map(str.strip, params)) - - # Remove empty spaces - comment = list(map(str.strip, comment)) - # Add comment if it exists, else generate it - if comment: - comment = comment[0] - if comment.startswith("[hidden]"): - return None - else: - comment = autogenerate_comment(dispatcher, params) - - if mods: - modstring = mods + MOD_SEPARATORS[0] # Add separator at end to ensure last mod is read - mods = [] - p = 0 - for index, char in enumerate(modstring): - if(char in MOD_SEPARATORS): - if(index - p > 1): - mods.append(modstring[p:index]) - p = index+1 - else: - mods = [] - - return KeyBinding(mods, key, dispatcher, params, comment) - -def get_binds_recursive(current_content, scope): - global content_lines - global reading_line - # print("get_binds_recursive({0}, {1}) [@L{2}]".format(current_content, scope, reading_line + 1)) - while reading_line < len(content_lines): # TODO: Adjust condition - line = content_lines[reading_line] - heading_search_result = re.search(TITLE_REGEX, line) - # print("Read line {0}: {1}\tisHeading: {2}".format(reading_line + 1, content_lines[reading_line], "[{0}, {1}, {2}]".format(heading_search_result.start(), heading_search_result.start() == 0, ((heading_search_result != None) and (heading_search_result.start() == 0))) if heading_search_result != None else "No")) - if ((heading_search_result != None) and (heading_search_result.start() == 0)): # Found title - # Determine scope - heading_scope = line.find('!') - # Lower? Return - if(heading_scope <= scope): - reading_line -= 1 - return current_content - - section_name = line[(heading_scope+1):].strip() - # print("[[ Found h{0} at line {1} ]] {2}".format(heading_scope, reading_line+1, content_lines[reading_line])) - reading_line += 1 - current_content["children"].append(get_binds_recursive(Section([], [], section_name), heading_scope)) - - elif line.startswith(COMMENT_BIND_PATTERN): - keybind = get_keybind_at_line(reading_line, line_start=len(COMMENT_BIND_PATTERN)) - if(keybind != None): - current_content["keybinds"].append(keybind) - - elif line == "" or not line.lstrip().startswith("bind"): # Comment, ignore - pass - - else: # Normal keybind - keybind = get_keybind_at_line(reading_line) - if(keybind != None): - current_content["keybinds"].append(keybind) - - reading_line += 1 - - return current_content; - -def parse_keys(path: str) -> Dict[str, List[KeyBinding]]: - global content_lines - content_lines = read_content(path).splitlines() - if content_lines[0] == "error": - return "error" - return get_binds_recursive(Section([], [], ""), 0) - - -if __name__ == "__main__": - import json - - ParsedKeys = parse_keys(args.path) - print(json.dumps(ParsedKeys)) diff --git a/src/share/sleex/scripts/hyprland/workspace_action.sh b/src/share/sleex/scripts/hyprland/workspace_action.sh deleted file mode 100755 index 2552764b..00000000 --- a/src/share/sleex/scripts/hyprland/workspace_action.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -hyprctl dispatch "$1" $(((($(hyprctl activeworkspace -j | jq -r .id) - 1) / 10) * 10 + $2)) \ No newline at end of file diff --git a/src/share/sleex/scripts/record-script.sh b/src/share/sleex/scripts/record-script.sh index 478c56f7..bb49aeff 100755 --- a/src/share/sleex/scripts/record-script.sh +++ b/src/share/sleex/scripts/record-script.sh @@ -7,7 +7,7 @@ getaudiooutput() { pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2 } getactivemonitor() { - hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name' + fht-compositor ipc -j space | jq -r '.monitors[] | select(.active).output' } xdgvideo="$(xdg-user-dir VIDEOS)" diff --git a/src/share/sleex/services/Audio.qml b/src/share/sleex/services/Audio.qml index eb69dfbc..de1dd0ec 100644 --- a/src/share/sleex/services/Audio.qml +++ b/src/share/sleex/services/Audio.qml @@ -2,7 +2,6 @@ import qs.modules.common import QtQuick import Quickshell import Quickshell.Services.Pipewire -import Quickshell.Hyprland pragma Singleton pragma ComponentBehavior: Bound @@ -49,10 +48,8 @@ Singleton { } } - // Reverted to Hyprland.dispatch, which is the idiomatic method for this shell. function playSound(relativeSoundPath) { const fullPath = "/usr/share/sleex/" + relativeSoundPath; - // Use the absolute path to paplay to be safe. const command = ["/usr/bin/paplay", `${fullPath}`]; Quickshell.execDetached(command); } diff --git a/src/share/sleex/services/Brightness.qml b/src/share/sleex/services/Brightness.qml index 552b2f88..8a61a125 100644 --- a/src/share/sleex/services/Brightness.qml +++ b/src/share/sleex/services/Brightness.qml @@ -8,6 +8,7 @@ import Quickshell import Quickshell.Io import Quickshell.Hyprland import QtQuick +import Sleex.Fhtc /** * For managing brightness of monitors. Supports both brightnessctl and ddcutil. @@ -27,21 +28,21 @@ Singleton { } function increaseBrightness(): void { - const focusedName = Hyprland.focusedMonitor.name; + const focusedName = FhtcMonitors.activeMonitorName; const monitor = monitors.find(m => focusedName === m.screen.name); if (monitor) monitor.setBrightness(monitor.brightness + 0.05); } function decreaseBrightness(): void { - const focusedName = Hyprland.focusedMonitor.name; + const focusedName = FhtcMonitors.activeMonitorName; const monitor = monitors.find(m => focusedName === m.screen.name); if (monitor) monitor.setBrightness(monitor.brightness - 0.05); } function setMonitorBrightness(b): void { - const focusedName = Hyprland.focusedMonitor.name; + const focusedName = FhtcMonitors.activeMonitorName; const monitor = monitors.find(m => focusedName === m.screen.name); if (monitor) monitor.setBrightness(b); diff --git a/src/share/sleex/services/ConfigLoader.qml b/src/share/sleex/services/ConfigLoader.qml index 498dcbf0..84df1f8e 100644 --- a/src/share/sleex/services/ConfigLoader.qml +++ b/src/share/sleex/services/ConfigLoader.qml @@ -7,7 +7,6 @@ import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Qt.labs.platform /** @@ -104,7 +103,6 @@ Singleton { } else { root.applyConfig(configFileView.text()) if (!root.preventNextNotification) { - // Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) } else { root.preventNextNotification = false; } diff --git a/src/share/sleex/services/HyprlandKeybinds.qml b/src/share/sleex/services/FhtcKeybinds.qml similarity index 77% rename from src/share/sleex/services/HyprlandKeybinds.qml rename to src/share/sleex/services/FhtcKeybinds.qml index 2dace266..392b056b 100644 --- a/src/share/sleex/services/HyprlandKeybinds.qml +++ b/src/share/sleex/services/FhtcKeybinds.qml @@ -7,17 +7,16 @@ import QtQuick import Quickshell import Quickshell.Io import Quickshell.Wayland -import Quickshell.Hyprland /** - * A service that provides access to Hyprland keybinds. + * A service that provides access to keybinds. * Uses the `get_keybinds.py` script to parse comments in config files in a certain format and convert to JSON. */ Singleton { id: root - property string keybindParserPath: FileUtils.trimFileProtocol(`/usr/share/sleex/scripts/hyprland/get_keybinds.py`) - property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`/etc/sleex/hyprland/keybinds.conf`) - property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/custom/keybinds.conf`) + property string keybindParserPath: FileUtils.trimFileProtocol(`/usr/share/sleex/scripts/fht/get_keybinds.py`) + property string defaultKeybindConfigPath: FileUtils.trimFileProtocol(`/etc/sleex/compositor/keybinds.toml`) + property string userKeybindConfigPath: FileUtils.trimFileProtocol(`${Directories.config}/fht/custom/keybinds.toml`) property var defaultKeybinds: {"children": []} property var userKeybinds: {"children": []} property var keybinds: ({ @@ -27,17 +26,6 @@ Singleton { ] }) - Connections { - target: Hyprland - - function onRawEvent(event) { - if (event.name == "configreloaded") { - getDefaultKeybinds.running = true - getUserKeybinds.running = true - } - } - } - Process { id: getDefaultKeybinds running: true diff --git a/src/share/sleex/services/FirstRunExperience.qml b/src/share/sleex/services/FirstRunExperience.qml index dff06a0c..5b4c18ee 100644 --- a/src/share/sleex/services/FirstRunExperience.qml +++ b/src/share/sleex/services/FirstRunExperience.qml @@ -4,7 +4,6 @@ import qs.modules.common.functions import qs.modules.common import Quickshell import Quickshell.Io -import Quickshell.Hyprland Singleton { id: root diff --git a/src/share/sleex/services/HyprlandData.qml b/src/share/sleex/services/HyprlandData.qml deleted file mode 100644 index a9868a45..00000000 --- a/src/share/sleex/services/HyprlandData.qml +++ /dev/null @@ -1,85 +0,0 @@ -pragma Singleton -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Hyprland - -/** - * Provides access to some Hyprland data not available in Quickshell.Hyprland. - */ -Singleton { - id: root - property var windowList: [] - property var addresses: [] - property var windowByAddress: ({}) - property var monitors: [] - property var layers: ({}) - - function updateWindowList() { - getClients.running = true - getMonitors.running = true - } - - function updateLayers() { - getLayers.running = true - } - - Component.onCompleted: { - updateWindowList() - updateLayers() - } - - Connections { - target: Hyprland - - function onRawEvent(event) { - // Filter out redundant old v1 events for the same thing - if(event.name in [ - "activewindow", "focusedmon", "monitoradded", - "createworkspace", "destroyworkspace", "moveworkspace", - "activespecial", "movewindow", "windowtitle" - ]) return ; - updateWindowList() - } - } - - Process { - id: getClients - command: ["bash", "-c", "hyprctl clients -j | jq -c"] - stdout: SplitParser { - onRead: (data) => { - root.windowList = JSON.parse(data) - let tempWinByAddress = {} - for (var i = 0; i < root.windowList.length; ++i) { - var win = root.windowList[i] - tempWinByAddress[win.address] = win - } - root.windowByAddress = tempWinByAddress - root.addresses = root.windowList.map((win) => win.address) - } - } - } - Process { - id: getMonitors - command: ["bash", "-c", "hyprctl monitors -j | jq -c"] - stdout: SplitParser { - onRead: (data) => { - root.monitors = JSON.parse(data) - } - } - } - - Process { - id: getLayers - command: ["bash", "-c", "hyprctl layers -j | jq -c"] - stdout: SplitParser { - onRead: (data) => { - root.layers = JSON.parse(data) - } - } - } -} - diff --git a/src/share/sleex/services/LatexRenderer.qml b/src/share/sleex/services/LatexRenderer.qml index ecfbf3bb..12c759d6 100644 --- a/src/share/sleex/services/LatexRenderer.qml +++ b/src/share/sleex/services/LatexRenderer.qml @@ -7,7 +7,6 @@ import qs.modules.common import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Qt.labs.platform /** diff --git a/src/share/sleex/services/Monitors.qml b/src/share/sleex/services/Monitors.qml deleted file mode 100644 index ae24fcd2..00000000 --- a/src/share/sleex/services/Monitors.qml +++ /dev/null @@ -1,170 +0,0 @@ -pragma Singleton - -import Quickshell -import Quickshell.Io -import QtQuick - - -Singleton { - id: root - - readonly property list monitors: [] - readonly property Monitor primary: monitors.find(m => m.primary) ?? null - property int snapThreshold: 20 - - reloadableId: "monitors" - - function setMonitorPosition(name: string, x: int, y: int): void { - const monitor = monitors.find(m => m.name === name); - if (!monitor) return; - - setPositionProc.exec([ - "hyprctl", "keyword", "monitor", - `${name},${monitor.width}x${monitor.height},${x}x${y},${monitor.scale}` - ]); - } - - function snapMonitors(primaryName: string, secondaryName: string, position: string): void { - const primary = monitors.find(m => m.name === primaryName); - const secondary = monitors.find(m => m.name === secondaryName); - if (!primary || !secondary) return; - - let x, y; - switch (position) { - case "right": - x = primary.x + primary.width; - y = primary.y; - break; - case "left": - x = primary.x - secondary.width; - y = primary.y; - break; - case "top": - x = primary.x; - y = primary.y - secondary.height; - break; - case "bottom": - x = primary.x; - y = primary.y + primary.height; - break; - default: - return; - } - - setMonitorPosition(secondaryName, x, y); - } - - function toggleMonitor(name: string): void { - const monitor = monitors.find(m => m.name === name); - if (!monitor) return; - - const config = monitor.enabled ? - `${name},disabled` : - `${name},${monitor.width}x${monitor.height},${monitor.x}x${monitor.y},${monitor.scale}`; - - toggleProc.exec(["hyprctl", "keyword", "monitor", config]); - } - - function refreshMonitors(): void { - getMonitors.running = true; - } - - // Get monitor information - Process { - id: getMonitors - - running: true - command: ["hyprctl", "monitors", "-j"] - environment: ({ - LANG: "C", - LC_ALL: "C" - }) - - stdout: StdioCollector { - onStreamFinished: { - let monitorData; - try { - monitorData = JSON.parse(text); - } catch (e) { - console.error("Failed to parse monitor data:", e); - return; - } - - const rMonitors = root.monitors; - - // Remove destroyed monitors - const destroyed = rMonitors.filter(rm => - !monitorData.find(m => m.name === rm.name) - ); - for (const monitor of destroyed) { - rMonitors.splice(rMonitors.indexOf(monitor), 1).forEach(m => m.destroy()); - } - - // Update or create monitors - for (const monitorInfo of monitorData) { - const match = rMonitors.find(m => m.name === monitorInfo.name); - if (match) { - match.lastIpcObject = monitorInfo; - } else { - rMonitors.push(monitorComp.createObject(root, { - lastIpcObject: monitorInfo - })); - } - } - } - } - } - - // Set monitor position - Process { - id: setPositionProc - - onExited: { - getMonitors.running = true; - } - } - - // Toggle monitor - Process { - id: toggleProc - - onExited: { - getMonitors.running = true; - } - } - - // Listen for Hyprland events - Process { - running: true - command: ["socat", "-", "UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"] - - stdout: SplitParser { - onRead: { - if (data.startsWith("monitoradded") || - data.startsWith("monitorremoved") || - data.startsWith("configreloaded")) { - getMonitors.running = true; - } - } - } - } - - component Monitor: QtObject { - required property var lastIpcObject - - readonly property string name: lastIpcObject.name ?? "" - readonly property int width: lastIpcObject.width ?? 0 - readonly property int height: lastIpcObject.height ?? 0 - readonly property int x: lastIpcObject.x ?? 0 - readonly property int y: lastIpcObject.y ?? 0 - readonly property real scale: lastIpcObject.scale ?? 1.0 - readonly property bool primary: lastIpcObject.focused ?? false - readonly property bool enabled: width > 0 && height > 0 - readonly property string resolution: `${width}x${height}` - } - - Component { - id: monitorComp - Monitor {} - } -} \ No newline at end of file diff --git a/src/share/sleex/services/NightLight.qml b/src/share/sleex/services/NightLight.qml index 5dfa45a2..580adfe3 100644 --- a/src/share/sleex/services/NightLight.qml +++ b/src/share/sleex/services/NightLight.qml @@ -4,11 +4,6 @@ import qs.modules.common import Quickshell import Quickshell.Io -/** - * Simple hyprsunset service with automatic mode. - * In theory we don't need this because hyprsunset has a config file, but it somehow doesn't work. - * It should also be possible to control it via hyprctl, but it doesn't work consistently either so we're just killing and launching. - */ Singleton { id: root property bool ready: false @@ -55,7 +50,7 @@ Singleton { onColorTemperatureChanged: { if (root.active) - Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", String(colorTemperature)]); + Quickshell.execDetached(["gammastep", "-O", String(colorTemperature)]); } function reEvaluate() { @@ -76,12 +71,12 @@ Singleton { function enable() { root.active = true; Quickshell.execDetached(["bash", "-c", - `pidof hyprsunset || hyprsunset --temperature ${root.colorTemperature}`]); + `pidof gammastep || gammastep -O ${root.colorTemperature}`]); } function disable() { root.active = false; - Quickshell.execDetached(["bash", "-c", "pkill hyprsunset"]); + Quickshell.execDetached(["bash", "-c", "pkill gammastep"]); } function fetchState() { @@ -91,7 +86,7 @@ Singleton { Process { id: fetchProc running: true - command: ["bash", "-c", "hyprctl hyprsunset temperature"] + command: ["bash", "-c", "hyprctl hyprsunset temperature"] // TODO: use gammastep stdout: StdioCollector { id: stateCollector onStreamFinished: { diff --git a/src/share/sleex/services/PersistentStateManager.qml b/src/share/sleex/services/PersistentStateManager.qml index 5a5addbe..bb126267 100644 --- a/src/share/sleex/services/PersistentStateManager.qml +++ b/src/share/sleex/services/PersistentStateManager.qml @@ -6,7 +6,6 @@ import qs.modules.common.functions import QtQuick import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Qt.labs.platform /** diff --git a/src/share/sleex/services/Ydotool.qml b/src/share/sleex/services/Ydotool.qml index ca9248cd..b7c36453 100644 --- a/src/share/sleex/services/Ydotool.qml +++ b/src/share/sleex/services/Ydotool.qml @@ -3,7 +3,6 @@ pragma Singleton import qs.modules.common import Quickshell import Quickshell.Io -import Quickshell.Hyprland Singleton { id: root diff --git a/src/share/sleex/settings.qml b/src/share/sleex/settings.qml index 6098bcc0..af766326 100644 --- a/src/share/sleex/settings.qml +++ b/src/share/sleex/settings.qml @@ -10,7 +10,6 @@ import QtQuick.Layouts import QtQuick.Window import Quickshell import Quickshell.Io -import Quickshell.Hyprland import Quickshell.Widgets import qs import qs.services diff --git a/src/share/sleex/shell.qml b/src/share/sleex/shell.qml index 24290ae9..04ae8454 100644 --- a/src/share/sleex/shell.qml +++ b/src/share/sleex/shell.qml @@ -18,7 +18,6 @@ import qs.modules.polkit import qs.modules.screenCorners import qs.modules.session import qs.modules.dashboard -import qs.modules.sidebarLeft import qs.modules.wallpaperSelector import qs.modules.background import qs.modules.lockscreen @@ -46,7 +45,6 @@ ShellRoot { property bool enableReloadPopup: true property bool enableScreenCorners: false property bool enableSession: true - property bool enableSidebarLeft: false property bool enableDashboard: true property bool enableWallSelector: true property bool enableBackground: true @@ -73,7 +71,6 @@ ShellRoot { LazyLoader { active: true; component: BatteryPopup {} } LazyLoader { active: enableScreenCorners; component: ScreenCorners {} } LazyLoader { active: enableSession; component: Session {} } - LazyLoader { active: enableSidebarLeft; component: SidebarLeft {} } LazyLoader { active: enableDashboard; component: Dashboard {} } LazyLoader { active: enableWallSelector; component: WallpaperSelector {} } LazyLoader { active: enableBackground; component: Background {} }