feat: window info panel

Also disable reload popup
This commit is contained in:
2 * r + 2 * t 2025-06-21 22:47:13 +10:00
parent 97c0c387f1
commit 4a86b66d06
13 changed files with 477 additions and 51 deletions

View file

@ -14,6 +14,7 @@ Singleton {
property alias notifs: adapter.notifs
property alias osd: adapter.osd
property alias session: adapter.session
property alias winfo: adapter.winfo
property alias paths: adapter.paths
FileView {
@ -32,6 +33,7 @@ Singleton {
property JsonObject notifs: NotifsConfig {}
property JsonObject osd: OsdConfig {}
property JsonObject session: SessionConfig {}
property JsonObject winfo: WInfoConfig {}
property JsonObject paths: UserPaths {}
}
}

8
config/WInfoConfig.qml Normal file
View file

@ -0,0 +1,8 @@
import Quickshell.Io
JsonObject {
property JsonObject sizes: JsonObject {
property real heightMult: 0.7
property real detailsWidth: 500
}
}

View file

@ -10,6 +10,8 @@ import QtQuick.Layouts
Item {
id: root
required property Item wrapper
implicitWidth: Hyprland.activeClient ? child.implicitWidth : -Appearance.padding.large * 2
implicitHeight: child.implicitHeight
@ -65,7 +67,7 @@ Item {
radius: Appearance.rounding.normal
function onClicked(): void {
// TODO
root.wrapper.detach("winfo");
}
}

View file

@ -13,15 +13,17 @@ ShapePath {
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
property real ibr: invertBottomRounding ? -1 : 1
property real sideRounding: startX > 0 ? -1 : 1
strokeWidth: -1
fillColor: Config.border.colour
PathArc {
relativeX: root.roundingX
relativeY: root.rounding
relativeY: root.rounding * root.sideRounding
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
direction: PathArc.Counterclockwise
direction: root.sideRounding < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 2
@ -50,10 +52,10 @@ ShapePath {
}
PathArc {
relativeX: -root.roundingX
relativeY: root.rounding
relativeY: root.rounding * root.sideRounding
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
direction: PathArc.Counterclockwise
direction: root.sideRounding < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
}
Behavior on fillColor {
@ -71,4 +73,12 @@ ShapePath {
easing.bezierCurve: Appearance.anim.curves.standard
}
}
Behavior on sideRounding {
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.standard
}
}
}

View file

@ -9,15 +9,15 @@ import QtQuick
Item {
id: root
required property Item wrapper
required property ShellScreen screen
property string currentName
property real currentCenter
property bool hasCurrent
required property string currentName
required property real currentCenter
required property bool hasCurrent
anchors.centerIn: parent
implicitWidth: hasCurrent ? (content.children.find(c => c.shouldBeActive)?.implicitWidth ?? 0) + Appearance.padding.large * 2 : 0
implicitWidth: (content.children.find(c => c.shouldBeActive)?.implicitWidth ?? 0) + Appearance.padding.large * 2
implicitHeight: (content.children.find(c => c.shouldBeActive)?.implicitHeight ?? 0) + Appearance.padding.large * 2
Item {
@ -26,11 +26,11 @@ Item {
anchors.fill: parent
anchors.margins: Appearance.padding.large
clip: true
Popout {
name: "activewindow"
source: "ActiveWindow.qml"
sourceComponent: ActiveWindow {
wrapper: root.wrapper
}
}
Popout {
@ -85,30 +85,6 @@ Item {
}
}
Behavior on implicitWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
Behavior on implicitHeight {
enabled: root.implicitWidth > 0
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
Behavior on currentCenter {
enabled: root.implicitWidth > 0
NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
component Popout: Loader {
id: popout

View file

@ -1,6 +1,11 @@
pragma ComponentBehavior: Bound
import "root:/services"
import "root:/config"
import "root:/modules/windowinfo"
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick
Item {
@ -8,18 +13,168 @@ Item {
required property ShellScreen screen
property alias currentName: content.currentName
property alias currentCenter: content.currentCenter
property alias hasCurrent: content.hasCurrent
readonly property real nonAnimWidth: x > 0 || hasCurrent ? children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth : 0
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
property string currentName
property real currentCenter
property bool hasCurrent
property string detachedMode
readonly property bool isDetached: detachedMode.length > 0
property int animLength: Appearance.anim.durations.normal
property list<real> animCurve: Appearance.anim.curves.emphasized
function detach(mode: string): void {
animLength = Appearance.anim.durations.large;
detachedMode = mode;
focus = true;
}
function close(): void {
hasCurrent = false;
animCurve = Appearance.anim.curves.emphasizedAccel;
animLength = Appearance.anim.durations.normal;
detachedMode = "";
animCurve = Appearance.anim.curves.emphasized;
}
visible: width > 0 && height > 0
clip: true
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight
Content {
Keys.onEscapePressed: close()
HyprlandFocusGrab {
active: root.isDetached
windows: [QsWindow.window]
onCleared: root.close()
}
Binding {
when: root.isDetached
target: QsWindow.window
property: "WlrLayershell.keyboardFocus"
value: WlrKeyboardFocus.OnDemand
}
Comp {
id: content
screen: root.screen
shouldBeActive: !root.detachedMode
asynchronous: true
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
sourceComponent: Content {
wrapper: root
screen: root.screen
currentName: root.currentName
currentCenter: root.currentCenter
hasCurrent: root.hasCurrent
}
}
Comp {
shouldBeActive: root.detachedMode === "winfo"
asynchronous: true
anchors.centerIn: parent
sourceComponent: WindowInfo {
screen: root.screen
}
}
Behavior on x {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on y {
enabled: root.implicitWidth > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on implicitWidth {
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
Behavior on implicitHeight {
enabled: root.implicitWidth > 0
Anim {
duration: root.animLength
easing.bezierCurve: root.animCurve
}
}
component Comp: Loader {
id: comp
property bool shouldBeActive
asynchronous: true
active: false
opacity: 0
states: State {
name: "active"
when: comp.shouldBeActive
PropertyChanges {
comp.opacity: 1
comp.active: true
}
}
transitions: [
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
property: "active"
}
Anim {
property: "opacity"
easing.bezierCurve: Appearance.anim.curves.standard
}
}
},
Transition {
from: "active"
to: ""
SequentialAnimation {
Anim {
property: "opacity"
easing.bezierCurve: Appearance.anim.curves.standard
}
PropertyAction {
property: "active"
}
}
}
]
}
component Anim: NumberAnimation {
duration: Appearance.anim.durations.normal
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}

View file

@ -60,7 +60,7 @@ Shape {
wrapper: panels.popouts
invertBottomRounding: wrapper.y + wrapper.height + 1 >= root.height
startX: 0
startY: wrapper.y - rounding
startX: wrapper.x
startY: wrapper.y - rounding * sideRounding
}
}

View file

@ -80,11 +80,13 @@ Item {
screen: root.screen
anchors.left: parent.left
anchors.verticalCenter: parent.top
anchors.verticalCenterOffset: {
const off = root.popouts.currentCenter - Config.border.thickness;
const diff = root.height - Math.floor(off + implicitHeight / 2);
x: isDetached ? (root.width - nonAnimWidth) / 2 : 0
y: {
if (isDetached)
return (root.height - nonAnimHeight) / 2;
const off = currentCenter - Config.border.thickness - nonAnimHeight / 2;
const diff = root.height - Math.floor(off + nonAnimHeight);
if (diff < 0)
return off + diff;
return off;

View file

@ -0,0 +1,158 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import QtQuick
import QtQuick.Layouts
ColumnLayout {
id: root
anchors.fill: parent
spacing: Appearance.spacing.small
Label {
Layout.topMargin: Appearance.padding.large * 2
text: Hyprland.activeClient.title
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pointSize: Appearance.font.size.large
font.weight: 500
}
Label {
text: Hyprland.activeClient.wmClass
color: Colours.palette.m3tertiary
font.pointSize: Appearance.font.size.larger
}
StyledRect {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.leftMargin: Appearance.padding.large * 2
Layout.rightMargin: Appearance.padding.large * 2
Layout.topMargin: Appearance.spacing.normal
Layout.bottomMargin: Appearance.spacing.large
color: Colours.palette.m3secondary
}
Detail {
icon: "location_on"
text: qsTr("Address: %1").arg(Hyprland.activeClient.address)
color: Colours.palette.m3primary
}
Detail {
icon: "location_searching"
text: qsTr("Position: %1, %2").arg(Hyprland.activeClient.x).arg(Hyprland.activeClient.y)
}
Detail {
icon: "resize"
text: qsTr("Size: %1 x %2").arg(Hyprland.activeClient.width).arg(Hyprland.activeClient.height)
color: Colours.palette.m3tertiary
}
Detail {
icon: "workspaces"
text: qsTr("Workspace: %1 (%2)").arg(Hyprland.activeClient.workspace.name).arg(Hyprland.activeClient.workspace.id)
color: Colours.palette.m3secondary
}
Detail {
icon: "desktop_windows"
text: {
const mon = Hyprland.activeClient.monitor;
return qsTr("Monitor: %1 (%2) at %3, %4").arg(mon.name).arg(mon.id).arg(mon.x).arg(mon.y);
}
}
Detail {
icon: "page_header"
text: qsTr("Initial title: %1").arg(Hyprland.activeClient.initialTitle)
color: Colours.palette.m3tertiary
}
Detail {
icon: "category"
text: qsTr("Initial class: %1").arg(Hyprland.activeClient.initialClass)
}
Detail {
icon: "account_tree"
text: qsTr("Process id: %1").arg(Hyprland.activeClient.pid)
color: Colours.palette.m3primary
}
Detail {
icon: "picture_in_picture_center"
text: qsTr("Floating: %1").arg(Hyprland.activeClient.floating ? "yes" : "no")
color: Colours.palette.m3secondary
}
Detail {
icon: "gradient"
text: qsTr("Xwayland: %1").arg(Hyprland.activeClient.lastIpcObject.xwayland ? "yes" : "no")
}
Detail {
icon: "keep"
text: qsTr("Pinned: %1").arg(Hyprland.activeClient.pinned ? "yes" : "no")
color: Colours.palette.m3secondary
}
Detail {
icon: "fullscreen"
text: {
const fs = Hyprland.activeClient.fullscreen;
return qsTr("Fullscreen state: %1").arg(fs == 0 ? "off" : fs == 1 ? "maximised" : "on");
}
color: Colours.palette.m3tertiary
}
Item {
Layout.fillHeight: true
}
component Detail: RowLayout {
id: detail
required property string icon
required property string text
property alias color: icon.color
Layout.leftMargin: Appearance.padding.large
Layout.rightMargin: Appearance.padding.large
Layout.fillWidth: true
spacing: Appearance.spacing.normal
MaterialIcon {
id: icon
Layout.alignment: Qt.AlignVCenter
text: detail.icon
}
StyledText {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: detail.text
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
}
}
component Label: StyledText {
Layout.leftMargin: Appearance.padding.large
Layout.rightMargin: Appearance.padding.large
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
animate: true
}
}

View file

@ -0,0 +1,65 @@
pragma ComponentBehavior: Bound
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
Item {
id: root
required property ShellScreen screen
Layout.preferredWidth: preview.implicitWidth + Appearance.padding.large * 2
Layout.fillHeight: true
ClippingRectangle {
id: preview
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.bottom: label.top
anchors.topMargin: Appearance.padding.large
anchors.bottomMargin: Appearance.spacing.normal
implicitWidth: view.implicitWidth
color: "transparent"
radius: Appearance.rounding.small
ScreencopyView {
id: view
anchors.centerIn: parent
captureSource: Hyprland.activeClient ? ToplevelManager.activeToplevel : null
live: true
constraintSize.width: parent.height * Math.min(screen.width / screen.height, Hyprland.activeClient.width / Hyprland.activeClient.height)
constraintSize.height: parent.height
}
}
StyledText {
id: label
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.large
animate: true
text: {
const client = Hyprland.activeClient;
if (!client)
return qsTr("No active client");
const mon = Hyprland.monitors.values[Hyprland.activeClient.lastIpcObject.monitor];
return qsTr("%1 on monitor %2 at %3, %4").arg(client.title).arg(mon.name).arg(client.x).arg(client.y);
}
}
}

View file

@ -0,0 +1,45 @@
import "root:/widgets"
import "root:/services"
import "root:/config"
import Quickshell
import QtQuick
import QtQuick.Layouts
Item {
id: root
required property ShellScreen screen
implicitWidth: child.implicitWidth
implicitHeight: screen.height * Config.winfo.sizes.heightMult
RowLayout {
id: child
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
Preview {
screen: root.screen
}
ColumnLayout {
spacing: Appearance.spacing.normal
Layout.preferredWidth: Config.winfo.sizes.detailsWidth
Layout.fillHeight: true
StyledRect {
Layout.fillWidth: true
Layout.fillHeight: true
color: Colours.palette.m3surfaceContainer
radius: Appearance.rounding.normal
Details {}
}
}
}
}

View file

@ -99,6 +99,7 @@ Singleton {
readonly property int width: lastIpcObject.size[0]
readonly property int height: lastIpcObject.size[1]
readonly property HyprlandWorkspace workspace: Hyprland.workspaces.values.find(w => w.id === lastIpcObject.workspace.id) ?? null
readonly property HyprlandMonitor monitor: Hyprland.monitors.values.find(m => m.id === lastIpcObject.monitor) ?? null
readonly property bool floating: lastIpcObject.floating
readonly property bool fullscreen: lastIpcObject.fullscreen
readonly property int pid: lastIpcObject.pid

View file

@ -1,3 +1,5 @@
//@ pragma Env QS_NO_RELOAD_POPUP=1
import "modules"
import "modules/drawers"
import "modules/background"