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 notifs: adapter.notifs
property alias osd: adapter.osd property alias osd: adapter.osd
property alias session: adapter.session property alias session: adapter.session
property alias winfo: adapter.winfo
property alias paths: adapter.paths property alias paths: adapter.paths
FileView { FileView {
@ -32,6 +33,7 @@ Singleton {
property JsonObject notifs: NotifsConfig {} property JsonObject notifs: NotifsConfig {}
property JsonObject osd: OsdConfig {} property JsonObject osd: OsdConfig {}
property JsonObject session: SessionConfig {} property JsonObject session: SessionConfig {}
property JsonObject winfo: WInfoConfig {}
property JsonObject paths: UserPaths {} 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 { Item {
id: root id: root
required property Item wrapper
implicitWidth: Hyprland.activeClient ? child.implicitWidth : -Appearance.padding.large * 2 implicitWidth: Hyprland.activeClient ? child.implicitWidth : -Appearance.padding.large * 2
implicitHeight: child.implicitHeight implicitHeight: child.implicitHeight
@ -65,7 +67,7 @@ Item {
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
function onClicked(): void { function onClicked(): void {
// TODO root.wrapper.detach("winfo");
} }
} }

View file

@ -13,15 +13,17 @@ ShapePath {
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
property real ibr: invertBottomRounding ? -1 : 1 property real ibr: invertBottomRounding ? -1 : 1
property real sideRounding: startX > 0 ? -1 : 1
strokeWidth: -1 strokeWidth: -1
fillColor: Config.border.colour fillColor: Config.border.colour
PathArc { PathArc {
relativeX: root.roundingX relativeX: root.roundingX
relativeY: root.rounding relativeY: root.rounding * root.sideRounding
radiusX: Math.min(root.rounding, root.wrapper.width) radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding radiusY: root.rounding
direction: PathArc.Counterclockwise direction: root.sideRounding < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
} }
PathLine { PathLine {
relativeX: root.wrapper.width - root.roundingX * 2 relativeX: root.wrapper.width - root.roundingX * 2
@ -50,10 +52,10 @@ ShapePath {
} }
PathArc { PathArc {
relativeX: -root.roundingX relativeX: -root.roundingX
relativeY: root.rounding relativeY: root.rounding * root.sideRounding
radiusX: Math.min(root.rounding, root.wrapper.width) radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding radiusY: root.rounding
direction: PathArc.Counterclockwise direction: root.sideRounding < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
} }
Behavior on fillColor { Behavior on fillColor {
@ -71,4 +73,12 @@ ShapePath {
easing.bezierCurve: Appearance.anim.curves.standard 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 { Item {
id: root id: root
required property Item wrapper
required property ShellScreen screen required property ShellScreen screen
required property string currentName
property string currentName required property real currentCenter
property real currentCenter required property bool hasCurrent
property bool hasCurrent
anchors.centerIn: parent 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 implicitHeight: (content.children.find(c => c.shouldBeActive)?.implicitHeight ?? 0) + Appearance.padding.large * 2
Item { Item {
@ -26,11 +26,11 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: Appearance.padding.large anchors.margins: Appearance.padding.large
clip: true
Popout { Popout {
name: "activewindow" name: "activewindow"
source: "ActiveWindow.qml" sourceComponent: ActiveWindow {
wrapper: root.wrapper
}
} }
Popout { 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 { component Popout: Loader {
id: popout id: popout

View file

@ -1,6 +1,11 @@
pragma ComponentBehavior: Bound
import "root:/services" import "root:/services"
import "root:/config" import "root:/config"
import "root:/modules/windowinfo"
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick import QtQuick
Item { Item {
@ -8,18 +13,168 @@ Item {
required property ShellScreen screen required property ShellScreen screen
property alias currentName: content.currentName readonly property real nonAnimWidth: x > 0 || hasCurrent ? children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth : 0
property alias currentCenter: content.currentCenter readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
property alias hasCurrent: content.hasCurrent
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 visible: width > 0 && height > 0
clip: true
implicitWidth: content.implicitWidth implicitWidth: nonAnimWidth
implicitHeight: content.implicitHeight 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 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 wrapper: panels.popouts
invertBottomRounding: wrapper.y + wrapper.height + 1 >= root.height invertBottomRounding: wrapper.y + wrapper.height + 1 >= root.height
startX: 0 startX: wrapper.x
startY: wrapper.y - rounding startY: wrapper.y - rounding * sideRounding
} }
} }

View file

@ -80,11 +80,13 @@ Item {
screen: root.screen screen: root.screen
anchors.left: parent.left x: isDetached ? (root.width - nonAnimWidth) / 2 : 0
anchors.verticalCenter: parent.top y: {
anchors.verticalCenterOffset: { if (isDetached)
const off = root.popouts.currentCenter - Config.border.thickness; return (root.height - nonAnimHeight) / 2;
const diff = root.height - Math.floor(off + implicitHeight / 2);
const off = currentCenter - Config.border.thickness - nonAnimHeight / 2;
const diff = root.height - Math.floor(off + nonAnimHeight);
if (diff < 0) if (diff < 0)
return off + diff; return off + diff;
return off; 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 width: lastIpcObject.size[0]
readonly property int height: lastIpcObject.size[1] readonly property int height: lastIpcObject.size[1]
readonly property HyprlandWorkspace workspace: Hyprland.workspaces.values.find(w => w.id === lastIpcObject.workspace.id) ?? null 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 floating: lastIpcObject.floating
readonly property bool fullscreen: lastIpcObject.fullscreen readonly property bool fullscreen: lastIpcObject.fullscreen
readonly property int pid: lastIpcObject.pid readonly property int pid: lastIpcObject.pid

View file

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