Compare commits
2 Commits
main
...
noctilia-e
| Author | SHA1 | Date | |
|---|---|---|---|
| 80d7fc08cc | |||
| 8ccb9205bf |
21
flake.lock
generated
21
flake.lock
generated
@@ -487,6 +487,26 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"noctalia": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769983098,
|
||||||
|
"narHash": "sha256-PKej3N1BxAoKzusrdWuS9gT8bXW0U/Zk8RkedsP3qYc=",
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"rev": "2a98d04b2f5e251935ba296c0d7dc374bdc5e32d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nur": {
|
"nur": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": [
|
"flake-parts": [
|
||||||
@@ -569,6 +589,7 @@
|
|||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nix-flatpak": "nix-flatpak",
|
"nix-flatpak": "nix-flatpak",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
|
"noctalia": "noctalia",
|
||||||
"prismlauncher-cracked": "prismlauncher-cracked",
|
"prismlauncher-cracked": "prismlauncher-cracked",
|
||||||
"stylix": "stylix",
|
"stylix": "stylix",
|
||||||
"wrappers": "wrappers"
|
"wrappers": "wrappers"
|
||||||
|
|||||||
@@ -59,6 +59,11 @@
|
|||||||
url = "github:lassulus/wrappers";
|
url = "github:lassulus/wrappers";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
noctalia = {
|
||||||
|
url = "github:noctalia-dev/noctalia-shell";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
./nfs.nix
|
./nfs.nix
|
||||||
./nh.nix
|
./nh.nix
|
||||||
./printing.nix
|
./printing.nix
|
||||||
|
./quickshell.nix
|
||||||
./greetd.nix
|
./greetd.nix
|
||||||
./security.nix
|
./security.nix
|
||||||
./services.nix
|
./services.nix
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
{pkgs, ...}: {
|
{
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
mpv
|
mpv
|
||||||
pavucontrol
|
pavucontrol
|
||||||
@@ -53,5 +58,10 @@
|
|||||||
hunspell
|
hunspell
|
||||||
hunspellDicts.pl_PL
|
hunspellDicts.pl_PL
|
||||||
hunspellDicts.en_US
|
hunspellDicts.en_US
|
||||||
|
# Noctalia Shell Dependencies
|
||||||
|
matugen
|
||||||
|
app2unit
|
||||||
|
gpu-screen-recorder
|
||||||
|
power-profiles-daemon
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
30
modules/core/quickshell.nix
Normal file
30
modules/core/quickshell.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{pkgs, ...}: {
|
||||||
|
environment = {
|
||||||
|
systemPackages = with pkgs; [
|
||||||
|
quickshell
|
||||||
|
|
||||||
|
# Qt6 related kits(for slove Qt5Compat problem)
|
||||||
|
qt6.qt5compat
|
||||||
|
qt6.qtbase
|
||||||
|
qt6.qtquick3d
|
||||||
|
qt6.qtwayland
|
||||||
|
qt6.qtdeclarative
|
||||||
|
qt6.qtsvg
|
||||||
|
|
||||||
|
# alternate options
|
||||||
|
# libsForQt5.qt5compat
|
||||||
|
kdePackages.qt5compat
|
||||||
|
libsForQt5.qt5.qtgraphicaleffects
|
||||||
|
];
|
||||||
|
# necessary environment variables
|
||||||
|
variables = {
|
||||||
|
QML_IMPORT_PATH = "${pkgs.qt6.qt5compat}/lib/qt-6/qml:${pkgs.qt6.qtbase}/lib/qt-6/qml";
|
||||||
|
QML2_IMPORT_PATH = "${pkgs.qt6.qt5compat}/lib/qt-6/qml:${pkgs.qt6.qtbase}/lib/qt-6/qml";
|
||||||
|
};
|
||||||
|
# make sure the Qt application is working properly
|
||||||
|
sessionVariables = {
|
||||||
|
QT_QPA_PLATFORM = "wayland;xcb";
|
||||||
|
QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -53,5 +53,6 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
upower.enable = true; # noctalia shell battery
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ in {
|
|||||||
./kdeConnect.nix
|
./kdeConnect.nix
|
||||||
./lutris.nix
|
./lutris.nix
|
||||||
./nextcloud.nix
|
./nextcloud.nix
|
||||||
|
./noctalia.nix
|
||||||
./obs-studio.nix
|
./obs-studio.nix
|
||||||
./onlyoffice.nix
|
./onlyoffice.nix
|
||||||
|
./overview.nix
|
||||||
./qt.nix
|
./qt.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./starship.nix
|
./starship.nix
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ in {
|
|||||||
"SUPER SHIFT, M, exec, dex ${desktopEntriesPath}/messenger.desktop"
|
"SUPER SHIFT, M, exec, dex ${desktopEntriesPath}/messenger.desktop"
|
||||||
"SUPER SHIFT, N, exec, nextcloud"
|
"SUPER SHIFT, N, exec, nextcloud"
|
||||||
"SUPER SHIFT, O, exec, onlyoffice-desktopeditors"
|
"SUPER SHIFT, O, exec, onlyoffice-desktopeditors"
|
||||||
"SUPER SHIFT, Return, exec, rofi-launcher"
|
# "SUPER SHIFT, Return, exec, rofi-launcher"
|
||||||
"SUPER SHIFT, T, exec, tutanota-desktop"
|
"SUPER SHIFT, T, exec, tutanota-desktop"
|
||||||
"SUPER SHIFT, W, exec, web-search"
|
# "SUPER SHIFT, W, exec, web-search"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# APLIKACJE - Z ALT
|
# APLIKACJE - Z ALT
|
||||||
@@ -149,6 +149,20 @@ in {
|
|||||||
",XF86MonBrightnessDown, exec, brightnessctl set 5%-"
|
",XF86MonBrightnessDown, exec, brightnessctl set 5%-"
|
||||||
",XF86MonBrightnessUp, exec, brightnessctl set +5%"
|
",XF86MonBrightnessUp, exec, brightnessctl set +5%"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# NOCTALIA SHELL
|
||||||
|
# =============================================================================
|
||||||
|
"SUPER SHIFT, Return, exec, noctalia-shell ipc call launcher toggle"
|
||||||
|
# "SUPER, M, Noctalia Notifications, exec, noctalia-shell ipc call notifications toggleHistory"
|
||||||
|
"SUPER SHIFT, V, exec, noctalia-shell ipc call launcher clipboard"
|
||||||
|
"SUPER ALT, P, exec, noctalia-shell ipc call settings toggle"
|
||||||
|
"SUPER ALT, L, exec, noctalia-shell ipc call sessionMenu lockAndSuspend"
|
||||||
|
"SUPER SHIFT, W, exec, noctalia-shell ipc call wallpaper toggle"
|
||||||
|
"SUPER, X, exec, noctalia-shell ipc call sessionMenu toggle"
|
||||||
|
"SUPER ALT, C, exec, noctalia-shell ipc call controlCenter toggle"
|
||||||
|
"SUPER CTRL, R, exec, noctalia-shell ipc call screenRecorder toggle"
|
||||||
|
"SUPER SHIFT, R, exec, restart.noctalia"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# NIEUŻYWANE KEYBINDY
|
# NIEUŻYWANE KEYBINDY
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@@ -10,13 +10,19 @@ in {
|
|||||||
"dbus-update-activation-environment --all --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"
|
"dbus-update-activation-environment --all --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"
|
||||||
"systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"
|
"systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"
|
||||||
"systemctl --user start hyprpolkitagent"
|
"systemctl --user start hyprpolkitagent"
|
||||||
|
"qs -c overview" # Start quickshell-overview daemon
|
||||||
|
|
||||||
"killall -q swww;sleep .5 && swww-daemon"
|
# "killall -q swww;sleep .5 && swww-daemon"
|
||||||
"killall -q waybar;sleep .5 && waybar"
|
# "killall -q waybar;sleep .5 && waybar"
|
||||||
"killall -q swaync;sleep .5 && swaync"
|
# "killall -q swaync;sleep .5 && swaync"
|
||||||
"#wallsetter &"
|
# "#wallsetter &"
|
||||||
"pypr &"
|
# "pypr &"
|
||||||
"nm-applet --indicator"
|
# "nm-applet --indicator"
|
||||||
"sleep 1.0 && swww img ${stylixImage}"
|
# "sleep 1.0 && swww img ${stylixImage}"
|
||||||
|
"killall -q waybar"
|
||||||
|
"pkill waybar"
|
||||||
|
"killall -q swaync"
|
||||||
|
"pkill swaync"
|
||||||
|
"noctalia-shell &"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
28
modules/home/noctalia.nix
Normal file
28
modules/home/noctalia.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
noctalia = inputs.noctalia.packages.${system}.default;
|
||||||
|
configDir = "${noctalia}/share/noctalia-shell";
|
||||||
|
in {
|
||||||
|
home = {
|
||||||
|
packages = with pkgs; [
|
||||||
|
noctalia
|
||||||
|
quickshell # Ensure quickshell is available for the service
|
||||||
|
];
|
||||||
|
activation.seedNoctaliaShellCode = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
set -eu
|
||||||
|
DEST="$HOME/.config/quickshell/noctalia-shell"
|
||||||
|
SRC="${configDir}"
|
||||||
|
|
||||||
|
if [ ! -d "$DEST" ]; then
|
||||||
|
$DRY_RUN_CMD mkdir -p "$HOME/.config/quickshell"
|
||||||
|
$DRY_RUN_CMD cp -R "$SRC" "$DEST"
|
||||||
|
$DRY_RUN_CMD chmod -R u+rwX "$DEST"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
22
modules/home/overview.nix
Normal file
22
modules/home/overview.nix
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{lib, ...}: let
|
||||||
|
overviewSource = ./overview;
|
||||||
|
in {
|
||||||
|
# Quickshell-overview is a Qt6 QML app for Hyprland workspace overview
|
||||||
|
# It shows all workspaces with live window previews, drag-and-drop support
|
||||||
|
# Toggled via: SUPER + TAB (bound in hyprland/binds.nix)
|
||||||
|
# Started via exec-once in hyprland/exec-once.nix
|
||||||
|
|
||||||
|
# Seed the Quickshell overview code into ~/.config/quickshell/overview
|
||||||
|
# Copy (not symlink) so QML module resolution works and users can edit files
|
||||||
|
home.activation.seedOverviewCode = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
set -eu
|
||||||
|
DEST="$HOME/.config/quickshell/overview"
|
||||||
|
SRC="${overviewSource}"
|
||||||
|
|
||||||
|
if [ ! -d "$DEST" ]; then
|
||||||
|
mkdir -p "$HOME/.config/quickshell"
|
||||||
|
cp -R "$SRC" "$DEST"
|
||||||
|
chmod -R u+rwX "$DEST"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
}
|
||||||
214
modules/home/overview/README.md
Normal file
214
modules/home/overview/README.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Quickshell Overview for Hyprland
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
A standalone workspace overview module for Hyprland using Quickshell - shows all workspaces with live window previews, drag-and-drop support, and Super+Tab keybind.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📸 Preview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/79ceb141-6b9e-4956-8e09-aaf72b66550c
|
||||||
|
|
||||||
|
> *Workspace overview showing live window previews with drag-and-drop support*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖼️ Visual workspace overview showing all workspaces and windows
|
||||||
|
- 🎯 Click windows to focus them
|
||||||
|
- 🖱️ Middle-click windows to close them
|
||||||
|
- 🔄 Drag and drop windows between workspaces
|
||||||
|
- ⌨️ Keyboard navigation (Arrow keys to switch workspaces, Escape/Enter to close)
|
||||||
|
- 💡 Hover tooltips showing window information
|
||||||
|
- 🎨 Material Design 3 theming
|
||||||
|
- ⚡ Smooth animations and transitions
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Hyprland** compositor
|
||||||
|
- **Quickshell** ([installation guide](https://quickshell.org/docs/v0.1.0/guide/install-setup/))
|
||||||
|
- **Qt 6** with modules: QtQuick, QtQuick.Controls, Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Clone this repository** to your Quickshell config directory:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Shanu-Kumawat/quickshell-overview ~/.config/quickshell/overview
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add keybind** to your Hyprland config (`~/.config/hypr/hyprland.conf`):
|
||||||
|
```conf
|
||||||
|
bind = Super, TAB, exec, qs ipc -c overview call overview toggle
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Auto-start** the overview (add to Hyprland config):
|
||||||
|
```conf
|
||||||
|
exec-once = qs -c overview
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Reload Hyprland**:
|
||||||
|
```bash
|
||||||
|
hyprctl reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Start (if needed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qs -c overview &
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎮 Usage
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Super + Tab** | Toggle the overview |
|
||||||
|
| **Left/Right Arrow Keys** | Navigate between workspaces horizontally |
|
||||||
|
| **Up/Down Arrow Keys** | Navigate between workspace rows |
|
||||||
|
| **Escape / Enter** | Close the overview |
|
||||||
|
| **Click workspace** | Switch to that workspace |
|
||||||
|
| **Click window** | Focus that window |
|
||||||
|
| **Middle-click window** | Close that window |
|
||||||
|
| **Drag window** | Move window to different workspace |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
> **⚠️ Want to change the size, position, or number of workspaces?**
|
||||||
|
> Edit `~/.config/quickshell/overview/common/Config.qml` - it's all there!
|
||||||
|
|
||||||
|
### Workspace Grid
|
||||||
|
|
||||||
|
Edit `~/.config/quickshell/overview/common/Config.qml`:
|
||||||
|
|
||||||
|
```qml
|
||||||
|
property QtObject overview: QtObject {
|
||||||
|
property int rows: 2 // Number of workspace rows
|
||||||
|
property int columns: 5 // Number of workspace columns (10 total workspaces)
|
||||||
|
property real scale: 0.16 // Overview scale factor (0.1-0.3, smaller = more compact)
|
||||||
|
property bool enable: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common adjustments:**
|
||||||
|
- **Too small?** Increase `scale` (try 0.20 or 0.25)
|
||||||
|
- **Too big?** Decrease `scale` (try 0.12 or 0.14)
|
||||||
|
- **More workspaces?** Change `rows` and `columns` (e.g., 3 rows × 4 columns = 12 workspaces)
|
||||||
|
|
||||||
|
### Position
|
||||||
|
|
||||||
|
Edit `~/.config/quickshell/overview/modules/overview/Overview.qml` (line ~111):
|
||||||
|
|
||||||
|
```qml
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 100 // Change this value to move up/down
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme & Colors
|
||||||
|
|
||||||
|
Edit `~/.config/quickshell/overview/common/Appearance.qml` to customize:
|
||||||
|
- Colors (m3colors and colors objects)
|
||||||
|
- Font families and sizes
|
||||||
|
- Animation curves and durations
|
||||||
|
- Border radius values
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- **Hyprland** compositor (tested on latest versions)
|
||||||
|
- **Quickshell** (Qt6-based shell framework)
|
||||||
|
- **Qt 6** with the following modules:
|
||||||
|
- QtQuick
|
||||||
|
- QtQuick.Controls
|
||||||
|
- QtQuick.Layouts
|
||||||
|
- Qt5Compat.GraphicalEffects
|
||||||
|
- Quickshell.Wayland
|
||||||
|
- Quickshell.Hyprland
|
||||||
|
|
||||||
|
## 🚫 Removed Features (from original illogical-impulse)
|
||||||
|
|
||||||
|
The following features were removed to make it standalone:
|
||||||
|
|
||||||
|
- App search functionality
|
||||||
|
- Emoji picker
|
||||||
|
- Clipboard history integration
|
||||||
|
- Search widget
|
||||||
|
- Integration with the full illogical-impulse shell ecosystem
|
||||||
|
|
||||||
|
## 📁 File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.config/quickshell/overview/
|
||||||
|
├── shell.qml # Main entry point
|
||||||
|
├── README.md # This file
|
||||||
|
├── hyprland-config.conf # Configuration reference
|
||||||
|
├── common/
|
||||||
|
│ ├── Appearance.qml # Theme and styling
|
||||||
|
│ ├── Config.qml # Configuration options
|
||||||
|
│ ├── functions/
|
||||||
|
│ │ └── ColorUtils.qml # Color manipulation utilities
|
||||||
|
│ └── widgets/
|
||||||
|
│ ├── StyledText.qml # Styled text component
|
||||||
|
│ ├── StyledRectangularShadow.qml
|
||||||
|
│ ├── StyledToolTip.qml
|
||||||
|
│ └── StyledToolTipContent.qml
|
||||||
|
├── services/
|
||||||
|
│ ├── GlobalStates.qml # Global state management
|
||||||
|
│ └── HyprlandData.qml # Hyprland data provider
|
||||||
|
└── modules/
|
||||||
|
└── overview/
|
||||||
|
├── Overview.qml # Main overview component
|
||||||
|
├── OverviewWidget.qml # Workspace grid widget
|
||||||
|
└── OverviewWindow.qml # Individual window preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 IPC Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Toggle overview
|
||||||
|
qs ipc -c overview call overview toggle
|
||||||
|
|
||||||
|
# Open overview
|
||||||
|
qs ipc -c overview call overview open
|
||||||
|
|
||||||
|
# Close overview
|
||||||
|
qs ipc -c overview call overview close
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Known Issues
|
||||||
|
|
||||||
|
- Window icons may fallback to generic icon if app class name doesn't match icon theme
|
||||||
|
- Potential crashes during rapid window state changes due to Wayland screencopy buffer management
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
Extracted from the overview feature in [illogical-impulse](https://github.com/end-4/dots-hyprland) by [end-4](https://github.com/end-4).
|
||||||
|
|
||||||
|
Adapted as a standalone component for Hyprland + Quickshell users who want just the overview functionality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**Note:** Maintenance will be limited due to time constraints, but **PRs and code improvements are welcome!** Feel free to contribute or fork for your own needs.
|
||||||
|
|
||||||
|
Made with ❤️ for the Hyprland community
|
||||||
|
|
||||||
|
</div>
|
||||||
BIN
modules/home/overview/assets/image.png
Normal file
BIN
modules/home/overview/assets/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
148
modules/home/overview/common/Appearance.qml
Normal file
148
modules/home/overview/common/Appearance.qml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import "functions"
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property QtObject m3colors
|
||||||
|
property QtObject animation
|
||||||
|
property QtObject animationCurves
|
||||||
|
property QtObject colors
|
||||||
|
property QtObject rounding
|
||||||
|
property QtObject font
|
||||||
|
property QtObject sizes
|
||||||
|
|
||||||
|
m3colors: QtObject {
|
||||||
|
property bool darkmode: true
|
||||||
|
property color m3primary: "#E5B6F2"
|
||||||
|
property color m3onPrimary: "#452152"
|
||||||
|
property color m3primaryContainer: "#5D386A"
|
||||||
|
property color m3onPrimaryContainer: "#F9D8FF"
|
||||||
|
property color m3secondary: "#D5C0D7"
|
||||||
|
property color m3onSecondary: "#392C3D"
|
||||||
|
property color m3secondaryContainer: "#534457"
|
||||||
|
property color m3onSecondaryContainer: "#F2DCF3"
|
||||||
|
property color m3background: "#161217"
|
||||||
|
property color m3onBackground: "#EAE0E7"
|
||||||
|
property color m3surface: "#161217"
|
||||||
|
property color m3surfaceContainerLow: "#1F1A1F"
|
||||||
|
property color m3surfaceContainer: "#231E23"
|
||||||
|
property color m3surfaceContainerHigh: "#2D282E"
|
||||||
|
property color m3surfaceContainerHighest: "#383339"
|
||||||
|
property color m3onSurface: "#EAE0E7"
|
||||||
|
property color m3surfaceVariant: "#4C444D"
|
||||||
|
property color m3onSurfaceVariant: "#CFC3CD"
|
||||||
|
property color m3inverseSurface: "#EAE0E7"
|
||||||
|
property color m3inverseOnSurface: "#342F34"
|
||||||
|
property color m3outline: "#988E97"
|
||||||
|
property color m3outlineVariant: "#4C444D"
|
||||||
|
property color m3shadow: "#000000"
|
||||||
|
}
|
||||||
|
|
||||||
|
colors: QtObject {
|
||||||
|
property color colSubtext: m3colors.m3outline
|
||||||
|
property color colLayer0: m3colors.m3background
|
||||||
|
property color colOnLayer0: m3colors.m3onBackground
|
||||||
|
property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
|
||||||
|
property color colLayer1: m3colors.m3surfaceContainerLow
|
||||||
|
property color colOnLayer1: m3colors.m3onSurfaceVariant
|
||||||
|
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45)
|
||||||
|
property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92)
|
||||||
|
property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85)
|
||||||
|
property color colLayer2: m3colors.m3surfaceContainer
|
||||||
|
property color colOnLayer2: m3colors.m3onSurface
|
||||||
|
property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90)
|
||||||
|
property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80)
|
||||||
|
property color colPrimary: m3colors.m3primary
|
||||||
|
property color colOnPrimary: m3colors.m3onPrimary
|
||||||
|
property color colSecondary: m3colors.m3secondary
|
||||||
|
property color colSecondaryContainer: m3colors.m3secondaryContainer
|
||||||
|
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
|
||||||
|
property color colTooltip: m3colors.m3inverseSurface
|
||||||
|
property color colOnTooltip: m3colors.m3inverseOnSurface
|
||||||
|
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
|
||||||
|
property color colOutline: m3colors.m3outline
|
||||||
|
}
|
||||||
|
|
||||||
|
rounding: QtObject {
|
||||||
|
property int unsharpen: 2
|
||||||
|
property int verysmall: 8
|
||||||
|
property int small: 12
|
||||||
|
property int normal: 17
|
||||||
|
property int large: 23
|
||||||
|
property int full: 9999
|
||||||
|
property int screenRounding: large
|
||||||
|
property int windowRounding: 18
|
||||||
|
}
|
||||||
|
|
||||||
|
font: QtObject {
|
||||||
|
property QtObject family: QtObject {
|
||||||
|
property string main: "sans-serif"
|
||||||
|
property string title: "sans-serif"
|
||||||
|
property string expressive: "sans-serif"
|
||||||
|
}
|
||||||
|
property QtObject pixelSize: QtObject {
|
||||||
|
property int smaller: 12
|
||||||
|
property int small: 15
|
||||||
|
property int normal: 16
|
||||||
|
property int larger: 19
|
||||||
|
property int huge: 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animationCurves: QtObject {
|
||||||
|
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
|
||||||
|
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
|
||||||
|
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
|
||||||
|
readonly property real expressiveDefaultSpatialDuration: 500
|
||||||
|
readonly property real expressiveEffectsDuration: 200
|
||||||
|
}
|
||||||
|
|
||||||
|
animation: QtObject {
|
||||||
|
property QtObject elementMove: QtObject {
|
||||||
|
property int duration: animationCurves.expressiveDefaultSpatialDuration
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMove.duration
|
||||||
|
easing.type: root.animation.elementMove.type
|
||||||
|
easing.bezierCurve: root.animation.elementMove.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject elementMoveEnter: QtObject {
|
||||||
|
property int duration: 400
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.emphasizedDecel
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMoveEnter.duration
|
||||||
|
easing.type: root.animation.elementMoveEnter.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject elementMoveFast: QtObject {
|
||||||
|
property int duration: animationCurves.expressiveEffectsDuration
|
||||||
|
property int type: Easing.BezierSpline
|
||||||
|
property list<real> bezierCurve: animationCurves.expressiveEffects
|
||||||
|
property Component numberAnimation: Component {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root.animation.elementMoveFast.duration
|
||||||
|
easing.type: root.animation.elementMoveFast.type
|
||||||
|
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizes: QtObject {
|
||||||
|
property real elevationMargin: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
22
modules/home/overview/common/Config.qml
Normal file
22
modules/home/overview/common/Config.qml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property QtObject options: QtObject {
|
||||||
|
property QtObject overview: QtObject {
|
||||||
|
property int rows: 2
|
||||||
|
property int columns: 5
|
||||||
|
property real scale: 0.16
|
||||||
|
property bool enable: true
|
||||||
|
}
|
||||||
|
|
||||||
|
property QtObject hacks: QtObject {
|
||||||
|
property int arbitraryRaceConditionDelay: 150
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
modules/home/overview/common/functions/ColorUtils.qml
Normal file
68
modules/home/overview/common/functions/ColorUtils.qml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
pragma Singleton
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
function colorWithHueOf(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
var hue = c2.hsvHue;
|
||||||
|
var sat = c1.hsvSaturation;
|
||||||
|
var val = c1.hsvValue;
|
||||||
|
var alpha = c1.a;
|
||||||
|
return Qt.hsva(hue, sat, val, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorWithSaturationOf(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
var hue = c1.hsvHue;
|
||||||
|
var sat = c2.hsvSaturation;
|
||||||
|
var val = c1.hsvValue;
|
||||||
|
var alpha = c1.a;
|
||||||
|
return Qt.hsva(hue, sat, val, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorWithLightness(color, lightness) {
|
||||||
|
var c = Qt.color(color);
|
||||||
|
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorWithLightnessOf(color1, color2) {
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
return colorWithLightness(color1, c2.hslLightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
function adaptToAccent(color1, color2) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
var hue = c2.hslHue;
|
||||||
|
var sat = c2.hslSaturation;
|
||||||
|
var light = c1.hslLightness;
|
||||||
|
var alpha = c1.a;
|
||||||
|
return Qt.hsla(hue, sat, light, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mix(color1, color2, percentage = 0.5) {
|
||||||
|
var c1 = Qt.color(color1);
|
||||||
|
var c2 = Qt.color(color2);
|
||||||
|
return Qt.rgba(
|
||||||
|
percentage * c1.r + (1 - percentage) * c2.r,
|
||||||
|
percentage * c1.g + (1 - percentage) * c2.g,
|
||||||
|
percentage * c1.b + (1 - percentage) * c2.b,
|
||||||
|
percentage * c1.a + (1 - percentage) * c2.a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function transparentize(color, percentage = 1) {
|
||||||
|
var c = Qt.color(color);
|
||||||
|
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAlpha(color, alpha) {
|
||||||
|
var c = Qt.color(color);
|
||||||
|
var a = Math.max(0, Math.min(1, alpha));
|
||||||
|
return Qt.rgba(c.r, c.g, c.b, a);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
modules/home/overview/common/functions/qmldir
Normal file
1
modules/home/overview/common/functions/qmldir
Normal file
@@ -0,0 +1 @@
|
|||||||
|
singleton ColorUtils 1.0 ColorUtils.qml
|
||||||
7
modules/home/overview/common/qmldir
Normal file
7
modules/home/overview/common/qmldir
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
singleton Appearance 1.0 Appearance.qml
|
||||||
|
singleton Config 1.0 Config.qml
|
||||||
|
singleton ColorUtils 1.0 functions/ColorUtils.qml
|
||||||
|
StyledText 1.0 widgets/StyledText.qml
|
||||||
|
StyledRectangularShadow 1.0 widgets/StyledRectangularShadow.qml
|
||||||
|
StyledToolTip 1.0 widgets/StyledToolTip.qml
|
||||||
|
StyledToolTipContent 1.0 widgets/StyledToolTipContent.qml
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
RectangularShadow {
|
||||||
|
required property var target
|
||||||
|
anchors.fill: target
|
||||||
|
radius: 20
|
||||||
|
blur: 0.9 * Appearance.sizes.elevationMargin
|
||||||
|
offset: Qt.vector2d(0.0, 1.0)
|
||||||
|
spread: 1
|
||||||
|
color: Appearance.colors.colShadow
|
||||||
|
cached: true
|
||||||
|
}
|
||||||
16
modules/home/overview/common/widgets/StyledText.qml
Normal file
16
modules/home/overview/common/widgets/StyledText.qml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import QtQuick
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: root
|
||||||
|
property bool animateChange: false
|
||||||
|
|
||||||
|
renderType: Text.NativeRendering
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font {
|
||||||
|
hintingPreference: Font.PreferFullHinting
|
||||||
|
family: Appearance?.font.family.main ?? "sans-serif"
|
||||||
|
pixelSize: Appearance?.font.pixelSize.small ?? 15
|
||||||
|
}
|
||||||
|
color: Appearance?.m3colors.m3onBackground ?? "white"
|
||||||
|
}
|
||||||
23
modules/home/overview/common/widgets/StyledToolTip.qml
Normal file
23
modules/home/overview/common/widgets/StyledToolTip.qml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import "."
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
id: root
|
||||||
|
property bool extraVisibleCondition: true
|
||||||
|
property bool alternativeVisibleCondition: false
|
||||||
|
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
|
||||||
|
verticalPadding: 5
|
||||||
|
horizontalPadding: 10
|
||||||
|
background: null
|
||||||
|
|
||||||
|
visible: internalVisibleCondition
|
||||||
|
|
||||||
|
contentItem: StyledToolTipContent {
|
||||||
|
id: contentItem
|
||||||
|
text: root.text
|
||||||
|
shown: root.internalVisibleCondition
|
||||||
|
horizontalPadding: root.horizontalPadding
|
||||||
|
verticalPadding: root.verticalPadding
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import QtQuick
|
||||||
|
import "."
|
||||||
|
import "../"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
required property string text
|
||||||
|
property bool shown: false
|
||||||
|
property real horizontalPadding: 10
|
||||||
|
property real verticalPadding: 5
|
||||||
|
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
|
||||||
|
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
|
||||||
|
|
||||||
|
property bool isVisible: backgroundRectangle.implicitHeight > 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: backgroundRectangle
|
||||||
|
anchors {
|
||||||
|
bottom: root.bottom
|
||||||
|
horizontalCenter: root.horizontalCenter
|
||||||
|
}
|
||||||
|
color: Appearance?.colors.colTooltip ?? "#3C4043"
|
||||||
|
radius: Appearance?.rounding.verysmall ?? 7
|
||||||
|
opacity: shown ? 1 : 0
|
||||||
|
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
|
||||||
|
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on implicitWidth {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on opacity {
|
||||||
|
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: tooltipTextObject
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.text
|
||||||
|
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
|
||||||
|
font.hintingPreference: Font.PreferNoHinting
|
||||||
|
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
modules/home/overview/common/widgets/qmldir
Normal file
4
modules/home/overview/common/widgets/qmldir
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
StyledText 1.0 StyledText.qml
|
||||||
|
StyledRectangularShadow 1.0 StyledRectangularShadow.qml
|
||||||
|
StyledToolTip 1.0 StyledToolTip.qml
|
||||||
|
StyledToolTipContent 1.0 StyledToolTipContent.qml
|
||||||
147
modules/home/overview/modules/overview/Overview.qml
Normal file
147
modules/home/overview/modules/overview/Overview.qml
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import "../../common"
|
||||||
|
import "../../services"
|
||||||
|
import "."
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: overviewScope
|
||||||
|
Variants {
|
||||||
|
id: overviewVariants
|
||||||
|
model: Quickshell.screens
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
required property var modelData
|
||||||
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
|
||||||
|
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
|
||||||
|
screen: modelData
|
||||||
|
visible: GlobalStates.overviewOpen
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "quickshell:overview"
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
mask: Region {
|
||||||
|
item: GlobalStates.overviewOpen ? keyHandler : null
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: !(Config?.options.overview.enable ?? true)
|
||||||
|
right: !(Config?.options.overview.enable ?? true)
|
||||||
|
}
|
||||||
|
|
||||||
|
HyprlandFocusGrab {
|
||||||
|
id: grab
|
||||||
|
windows: [root]
|
||||||
|
property bool canBeActive: root.monitorIsFocused
|
||||||
|
active: false
|
||||||
|
onCleared: () => {
|
||||||
|
if (!active)
|
||||||
|
GlobalStates.overviewOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: GlobalStates
|
||||||
|
function onOverviewOpenChanged() {
|
||||||
|
if (GlobalStates.overviewOpen) {
|
||||||
|
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
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: keyHandler
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: GlobalStates.overviewOpen
|
||||||
|
focus: GlobalStates.overviewOpen
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return) {
|
||||||
|
GlobalStates.overviewOpen = false;
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Left || event.key === Qt.Key_Right || event.key === Qt.Key_Up || event.key === Qt.Key_Down) {
|
||||||
|
const workspacesPerGroup = Config.options.overview.rows * Config.options.overview.columns;
|
||||||
|
const currentId = Hyprland.focusedMonitor?.activeWorkspace?.id ?? 1;
|
||||||
|
const currentGroup = Math.floor((currentId - 1) / workspacesPerGroup);
|
||||||
|
const minWorkspaceId = currentGroup * workspacesPerGroup + 1;
|
||||||
|
const maxWorkspaceId = minWorkspaceId + workspacesPerGroup - 1;
|
||||||
|
|
||||||
|
let targetId;
|
||||||
|
if (event.key === Qt.Key_Left) {
|
||||||
|
targetId = currentId - 1;
|
||||||
|
if (targetId < minWorkspaceId) targetId = maxWorkspaceId;
|
||||||
|
} else if (event.key === Qt.Key_Right) {
|
||||||
|
targetId = currentId + 1;
|
||||||
|
if (targetId > maxWorkspaceId) targetId = minWorkspaceId;
|
||||||
|
} else if (event.key === Qt.Key_Up) {
|
||||||
|
targetId = currentId - Config.options.overview.columns;
|
||||||
|
if (targetId < minWorkspaceId) targetId += workspacesPerGroup;
|
||||||
|
} else {
|
||||||
|
targetId = currentId + Config.options.overview.columns;
|
||||||
|
if (targetId > maxWorkspaceId) targetId -= workspacesPerGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hyprland.dispatch("workspace " + targetId);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: columnLayout
|
||||||
|
visible: GlobalStates.overviewOpen
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: overviewLoader
|
||||||
|
active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true)
|
||||||
|
sourceComponent: OverviewWidget {
|
||||||
|
panelWindow: root
|
||||||
|
visible: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "overview"
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
|
||||||
|
}
|
||||||
|
function close() {
|
||||||
|
GlobalStates.overviewOpen = false;
|
||||||
|
}
|
||||||
|
function open() {
|
||||||
|
GlobalStates.overviewOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
303
modules/home/overview/modules/overview/OverviewWidget.qml
Normal file
303
modules/home/overview/modules/overview/OverviewWidget.qml
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import "../../common"
|
||||||
|
import "../../common/functions"
|
||||||
|
import "../../common/widgets"
|
||||||
|
import "../../services"
|
||||||
|
import "."
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
required property var panelWindow
|
||||||
|
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
|
||||||
|
readonly property var toplevels: ToplevelManager.toplevels
|
||||||
|
readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
|
||||||
|
readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown)
|
||||||
|
property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name)
|
||||||
|
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)
|
||||||
|
property real scale: Config.options.overview.scale
|
||||||
|
property color activeBorderColor: Appearance.colors.colSecondary
|
||||||
|
|
||||||
|
property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ?
|
||||||
|
((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) :
|
||||||
|
((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale)
|
||||||
|
property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ?
|
||||||
|
((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) :
|
||||||
|
((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale)
|
||||||
|
|
||||||
|
property real workspaceNumberMargin: 80
|
||||||
|
property real workspaceNumberSize: 250 * monitor.scale
|
||||||
|
property int workspaceZ: 0
|
||||||
|
property int windowZ: 1
|
||||||
|
property int windowDraggingZ: 99999
|
||||||
|
property real workspaceSpacing: 5
|
||||||
|
|
||||||
|
property int draggingFromWorkspace: -1
|
||||||
|
property int draggingTargetWorkspace: -1
|
||||||
|
|
||||||
|
implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
|
||||||
|
implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
|
||||||
|
|
||||||
|
property Component windowComponent: OverviewWindow {}
|
||||||
|
property list<OverviewWindow> windowWidgets: []
|
||||||
|
|
||||||
|
StyledRectangularShadow {
|
||||||
|
target: overviewBackground
|
||||||
|
}
|
||||||
|
Rectangle { // Background
|
||||||
|
id: overviewBackground
|
||||||
|
property real padding: 10
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.sizes.elevationMargin
|
||||||
|
|
||||||
|
implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2
|
||||||
|
implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2
|
||||||
|
radius: Appearance.rounding.screenRounding * root.scale + padding
|
||||||
|
color: Appearance.colors.colLayer0
|
||||||
|
border.width: 1
|
||||||
|
border.color: Appearance.colors.colLayer0Border
|
||||||
|
|
||||||
|
ColumnLayout { // Workspaces
|
||||||
|
id: workspaceColumnLayout
|
||||||
|
|
||||||
|
z: root.workspaceZ
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: workspaceSpacing
|
||||||
|
Repeater {
|
||||||
|
model: Config.options.overview.rows
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: row
|
||||||
|
property int rowIndex: index
|
||||||
|
spacing: workspaceSpacing
|
||||||
|
|
||||||
|
Repeater { // Workspace repeater
|
||||||
|
model: Config.options.overview.columns
|
||||||
|
Rectangle { // Workspace
|
||||||
|
id: workspace
|
||||||
|
property int colIndex: index
|
||||||
|
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * Config.options.overview.columns + colIndex + 1
|
||||||
|
property color defaultWorkspaceColor: Appearance.colors.colLayer1
|
||||||
|
property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
|
||||||
|
property color hoveredBorderColor: Appearance.colors.colLayer2Hover
|
||||||
|
property bool hoveredWhileDragging: false
|
||||||
|
|
||||||
|
implicitWidth: root.workspaceImplicitWidth
|
||||||
|
implicitHeight: root.workspaceImplicitHeight
|
||||||
|
color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor
|
||||||
|
radius: Appearance.rounding.screenRounding * root.scale
|
||||||
|
border.width: 2
|
||||||
|
border.color: hoveredWhileDragging ? hoveredBorderColor : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: workspaceValue
|
||||||
|
font {
|
||||||
|
pixelSize: root.workspaceNumberSize * root.scale
|
||||||
|
weight: Font.DemiBold
|
||||||
|
family: Appearance.font.family.expressive
|
||||||
|
}
|
||||||
|
color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: workspaceArea
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onClicked: {
|
||||||
|
if (root.draggingTargetWorkspace === -1) {
|
||||||
|
GlobalStates.overviewOpen = false
|
||||||
|
Hyprland.dispatch(`workspace ${workspaceValue}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DropArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onEntered: {
|
||||||
|
root.draggingTargetWorkspace = workspaceValue
|
||||||
|
if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return;
|
||||||
|
hoveredWhileDragging = true
|
||||||
|
}
|
||||||
|
onExited: {
|
||||||
|
hoveredWhileDragging = false
|
||||||
|
if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { // Windows & focused workspace indicator
|
||||||
|
id: windowSpace
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitWidth: workspaceColumnLayout.implicitWidth
|
||||||
|
implicitHeight: workspaceColumnLayout.implicitHeight
|
||||||
|
|
||||||
|
Repeater { // Window repeater
|
||||||
|
model: ScriptModel {
|
||||||
|
values: {
|
||||||
|
return ToplevelManager.toplevels.values.filter((toplevel) => {
|
||||||
|
const address = `0x${toplevel.HyprlandToplevel.address}`
|
||||||
|
var win = windowByAddress[address]
|
||||||
|
const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)
|
||||||
|
return inWorkspaceGroup;
|
||||||
|
}).sort((a, b) => {
|
||||||
|
// Proper stacking order based on Hyprland's window properties
|
||||||
|
const addrA = `0x${a.HyprlandToplevel.address}`
|
||||||
|
const addrB = `0x${b.HyprlandToplevel.address}`
|
||||||
|
const winA = windowByAddress[addrA]
|
||||||
|
const winB = windowByAddress[addrB]
|
||||||
|
|
||||||
|
// 1. Pinned windows are always on top
|
||||||
|
if (winA?.pinned !== winB?.pinned) {
|
||||||
|
return winA?.pinned ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Floating windows above tiled windows
|
||||||
|
if (winA?.floating !== winB?.floating) {
|
||||||
|
return winA?.floating ? 1 : -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Within same category, sort by focus history
|
||||||
|
// Lower focusHistoryID = more recently focused = higher in stack
|
||||||
|
return (winB?.focusHistoryID ?? 0) - (winA?.focusHistoryID ?? 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate: OverviewWindow {
|
||||||
|
id: window
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
property int monitorId: windowData?.monitor
|
||||||
|
property var monitor: HyprlandData.monitors.find(m => m.id === monitorId)
|
||||||
|
property var address: `0x${modelData.HyprlandToplevel.address}`
|
||||||
|
windowData: windowByAddress[address]
|
||||||
|
toplevel: modelData
|
||||||
|
monitorData: monitor
|
||||||
|
|
||||||
|
// Calculate scale relative to window's source monitor
|
||||||
|
property real sourceMonitorWidth: (monitor?.transform % 2 === 1) ?
|
||||||
|
(monitor?.height ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0) :
|
||||||
|
(monitor?.width ?? 1920) / (monitor?.scale ?? 1) - (monitor?.reserved?.[0] ?? 0) - (monitor?.reserved?.[2] ?? 0)
|
||||||
|
property real sourceMonitorHeight: (monitor?.transform % 2 === 1) ?
|
||||||
|
(monitor?.width ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0) :
|
||||||
|
(monitor?.height ?? 1080) / (monitor?.scale ?? 1) - (monitor?.reserved?.[1] ?? 0) - (monitor?.reserved?.[3] ?? 0)
|
||||||
|
|
||||||
|
// Scale windows to fit the workspace size, accounting for different monitor sizes
|
||||||
|
scale: Math.min(
|
||||||
|
root.workspaceImplicitWidth / sourceMonitorWidth,
|
||||||
|
root.workspaceImplicitHeight / sourceMonitorHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
availableWorkspaceWidth: root.workspaceImplicitWidth
|
||||||
|
availableWorkspaceHeight: root.workspaceImplicitHeight
|
||||||
|
widgetMonitorId: root.monitor.id
|
||||||
|
|
||||||
|
property bool atInitPosition: (initX == x && initY == y)
|
||||||
|
|
||||||
|
property int workspaceColIndex: (windowData?.workspace.id - 1) % Config.options.overview.columns
|
||||||
|
property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / Config.options.overview.columns)
|
||||||
|
xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
|
||||||
|
yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: updateWindowPosition
|
||||||
|
interval: Config.options.hacks.arbitraryRaceConditionDelay
|
||||||
|
repeat: false
|
||||||
|
running: false
|
||||||
|
onTriggered: {
|
||||||
|
window.x = Math.round(Math.max((windowData?.at[0] - (monitor?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset)
|
||||||
|
window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
z: atInitPosition ? (root.windowZ + index) : root.windowDraggingZ
|
||||||
|
Drag.hotSpot.x: targetWindowWidth / 2
|
||||||
|
Drag.hotSpot.y: targetWindowHeight / 2
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onEntered: hovered = true
|
||||||
|
onExited: hovered = false
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||||
|
drag.target: parent
|
||||||
|
onPressed: (mouse) => {
|
||||||
|
root.draggingFromWorkspace = windowData?.workspace.id
|
||||||
|
window.pressed = true
|
||||||
|
window.Drag.active = true
|
||||||
|
window.Drag.source = window
|
||||||
|
window.Drag.hotSpot.x = mouse.x
|
||||||
|
window.Drag.hotSpot.y = mouse.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}`)
|
||||||
|
updateWindowPosition.restart()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.x = window.initX
|
||||||
|
window.y = window.initY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onClicked: (event) => {
|
||||||
|
if (!windowData) return;
|
||||||
|
|
||||||
|
if (event.button === Qt.LeftButton) {
|
||||||
|
GlobalStates.overviewOpen = false
|
||||||
|
Hyprland.dispatch(`focuswindow address:${windowData.address}`)
|
||||||
|
event.accepted = true
|
||||||
|
} else if (event.button === Qt.MiddleButton) {
|
||||||
|
Hyprland.dispatch(`closewindow address:${windowData.address}`)
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledToolTip {
|
||||||
|
extraVisibleCondition: false
|
||||||
|
alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
|
||||||
|
text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.columns)
|
||||||
|
property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % Config.options.overview.columns
|
||||||
|
x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
|
||||||
|
y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex
|
||||||
|
z: root.windowZ
|
||||||
|
width: root.workspaceImplicitWidth
|
||||||
|
height: root.workspaceImplicitHeight
|
||||||
|
color: "transparent"
|
||||||
|
radius: Appearance.rounding.screenRounding * root.scale
|
||||||
|
border.width: 2
|
||||||
|
border.color: root.activeBorderColor
|
||||||
|
Behavior on x {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
modules/home/overview/modules/overview/OverviewWindow.qml
Normal file
109
modules/home/overview/modules/overview/OverviewWindow.qml
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import "../../common"
|
||||||
|
import "../../common/functions"
|
||||||
|
import "../../services"
|
||||||
|
|
||||||
|
Item { // Window
|
||||||
|
id: root
|
||||||
|
property var toplevel
|
||||||
|
property var windowData
|
||||||
|
property var monitorData
|
||||||
|
property var scale
|
||||||
|
property var availableWorkspaceWidth
|
||||||
|
property var availableWorkspaceHeight
|
||||||
|
property bool restrictToWorkspace: true
|
||||||
|
property real initX: Math.max(((windowData?.at[0] ?? 0) - (monitorData?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale, 0) + xOffset
|
||||||
|
property real initY: Math.max(((windowData?.at[1] ?? 0) - (monitorData?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale, 0) + yOffset
|
||||||
|
property real xOffset: 0
|
||||||
|
property real yOffset: 0
|
||||||
|
property int widgetMonitorId: 0
|
||||||
|
|
||||||
|
property var targetWindowWidth: (windowData?.size[0] ?? 100) * scale
|
||||||
|
property var targetWindowHeight: (windowData?.size[1] ?? 100) * scale
|
||||||
|
property bool hovered: false
|
||||||
|
property bool pressed: false
|
||||||
|
|
||||||
|
property var iconToWindowRatio: 0.25
|
||||||
|
property var xwaylandIndicatorToIconRatio: 0.35
|
||||||
|
property var iconToWindowRatioCompact: 0.45
|
||||||
|
property var entry: DesktopEntries.heuristicLookup(windowData?.class)
|
||||||
|
property var iconPath: Quickshell.iconPath(entry?.icon ?? windowData?.class ?? "application-x-executable", "image-missing")
|
||||||
|
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
|
||||||
|
|
||||||
|
property bool indicateXWayland: windowData?.xwayland ?? false
|
||||||
|
|
||||||
|
x: initX
|
||||||
|
y: initY
|
||||||
|
width: Math.min((windowData?.size[0] ?? 100) * root.scale, availableWorkspaceWidth)
|
||||||
|
height: Math.min((windowData?.size[1] ?? 100) * root.scale, availableWorkspaceHeight)
|
||||||
|
opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : 0.4
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: root.width
|
||||||
|
height: root.height
|
||||||
|
radius: Appearance.rounding.windowRounding * root.scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on x {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on y {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on width {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScreencopyView {
|
||||||
|
id: windowPreview
|
||||||
|
anchors.fill: parent
|
||||||
|
captureSource: GlobalStates.overviewOpen ? root.toplevel : null
|
||||||
|
live: true
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Appearance.rounding.windowRounding * root.scale
|
||||||
|
color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) :
|
||||||
|
hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) :
|
||||||
|
ColorUtils.transparentize(Appearance.colors.colLayer2)
|
||||||
|
border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
|
||||||
|
border.width : 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: Appearance.font.pixelSize.smaller * 0.5
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: windowIcon
|
||||||
|
property var iconSize: {
|
||||||
|
return Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1);
|
||||||
|
}
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
source: root.iconPath
|
||||||
|
width: iconSize
|
||||||
|
height: iconSize
|
||||||
|
sourceSize: Qt.size(iconSize, iconSize)
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
Behavior on height {
|
||||||
|
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
modules/home/overview/modules/overview/qmldir
Normal file
3
modules/home/overview/modules/overview/qmldir
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Overview 1.0 Overview.qml
|
||||||
|
OverviewWidget 1.0 OverviewWidget.qml
|
||||||
|
OverviewWindow 1.0 OverviewWindow.qml
|
||||||
11
modules/home/overview/services/GlobalStates.qml
Normal file
11
modules/home/overview/services/GlobalStates.qml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
property bool overviewOpen: false
|
||||||
|
property bool superReleaseMightTrigger: true
|
||||||
|
}
|
||||||
137
modules/home/overview/services/HyprlandData.qml
Normal file
137
modules/home/overview/services/HyprlandData.qml
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
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 workspaces: []
|
||||||
|
property var workspaceIds: []
|
||||||
|
property var workspaceById: ({})
|
||||||
|
property var activeWorkspace: null
|
||||||
|
property var monitors: []
|
||||||
|
property var layers: ({})
|
||||||
|
|
||||||
|
function updateWindowList() {
|
||||||
|
getClients.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLayers() {
|
||||||
|
getLayers.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMonitors() {
|
||||||
|
getMonitors.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWorkspaces() {
|
||||||
|
getWorkspaces.running = true;
|
||||||
|
getActiveWorkspace.running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAll() {
|
||||||
|
updateWindowList();
|
||||||
|
updateMonitors();
|
||||||
|
updateLayers();
|
||||||
|
updateWorkspaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
function biggestWindowForWorkspace(workspaceId) {
|
||||||
|
const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
updateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland
|
||||||
|
|
||||||
|
function onRawEvent(event) {
|
||||||
|
updateAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getClients
|
||||||
|
command: ["hyprctl", "clients", "-j"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: clientsCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.windowList = JSON.parse(clientsCollector.text)
|
||||||
|
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: ["hyprctl", "monitors", "-j"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: monitorsCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.monitors = JSON.parse(monitorsCollector.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getLayers
|
||||||
|
command: ["hyprctl", "layers", "-j"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: layersCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.layers = JSON.parse(layersCollector.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getWorkspaces
|
||||||
|
command: ["hyprctl", "workspaces", "-j"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: workspacesCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.workspaces = JSON.parse(workspacesCollector.text);
|
||||||
|
let tempWorkspaceById = {};
|
||||||
|
for (var i = 0; i < root.workspaces.length; ++i) {
|
||||||
|
var ws = root.workspaces[i];
|
||||||
|
tempWorkspaceById[ws.id] = ws;
|
||||||
|
}
|
||||||
|
root.workspaceById = tempWorkspaceById;
|
||||||
|
root.workspaceIds = root.workspaces.map(ws => ws.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getActiveWorkspace
|
||||||
|
command: ["hyprctl", "activeworkspace", "-j"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: activeWorkspaceCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
modules/home/overview/services/qmldir
Normal file
2
modules/home/overview/services/qmldir
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
singleton HyprlandData 1.0 HyprlandData.qml
|
||||||
|
singleton GlobalStates 1.0 GlobalStates.qml
|
||||||
16
modules/home/overview/shell.qml
Normal file
16
modules/home/overview/shell.qml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//@ pragma UseQApplication
|
||||||
|
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||||
|
|
||||||
|
import "./modules/overview/"
|
||||||
|
import "./services/"
|
||||||
|
import "./common/"
|
||||||
|
import "./common/functions/"
|
||||||
|
import "./common/widgets/"
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
|
||||||
|
ShellRoot {
|
||||||
|
Overview {}
|
||||||
|
}
|
||||||
@@ -30,5 +30,6 @@
|
|||||||
inherit username;
|
inherit username;
|
||||||
})
|
})
|
||||||
(import ./web-search.nix {inherit pkgs;})
|
(import ./web-search.nix {inherit pkgs;})
|
||||||
|
(import ./restart.noctalia.nix {inherit pkgs;})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
54
modules/home/scripts/restart.noctalia
Normal file
54
modules/home/scripts/restart.noctalia
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Restart the Noctalia QuickShell session by terminating only the noctalia-shell
|
||||||
|
# processes, avoiding any signals to unrelated process groups (e.g. Hyprland).
|
||||||
|
|
||||||
|
log() { printf "[restart.noctalia] %s\n" "$*"; }
|
||||||
|
|
||||||
|
list_target_pids() {
|
||||||
|
# Collect only PIDs whose command explicitly runs noctalia-shell
|
||||||
|
# - direct wrapper: ".../noctalia-shell"
|
||||||
|
# - quickshell/qs with "-c noctalia-shell"
|
||||||
|
ps -eo pid=,cmd= \
|
||||||
|
| ${GREP:-grep} -E "(^|/)(noctalia-shell)( |$)|(^| )((qs|quickshell))( | ).*-c( |=)?noctalia-shell( |$)" \
|
||||||
|
| awk '{print $1}'
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate_targets() {
|
||||||
|
local pids left tries
|
||||||
|
mapfile -t pids < <(list_target_pids || true)
|
||||||
|
|
||||||
|
if ((${#pids[@]} > 0)); then
|
||||||
|
kill -TERM "${pids[@]}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait up to ~3s for clean exit
|
||||||
|
for tries in {1..15}; do
|
||||||
|
mapfile -t left < <(list_target_pids || true)
|
||||||
|
((${#left[@]} == 0)) && break
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Force kill leftovers only (do not touch anything else)
|
||||||
|
if ((${#left[@]} > 0)); then
|
||||||
|
kill -KILL "${left[@]}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_noctalia() {
|
||||||
|
# Prefer the noctalia-shell wrapper to ensure proper env and runtime flags
|
||||||
|
if command -v noctalia-shell >/dev/null 2>&1; then
|
||||||
|
nohup setsid noctalia-shell >/dev/null 2>&1 &
|
||||||
|
elif command -v quickshell >/dev/null 2>&1; then
|
||||||
|
nohup setsid quickshell -c noctalia-shell >/dev/null 2>&1 &
|
||||||
|
elif command -v qs >/dev/null 2>&1; then
|
||||||
|
nohup setsid qs -c noctalia-shell >/dev/null 2>&1 &
|
||||||
|
else
|
||||||
|
echo "Error: noctalia-shell/quickshell/qs not found in PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate_targets
|
||||||
|
start_noctalia
|
||||||
24
modules/home/scripts/restart.noctalia.nix
Normal file
24
modules/home/scripts/restart.noctalia.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{pkgs, ...}: let
|
||||||
|
binPath = pkgs.lib.makeBinPath [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.procps
|
||||||
|
pkgs.psmisc
|
||||||
|
pkgs.gnugrep
|
||||||
|
pkgs.findutils
|
||||||
|
pkgs.util-linux
|
||||||
|
pkgs.bash
|
||||||
|
];
|
||||||
|
script = builtins.readFile ./restart.noctalia;
|
||||||
|
in
|
||||||
|
pkgs.writeShellScriptBin "restart.noctalia" ''
|
||||||
|
set -euo pipefail
|
||||||
|
export PATH=${binPath}:$PATH
|
||||||
|
|
||||||
|
tmp_script=$(mktemp)
|
||||||
|
trap 'rm -f "$tmp_script"' EXIT
|
||||||
|
cat > "$tmp_script" <<'BASH_EOF'
|
||||||
|
${script}
|
||||||
|
BASH_EOF
|
||||||
|
chmod +x "$tmp_script"
|
||||||
|
exec ${pkgs.bash}/bin/bash "$tmp_script" "$@"
|
||||||
|
''
|
||||||
@@ -12,5 +12,6 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
platform = "qtct";
|
platform = "qtct";
|
||||||
};
|
};
|
||||||
|
noctalia-shell.enable = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ _: {
|
|||||||
enable = true;
|
enable = true;
|
||||||
mime.enable = true;
|
mime.enable = true;
|
||||||
mimeApps.enable = true;
|
mimeApps.enable = true;
|
||||||
|
configFile."mimeapps.list".force = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
|
|||||||
Reference in New Issue
Block a user