Add Ongoing to the Panama Daybook
This commit is contained in:
@@ -20,7 +20,8 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
|
||||
| Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live |
|
||||
| Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live |
|
||||
| Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live |
|
||||
| Date menu | Two-column Daybook with EDS agenda, notifications, month calendar, weather, and media | Live |
|
||||
| Date menu | Two-column Daybook with Agenda, Ongoing activity, Notifications, month calendar, weather, and media | Live |
|
||||
| Ongoing activity | Focus, Caffeine, recording, screen sharing, camera, and microphone state with direct controls | Live |
|
||||
| Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live |
|
||||
| Quick settings | Wi-Fi, Bluetooth, audio input/output, caffeine, night light, focus | Live |
|
||||
| Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live |
|
||||
|
||||
@@ -191,8 +191,16 @@ Settings remains searchable in Vicinae for hardware and account panels.
|
||||
|
||||
Click the clock to open the two-column Daybook on **Agenda**. The month grid,
|
||||
today's schedule, Up Next, weather, and conditional media controls stay visible
|
||||
together. `SUPER + B` opens **Notifications** in that same surface; returning to
|
||||
Agenda preserves the selected date and visible month.
|
||||
together. Its right pane has three first-class pages:
|
||||
|
||||
- **Agenda** — the selected day's calendar.
|
||||
- **Ongoing** — Focus, Caffeine, recording, screen sharing, camera, and
|
||||
microphone activity, visible until each state ends.
|
||||
- **Notifications** — grouped history, Do Not Disturb, and clear actions.
|
||||
|
||||
`SUPER + B` opens Notifications directly. Switching pages preserves the
|
||||
selected date and visible month. Brief completed events such as screenshots and
|
||||
device changes remain in Signal Glass instead of accumulating in Ongoing.
|
||||
|
||||
Calendar data comes from Evolution Data Server, so the Google, iCloud,
|
||||
Nextcloud, and local calendars already configured for GNOME remain the source
|
||||
|
||||
@@ -7,8 +7,6 @@ import qs.modules.quicksettings
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal requestNotifications
|
||||
|
||||
readonly property bool isToday: CalendarAgenda.selectedDateKey === CalendarAgenda.dateKey(clock.date)
|
||||
readonly property var upNext: CalendarAgenda.nextEventForSelectedDate
|
||||
readonly property var listedEvents: CalendarAgenda.selectedEvents.filter(event => !root.upNext || event.id !== root.upNext.id)
|
||||
@@ -238,10 +236,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NotificationSummary {
|
||||
width: parent.width
|
||||
onActivated: root.requestNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
function relativeStart(event: var): string {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// GNOME's date/time menu: the calendar and the message tray in one panel,
|
||||
// GNOME's date/time menu: calendar, ongoing activity and messages in one panel,
|
||||
// dropping from the top centre because that is where GNOME put it and the
|
||||
// muscle memory is the whole point.
|
||||
//
|
||||
@@ -20,8 +20,10 @@ import qs.widgets
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
// "notifications" is the existing overlay key, so Super+B, the notification
|
||||
// IPC target and the clock all land here.
|
||||
readonly property bool ongoingContentFits: panel.ongoingContentFits
|
||||
|
||||
// "notifications" remains the overlay key so existing keybinds keep their
|
||||
// contract; the surface itself now has Agenda, Ongoing and Notifications.
|
||||
visible: ShellState.notificationsOpen
|
||||
color: "transparent"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// The approved A · Daybook: month context on the left, the selected day's
|
||||
// schedule or grouped notification history on the right.
|
||||
// schedule, persistent activity or grouped notification history on the right.
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
@@ -46,7 +46,7 @@ Item {
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 4
|
||||
anchors.right: headerActions.left
|
||||
anchors.right: dateMenuTabs.left
|
||||
anchors.rightMargin: Theme.itemSpacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Qt.formatDateTime(clock.date, "dddd, MMMM d")
|
||||
@@ -59,35 +59,36 @@ Item {
|
||||
}
|
||||
|
||||
Row {
|
||||
id: headerActions
|
||||
id: dateMenuTabs
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 6
|
||||
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "notifications"
|
||||
text: "← Back to agenda"
|
||||
text: "Agenda"
|
||||
active: ShellState.dateMenuPage === "agenda"
|
||||
onClicked: ShellState.dateMenuPage = "agenda"
|
||||
}
|
||||
|
||||
HeaderPill {
|
||||
text: "Do Not Disturb"
|
||||
active: Notifs.doNotDisturb
|
||||
icon: "notifications-disabled-symbolic"
|
||||
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
|
||||
text: "Ongoing"
|
||||
active: ShellState.dateMenuPage === "ongoing"
|
||||
badge: Ongoing.count
|
||||
onClicked: ShellState.dateMenuPage = "ongoing"
|
||||
}
|
||||
|
||||
HeaderPill {
|
||||
text: "Notifications"
|
||||
active: ShellState.dateMenuPage === "notifications"
|
||||
badge: Notifs.history.length
|
||||
onClicked: ShellState.dateMenuPage = "notifications"
|
||||
}
|
||||
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "agenda"
|
||||
text: "Open Calendar ↗"
|
||||
text: "Calendar ↗"
|
||||
onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate)
|
||||
}
|
||||
|
||||
HeaderPill {
|
||||
visible: ShellState.dateMenuPage === "notifications" && Notifs.hasNotifications
|
||||
text: "Clear all"
|
||||
onClicked: Notifs.dismissAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,10 +166,16 @@ Item {
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 14
|
||||
visible: ShellState.dateMenuPage === "agenda"
|
||||
onRequestNotifications: {
|
||||
ShellState.dateMenuPage = "notifications";
|
||||
Notifs.markAllRead();
|
||||
}
|
||||
}
|
||||
|
||||
OngoingPane {
|
||||
id: ongoingPane
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 14
|
||||
maxHeight: body.height - 28
|
||||
visible: ShellState.dateMenuPage === "ongoing"
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -186,6 +193,8 @@ Item {
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.right: notificationActions.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Notifications"
|
||||
color: Theme.fg
|
||||
@@ -194,14 +203,24 @@ Item {
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Text {
|
||||
Row {
|
||||
id: notificationActions
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Notifs.history.length + (Notifs.history.length === 1 ? " item" : " items")
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
spacing: 6
|
||||
|
||||
HeaderPill {
|
||||
text: "Do Not Disturb"
|
||||
active: Notifs.doNotDisturb
|
||||
icon: "notifications-disabled-symbolic"
|
||||
onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb
|
||||
}
|
||||
|
||||
HeaderPill {
|
||||
visible: Notifs.hasNotifications
|
||||
text: "Clear all"
|
||||
onClicked: Notifs.dismissAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,9 +234,14 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real rightContentHeight: ShellState.dateMenuPage === "notifications"
|
||||
? notificationPane.implicitHeight
|
||||
: agenda.implicitHeight
|
||||
readonly property real rightContentHeight: {
|
||||
if (ShellState.dateMenuPage === "notifications")
|
||||
return notificationPane.implicitHeight;
|
||||
if (ShellState.dateMenuPage === "ongoing")
|
||||
return ongoingPane.implicitHeight;
|
||||
return agenda.implicitHeight;
|
||||
}
|
||||
readonly property bool ongoingContentFits: ongoingPane.implicitHeight <= body.height - 28
|
||||
|
||||
component HeaderPill: Rectangle {
|
||||
id: pill
|
||||
@@ -225,6 +249,7 @@ Item {
|
||||
required property string text
|
||||
property string icon: ""
|
||||
property bool active: false
|
||||
property int badge: -1
|
||||
signal clicked
|
||||
|
||||
width: pillRow.implicitWidth + 20
|
||||
@@ -260,6 +285,27 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: pill.badge > 0
|
||||
width: visible ? badgeText.implicitWidth + 9 : 0
|
||||
height: 17
|
||||
radius: Theme.pillRadius
|
||||
border.width: 0
|
||||
color: Theme.alpha(pill.active ? Theme.accent : Theme.fg, 0.12)
|
||||
|
||||
Text {
|
||||
id: badgeText
|
||||
anchors.centerIn: parent
|
||||
text: String(pill.badge)
|
||||
color: pill.active ? Theme.accent : Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: 9
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
// Persistent work and privacy activity inside the Daybook.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.modules.quicksettings
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real maxHeight: 500
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: 12
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 38
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: "PERSISTENT ACTIVITY"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: 0.9
|
||||
}
|
||||
Text {
|
||||
text: "Ongoing"
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
text: Ongoing.count === 0 ? "Nothing active" : Ongoing.count + (Ongoing.count === 1 ? " activity" : " activities")
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: visible ? 190 : 0
|
||||
visible: Ongoing.count === 0
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "\u{F04A5}"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 24
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "Nothing ongoing"
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "Focus, Caffeine, recording and privacy activity appear here."
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrollColumn {
|
||||
width: parent.width
|
||||
// Header + footer + the two 12px Column gaps consume 116px.
|
||||
// Keeping that outside the scroll budget prevents the pane from
|
||||
// growing beyond the Daybook when every producer is active.
|
||||
maxHeight: Math.max(0, root.maxHeight - 116)
|
||||
spacing: 8
|
||||
visible: Ongoing.count > 0
|
||||
|
||||
Repeater {
|
||||
model: Ongoing.activities
|
||||
|
||||
Rectangle {
|
||||
id: activityRow
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
height: 62
|
||||
radius: Theme.cardRadius
|
||||
border.width: 0
|
||||
color: rowMouse.containsMouse
|
||||
? Theme.alpha(Theme.fg, 0.10)
|
||||
: Theme.alpha(Theme.fg, 0.055)
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||
|
||||
Text {
|
||||
id: activityIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 13
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: activityRow.modelData.glyph
|
||||
color: root.toneColor(activityRow.modelData.tone)
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 17
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: activityIcon.right
|
||||
anchors.leftMargin: 11
|
||||
anchors.right: activityAction.visible ? activityAction.left : activityState.left
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: activityRow.modelData.label
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: activityRow.modelData.detail
|
||||
color: Theme.fgDim
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: activityState
|
||||
anchors.right: activityAction.visible ? activityAction.left : parent.right
|
||||
anchors.rightMargin: activityAction.visible ? 7 : 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: stateLabel.implicitWidth + 16
|
||||
height: 25
|
||||
radius: Theme.pillRadius
|
||||
border.width: 0
|
||||
color: Theme.alpha(root.toneColor(activityRow.modelData.tone), 0.12)
|
||||
|
||||
Text {
|
||||
id: stateLabel
|
||||
anchors.centerIn: parent
|
||||
text: activityRow.modelData.state
|
||||
color: root.toneColor(activityRow.modelData.tone)
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: activityAction
|
||||
z: 2
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 9
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: activityRow.modelData.action !== ""
|
||||
width: visible ? actionLabel.implicitWidth + 18 : 0
|
||||
height: 28
|
||||
radius: Theme.pillRadius
|
||||
border.width: 0
|
||||
color: actionMouse.containsMouse
|
||||
? Theme.alpha(activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg, 0.20)
|
||||
: Theme.alpha(activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg, 0.08)
|
||||
|
||||
Text {
|
||||
id: actionLabel
|
||||
anchors.centerIn: parent
|
||||
text: activityRow.modelData.action
|
||||
color: activityRow.modelData.tone === "danger" ? Theme.danger : Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: actionMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Ongoing.invoke(activityRow.modelData.kind)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: rowMouse
|
||||
anchors.fill: parent
|
||||
z: 1
|
||||
enabled: activityRow.modelData.kind !== "caffeine"
|
||||
hoverEnabled: enabled
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: Ongoing.activate(activityRow.modelData.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 54
|
||||
radius: Theme.cardRadius
|
||||
border.width: 0
|
||||
visible: Ongoing.count > 0
|
||||
color: Theme.alpha(Theme.fg, 0.035)
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 13
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
Text {
|
||||
text: "Visible until it ends"
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
text: "Completed screenshots, devices and other brief events stay in Signal Glass."
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toneColor(tone: string): color {
|
||||
if (tone === "danger")
|
||||
return Theme.danger;
|
||||
if (tone === "warn")
|
||||
return Theme.warn;
|
||||
if (tone === "ok")
|
||||
return Theme.ok;
|
||||
return Theme.accent;
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,5 @@ MonthArrow 1.0 MonthArrow.qml
|
||||
NotificationGroup 1.0 NotificationGroup.qml
|
||||
NotificationList 1.0 NotificationList.qml
|
||||
NotificationSummary 1.0 NotificationSummary.qml
|
||||
OngoingPane 1.0 OngoingPane.qml
|
||||
WeatherCard 1.0 WeatherCard.qml
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
pragma Singleton
|
||||
|
||||
// One presentation model for state that remains active until it is explicitly
|
||||
// stopped or naturally completes. Transient confirmations stay in Signal Glass.
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property var activities: {
|
||||
const result = [];
|
||||
|
||||
if (FocusSession.active) {
|
||||
result.push({
|
||||
kind: "focus",
|
||||
glyph: "\u{F051F}",
|
||||
label: "Focus session",
|
||||
detail: (FocusSession.workspaceLabel || "Focused workspace") + " · " + FocusSession.statusText,
|
||||
state: FocusSession.remainingText,
|
||||
tone: FocusSession.paused ? "warn" : "accent",
|
||||
action: FocusSession.paused ? "Resume" : "Pause"
|
||||
});
|
||||
}
|
||||
|
||||
if (Caffeine.enabled) {
|
||||
result.push({
|
||||
kind: "caffeine",
|
||||
glyph: "\u{F0F2E}",
|
||||
label: "Caffeine",
|
||||
detail: "Automatic sleep is blocked",
|
||||
state: "On",
|
||||
tone: "ok",
|
||||
action: "Turn off"
|
||||
});
|
||||
}
|
||||
|
||||
if (PrivacyState.recordingActive) {
|
||||
result.push({
|
||||
kind: "recording",
|
||||
glyph: "\u{F044A}",
|
||||
label: "Screen recording",
|
||||
detail: "Panama · " + root.recordingElapsed,
|
||||
state: "Live",
|
||||
tone: "danger",
|
||||
action: "Stop"
|
||||
});
|
||||
}
|
||||
if (PrivacyState.screenSharingActive)
|
||||
result.push(root.privacyActivity("screen", "\u{F0379}", "Screen sharing", PrivacyState.screenSharingApp));
|
||||
if (PrivacyState.cameraActive)
|
||||
result.push(root.privacyActivity("camera", "\u{F0100}", "Camera", PrivacyState.cameraApp));
|
||||
if (PrivacyState.microphoneActive)
|
||||
result.push(root.privacyActivity("microphone", "\u{F036C}", "Microphone", PrivacyState.microphoneApp));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly property int count: activities.length
|
||||
readonly property string recordingElapsed: {
|
||||
const total = Capture.recordingSeconds;
|
||||
const seconds = String(total % 60).padStart(2, "0");
|
||||
const minutes = Math.floor(total / 60) % 60;
|
||||
const hours = Math.floor(total / 3600);
|
||||
return hours > 0
|
||||
? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}`
|
||||
: `${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function privacyActivity(kind: string, glyph: string, label: string, app: string): var {
|
||||
return {
|
||||
kind,
|
||||
glyph,
|
||||
label,
|
||||
detail: app || "Managed by the application",
|
||||
state: "In use",
|
||||
tone: "warn",
|
||||
action: ""
|
||||
};
|
||||
}
|
||||
|
||||
function activate(kind: string): void {
|
||||
if (kind === "focus")
|
||||
FocusSession.activateWorkspace();
|
||||
else if (["recording", "screen", "camera", "microphone"].indexOf(kind) >= 0)
|
||||
ShellState.open("activity");
|
||||
}
|
||||
|
||||
function invoke(kind: string): void {
|
||||
if (kind === "focus")
|
||||
FocusSession.pauseOrResume();
|
||||
else if (kind === "caffeine")
|
||||
Caffeine.toggle();
|
||||
else if (kind === "recording")
|
||||
Capture.stopRecording();
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,8 @@ Singleton {
|
||||
root.applyStates(false, true, false, "", "Contract fixture", "", true);
|
||||
else if (name === "screen-on")
|
||||
root.applyStates(false, false, true, "", "", "Contract fixture", true);
|
||||
else if (name === "all-on")
|
||||
root.applyStates(true, true, true, "Contract fixture", "Contract fixture", "Contract fixture", true);
|
||||
else if (name === "all-off")
|
||||
root.applyStates(false, false, false, "", "", "", true);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ Singleton {
|
||||
|
||||
readonly property bool anyOverlayOpen: activeOverlay !== ""
|
||||
|
||||
// The date menu is one surface with two deliberate entry points. The clock
|
||||
// opens the Daybook; Super+B and notification actions open message history.
|
||||
// The date menu keeps calendar context fixed while its right pane switches
|
||||
// between the schedule, persistent activity and message history.
|
||||
property string dateMenuPage: "agenda"
|
||||
|
||||
// 0 means "use the currently focused workspace". A positive value lets a
|
||||
@@ -53,12 +53,12 @@ Singleton {
|
||||
}
|
||||
|
||||
function openDateMenu(page: string): void {
|
||||
root.dateMenuPage = page === "notifications" ? "notifications" : "agenda";
|
||||
root.dateMenuPage = root.normalizedDateMenuPage(page);
|
||||
root.activeOverlay = "notifications";
|
||||
}
|
||||
|
||||
function toggleDateMenu(page: string): void {
|
||||
const target = page === "notifications" ? "notifications" : "agenda";
|
||||
const target = root.normalizedDateMenuPage(page);
|
||||
if (root.activeOverlay === "notifications" && root.dateMenuPage === target) {
|
||||
root.activeOverlay = "";
|
||||
return;
|
||||
@@ -67,6 +67,10 @@ Singleton {
|
||||
root.activeOverlay = "notifications";
|
||||
}
|
||||
|
||||
function normalizedDateMenuPage(page: string): string {
|
||||
return ["agenda", "ongoing", "notifications"].indexOf(page) >= 0 ? page : "agenda";
|
||||
}
|
||||
|
||||
function openOverview(workspaceId: int): void {
|
||||
root.overviewWorkspaceId = Math.max(0, workspaceId);
|
||||
root.overviewQuery = "";
|
||||
|
||||
@@ -79,7 +79,7 @@ ShellRoot {
|
||||
Overview {}
|
||||
// QuickSettings is the window; QuickSettingsPanel is its content.
|
||||
QuickSettings {}
|
||||
DateMenu {}
|
||||
DateMenu { id: dateMenu }
|
||||
ClipboardPanel {}
|
||||
CaptureOverlay {}
|
||||
IntelligenceResult {}
|
||||
@@ -268,6 +268,7 @@ ShellRoot {
|
||||
function reset(): void { CalendarAgenda.clearFixture(); }
|
||||
function refresh(): void { CalendarAgenda.refresh(); }
|
||||
function open(): void { ShellState.openDateMenu("agenda"); }
|
||||
function page(name: string): void { ShellState.openDateMenu(name); }
|
||||
function close(): void { ShellState.close(); }
|
||||
function select(date: string): void { CalendarAgenda.selectIsoDate(date); }
|
||||
function status(): string {
|
||||
@@ -281,7 +282,9 @@ ShellRoot {
|
||||
selectedDate: CalendarAgenda.selectedDateKey,
|
||||
selectedEventCount: CalendarAgenda.selectedEvents.length,
|
||||
capsuleVisible: CalendarAgenda.capsuleVisible,
|
||||
capsuleText: CalendarAgenda.capsuleText
|
||||
capsuleText: CalendarAgenda.capsuleText,
|
||||
ongoingCount: Ongoing.count,
|
||||
ongoingFits: dateMenu.ongoingContentFits
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ fail() {
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
qs ipc call focus end >/dev/null 2>&1 || true
|
||||
qs ipc call activity clear >/dev/null 2>&1 || true
|
||||
qs ipc call calendar-agenda reset >/dev/null 2>&1 || true
|
||||
qs ipc call calendar-agenda close >/dev/null 2>&1 || true
|
||||
}
|
||||
@@ -40,6 +42,18 @@ jq -e '.open == true and .page == "notifications"' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'notification open did not route to Notifications'
|
||||
|
||||
qs ipc call activity fixture microphone-on >/dev/null
|
||||
qs ipc call calendar-agenda page ongoing >/dev/null
|
||||
jq -e '.open == true and .page == "ongoing" and .ongoingCount >= 1' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'persistent activity did not route to Ongoing'
|
||||
|
||||
qs ipc call activity fixture all-on >/dev/null
|
||||
qs ipc call focus start >/dev/null
|
||||
jq -e '.ongoingCount == 5 and .ongoingFits == true' \
|
||||
<<<"$(qs ipc call calendar-agenda status)" >/dev/null \
|
||||
|| fail 'crowded Ongoing activity does not fit the Daybook'
|
||||
|
||||
hyprctl layers | rg -q 'namespace: qs-popover-datemenu' \
|
||||
|| fail 'Daybook layer did not map'
|
||||
rg -q 'implicitWidth: 760' config/dot/quickshell/modules/datemenu/DateMenu.qml \
|
||||
|
||||
Reference in New Issue
Block a user