mirror of
https://github.com/JaKooLit/Hyprland-Dots.git
synced 2026-01-11 15:01:02 -03:00
Integrate Quickshell-Overview with Qt6 fixes and automation scripts
## Overview This commit integrates the corrected Quickshell-Overview feature across all installation and update workflows. The overview provides an AGS alternative with live window previews toggled via Super+TAB keybind. ## Changes ### 1. Quickshell Overview QML Files - Added config/quickshell/overview/ subdirectory with Qt6-compatible QML - Includes 20+ files covering: * OverviewWindow.qml with proper clipping (no OpacityMask, uses QtQuick.Effects) * OverviewWidget.qml for window handling * Overview.qml main component with Hyprland integration * Common utilities and styling * Services for Hyprland data and global state management ### 2. copy.sh Updates - Removes default shell.qml that blocks quickshell named config detection - Auto-copies config/quickshell/overview to ~/.config/quickshell/overview/ - Updates old 'qs' startup commands to 'qs -c overview' - Handles both fresh installs and config overwrite scenarios ### 3. upgrade.sh Updates - Added config/quickshell/ to upgrade directory list - Excludes shell.qml to preserve overview config detection capability - Enables seamless upgrades without losing quickshell settings ### 4. IPC Command Fixes - Corrected OverviewToggle.sh to use proper 'qs ipc -c overview call overview toggle' - Fixed startup commands from old 'qs' to 'qs -c overview' - Hyprland-Dots now uses corrected toggle script ## Qt6 Compatibility - Replaced Qt5Compat.GraphicalEffects with QtQuick.Effects - Removed OpacityMask in favor of Qt6-compatible clipping technique - All QML properly imports Qt6 modules ## Release Script - release.sh automatically uses copy.sh, inheriting all quickshell updates ## Testing - Verified on target systems (Fedora 43 VM, jak-nixos) - qs -c overview successfully launches overview config when shell.qml is removed - IPC toggle commands work correctly within Wayland sessions ## Files Modified - config/quickshell/overview/* (20 new files) - copy.sh (enhanced QS handling) - upgrade.sh (added quickshell to upgrade paths)
This commit is contained in:
214
config/quickshell/overview/README.md
Normal file
214
config/quickshell/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
config/quickshell/overview/assets/image.png
Normal file
BIN
config/quickshell/overview/assets/image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
148
config/quickshell/overview/common/Appearance.qml
Normal file
148
config/quickshell/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
config/quickshell/overview/common/Config.qml
Normal file
22
config/quickshell/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
config/quickshell/overview/common/functions/ColorUtils.qml
Normal file
68
config/quickshell/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
config/quickshell/overview/common/functions/qmldir
Normal file
1
config/quickshell/overview/common/functions/qmldir
Normal file
@@ -0,0 +1 @@
|
||||
singleton ColorUtils 1.0 ColorUtils.qml
|
||||
7
config/quickshell/overview/common/qmldir
Normal file
7
config/quickshell/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
config/quickshell/overview/common/widgets/StyledText.qml
Normal file
16
config/quickshell/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
config/quickshell/overview/common/widgets/StyledToolTip.qml
Normal file
23
config/quickshell/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
config/quickshell/overview/common/widgets/qmldir
Normal file
4
config/quickshell/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
config/quickshell/overview/modules/overview/Overview.qml
Normal file
147
config/quickshell/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
config/quickshell/overview/modules/overview/OverviewWidget.qml
Normal file
303
config/quickshell/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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
112
config/quickshell/overview/modules/overview/OverviewWindow.qml
Normal file
112
config/quickshell/overview/modules/overview/OverviewWindow.qml
Normal file
@@ -0,0 +1,112 @@
|
||||
import QtQuick.Effects
|
||||
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.smooth: true
|
||||
layer.mipmap: true
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clipContainer
|
||||
anchors.fill: parent
|
||||
radius: Appearance.rounding.windowRounding * root.scale
|
||||
clip: true
|
||||
color: "transparent"
|
||||
|
||||
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
config/quickshell/overview/modules/overview/qmldir
Normal file
3
config/quickshell/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
config/quickshell/overview/services/GlobalStates.qml
Normal file
11
config/quickshell/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
config/quickshell/overview/services/HyprlandData.qml
Normal file
137
config/quickshell/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
config/quickshell/overview/services/qmldir
Normal file
2
config/quickshell/overview/services/qmldir
Normal file
@@ -0,0 +1,2 @@
|
||||
singleton HyprlandData 1.0 HyprlandData.qml
|
||||
singleton GlobalStates 1.0 GlobalStates.qml
|
||||
16
config/quickshell/overview/shell.qml
Normal file
16
config/quickshell/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 {}
|
||||
}
|
||||
29
copy.sh
29
copy.sh
@@ -767,6 +767,13 @@ if command -v qs >/dev/null 2>&1; then
|
||||
cp -r "config/quickshell/" "$DIRPATH_QS" 2>&1 | tee -a "$LOG"
|
||||
fi
|
||||
else
|
||||
# If default shell.qml exists, it blocks named config subdirectory detection
|
||||
# Remove it to enable the overview config to be found
|
||||
if [ -f "$DIRPATH_QS/shell.qml" ]; then
|
||||
echo "${NOTE} - Removing default shell.qml to enable quickshell overview config detection" 2>&1 | tee -a "$LOG"
|
||||
rm "$DIRPATH_QS/shell.qml"
|
||||
fi
|
||||
|
||||
read -p "${CAT} Do you want to overwrite your existing ${YELLOW}quickshell${RESET} config? [y/N] " answer_qs
|
||||
case "$answer_qs" in
|
||||
[Yy]*)
|
||||
@@ -777,6 +784,8 @@ if command -v qs >/dev/null 2>&1; then
|
||||
cp -r "config/quickshell/" "$DIRPATH_QS" 2>&1 | tee -a "$LOG"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "${OK} - ${YELLOW}quickshell${RESET} overwritten successfully."
|
||||
# Remove default shell.qml from new copy to enable overview detection
|
||||
rm -f "$DIRPATH_QS/shell.qml" 2>&1 | tee -a "$LOG"
|
||||
else
|
||||
echo "${ERROR} - Failed to copy ${YELLOW}quickshell${RESET} config."
|
||||
exit 1
|
||||
@@ -787,6 +796,26 @@ if command -v qs >/dev/null 2>&1; then
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Ensure overview subdirectory exists and is up to date
|
||||
DIRPATH_OVERVIEW="$DIRPATH_QS/overview"
|
||||
if [ ! -d "$DIRPATH_OVERVIEW" ] && [ -d "config/quickshell/overview" ]; then
|
||||
echo "${INFO} - Copying quickshell overview config..." 2>&1 | tee -a "$LOG"
|
||||
cp -r "config/quickshell/overview" "$DIRPATH_QS/" 2>&1 | tee -a "$LOG"
|
||||
echo "${OK} - Quickshell overview config copied successfully" 2>&1 | tee -a "$LOG"
|
||||
fi
|
||||
|
||||
# Check for old quickshell startup commands and update them
|
||||
HYPR_STARTUP="$HOME/.config/hypr/configs/Startup_Apps.conf"
|
||||
if [ -f "$HYPR_STARTUP" ]; then
|
||||
if grep -q '^exec-once = qs\s*$\|^exec-once = qs &' "$HYPR_STARTUP"; then
|
||||
echo "${NOTE} - Found old Quickshell startup command, updating to new overview config..." 2>&1 | tee -a "$LOG"
|
||||
# Replace old 'qs' or 'qs &' with new 'qs -c overview'
|
||||
sed -i 's/^\(\s*\)exec-once = qs\s*$/\1exec-once = qs -c overview # Quickshell Overview/' "$HYPR_STARTUP" 2>&1 | tee -a "$LOG"
|
||||
sed -i 's/^\(\s*\)exec-once = qs &$/\1exec-once = qs -c overview # Quickshell Overview/' "$HYPR_STARTUP" 2>&1 | tee -a "$LOG"
|
||||
echo "${OK} - Updated Quickshell startup command to use overview config" 2>&1 | tee -a "$LOG"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
printf "\n%.0s" {1..1}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ declare -A directories=(
|
||||
["config/waybar/"]="$HOME/.config/waybar/"
|
||||
["config/cava/"]="$HOME/.config/cava/"
|
||||
["config/ags/"]="$HOME/.config/ags/"
|
||||
["config/quickshell/"]="$HOME/.config/quickshell/"
|
||||
["config/fastfetch/"]="$HOME/.config/fastfetch/"
|
||||
["config/wallust/"]="$HOME/.config/wallust/"
|
||||
["config/wlogout/"]="$HOME/.config/wlogout/"
|
||||
@@ -102,6 +103,7 @@ declare -A exclusions=(
|
||||
["config/hypr/"]="--exclude=UserConfigs/ --exclude=UserScripts/"
|
||||
["config/waybar/"]="--exclude=config --exclude=style.css"
|
||||
["config/rofi/"]="--exclude=.current_wallpaper"
|
||||
["config/quickshell/"]="--exclude=shell.qml"
|
||||
# Add more exclusions as needed
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user