This commit is contained in:
Zacharias-Brohn
2025-12-07 02:16:33 +01:00
parent ef3791b0d4
commit 0f3f4a981f
12 changed files with 1173 additions and 183 deletions
+1
View File
@@ -51,5 +51,6 @@ JsonObject {
property bool audio: true property bool audio: true
property bool activeWindow: false property bool activeWindow: false
property bool resources: true property bool resources: true
property bool clock: true
} }
} }
+99
View File
@@ -0,0 +1,99 @@
pragma Singleton
import Quickshell
import qs.Helpers
Singleton {
id: root
property int displayMonth: new Date().getMonth()
property int displayYear: new Date().getFullYear()
readonly property int weekStartDay: 1 // 0 = Sunday, 1 = Monday
function getWeeksForMonth(month: int, year: int): var {
const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0);
const days = [];
let currentDate = new Date(year, month, 1);
// Back up to the start of the first week (Sunday or Monday based on locale)
const dayOfWeek = firstDayOfMonth.getDay();
const daysToBackup = (dayOfWeek - root.weekStartDay + 7) % 7;
currentDate.setDate(currentDate.getDate() - daysToBackup);
// Collect all days, including padding from previous/next month to complete the weeks
while (true) {
days.push({
day: currentDate.getDate(),
month: currentDate.getMonth(),
year: currentDate.getFullYear(),
isCurrentMonth: currentDate.getMonth() === month,
isToday: isDateToday(currentDate)
});
currentDate.setDate(currentDate.getDate() + 1);
// Stop after we've completed a full week following the last day of the month
if (currentDate > lastDayOfMonth && days.length % 7 === 0) {
break;
}
}
return days;
}
function getWeekNumbers(month: int, year: int): var {
const days = getWeeksForMonth(month, year);
const weekNumbers = [];
let lastWeekNumber = -1;
for (let i = 0; i < days.length; i++) {
// Only add week numbers for days that belong to the current month
if (days[i].isCurrentMonth) {
const dayDate = new Date(days[i].year, days[i].month, days[i].day);
const weekNumber = getISOWeekNumber(dayDate);
// Only push if this is a new week
if (weekNumber !== lastWeekNumber) {
weekNumbers.push(weekNumber);
lastWeekNumber = weekNumber;
}
}
}
return weekNumbers;
}
function getISOWeekNumber(date: var): int {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}
function isDateToday(date: var): bool {
const today = new Date();
return date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear();
}
function getWeekStartIndex(month: int, year: int): int {
const today = new Date();
if (today.getMonth() !== month || today.getFullYear() !== year) {
return 0;
}
const days = getWeeksForMonth(month, year);
for (let i = 0; i < days.length; i++) {
if (days[i].isToday) {
// Return the start index of the week containing today
return Math.floor(i / 7) * 7;
}
}
return 0;
}
}
+420
View File
@@ -0,0 +1,420 @@
import Quickshell.Io
JsonObject {
property list<var> week_0: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_1: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_2: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_3: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_4: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_5: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_6: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_7: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_8: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_9: [
0,
0,
0,
0,
0,
0,
]
property list<var> week_10:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_11:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_12:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_13:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_14:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_15:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_16:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_17:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_18:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_19:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_20:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_21:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_22:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_23:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_24:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_25:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_26:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_27:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_28:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_29:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_30:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_31:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_32:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_33:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_34:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_35:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_36:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_37:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_38:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_39:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_40:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_41:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_42:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_43:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_44:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_45:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_46:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_47:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_48:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_49:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_50:[
0,
0,
0,
0,
0,
0,
]
property list<var> week_51:[
0,
0,
0,
0,
0,
0,
]
}
+6
View File
@@ -48,6 +48,12 @@ RowLayout {
} else { } else {
popouts.hasCurrent = false; popouts.hasCurrent = false;
} }
} else if ( id === "clock" && Config.barConfig.popouts.clock ) {
Calendar.displayYear = new Date().getFullYear();
Calendar.displayMonth = new Date().getMonth();
popouts.currentName = "calendar";
popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
popouts.hasCurrent = true;
} }
} }
+73
View File
@@ -0,0 +1,73 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
RowLayout {
spacing: 12
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
color: "transparent"
radius: 1000
CustomText {
text: "◀"
font.pointSize: 12
color: DynamicColors.palette.m3onSurface
anchors.centerIn: parent
}
StateLayer {
onClicked: {
if (Calendar.displayMonth === 0) {
Calendar.displayMonth = 11;
Calendar.displayYear -= 1;
} else {
Calendar.displayMonth -= 1;
}
}
}
}
CustomText {
text: new Date(Calendar.displayYear, Calendar.displayMonth, 1).toLocaleDateString(
Qt.locale(),
"MMMM yyyy"
)
font.weight: 600
font.pointSize: 14
color: DynamicColors.palette.m3onSurface
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
}
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
color: "transparent"
radius: 1000
CustomText {
text: "▶"
font.pointSize: 12
color: DynamicColors.palette.m3onSurface
anchors.centerIn: parent
}
StateLayer {
onClicked: {
if (Calendar.displayMonth === 11) {
Calendar.displayMonth = 0;
Calendar.displayYear += 1;
} else {
Calendar.displayMonth += 1;
}
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
required property Item wrapper
implicitWidth: layout.childrenRect.width + layout.anchors.margins * 2
implicitHeight: layout.childrenRect.height + layout.anchors.margins * 2
ColumnLayout {
id: layout
anchors.centerIn: parent
anchors.margins: 16
spacing: 16
// Header with month/year and navigation
CalendarHeader {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
}
// Calendar grid
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
spacing: 12
ColumnLayout {
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: childrenRect.height
Layout.preferredWidth: weekNumberColumn.width
spacing: 8
Item {
Layout.preferredHeight: dayOfWeekRow.height
}
WeekNumberColumn {
id: weekNumberColumn
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: weekNumbers.values.length * 44
}
}
ColumnLayout {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
spacing: 8
DayOfWeekRow {
id: dayOfWeekRow
locale: Qt.locale()
Layout.fillWidth: true
Layout.preferredHeight: 30
}
MonthGrid {
locale: Qt.locale()
wrapper: root.wrapper
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: childrenRect.height
}
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
RowLayout {
id: root
required property var locale
spacing: 4
Repeater {
model: 7
Item {
required property int index
Layout.fillWidth: true
Layout.preferredHeight: 30
readonly property string dayName: {
// Get the day name for this column
const dayIndex = (index + Calendar.weekStartDay) % 7;
return root.locale.dayName(dayIndex, Locale.ShortFormat);
}
CustomText {
anchors.centerIn: parent
text: parent.dayName
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: 0.8
font.weight: 500
font.pointSize: 11
}
}
}
}
+117
View File
@@ -0,0 +1,117 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
GridLayout {
id: root
required property var locale
required property Item wrapper
columns: 7
rowSpacing: 4
columnSpacing: 4
uniformCellWidths: true
uniformCellHeights: true
component Anim: NumberAnimation {
target: root
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Repeater {
id: repeater
model: ScriptModel {
values: Calendar.getWeeksForMonth(Calendar.displayMonth, Calendar.displayYear)
Behavior on values {
SequentialAnimation {
id: switchAnim
ParallelAnimation {
Anim {
property: "opacity"
from: 1.0
to: 0.0
}
Anim {
property: "scale"
from: 1.0
to: 0.8
}
}
PropertyAction {}
ParallelAnimation {
Anim {
property: "opacity"
from: 0.0
to: 1.0
}
Anim {
property: "scale"
from: 0.8
to: 1.0
}
}
}
}
}
Rectangle {
required property var modelData
required property int index
Layout.preferredWidth: 40
Layout.preferredHeight: width
radius: 1000
color: {
if (modelData.isToday) {
console.log(width);
return DynamicColors.palette.m3primaryContainer;
}
return "transparent";
}
Behavior on color {
ColorAnimation { duration: 200 }
}
CustomText {
anchors.centerIn: parent
text: parent.modelData.day.toString()
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: parent.modelData.isCurrentMonth ? 1.0 : 0.4
color: {
if (parent.modelData.isToday) {
return DynamicColors.palette.m3onPrimaryContainer;
}
return DynamicColors.palette.m3onSurface;
}
Behavior on color {
ColorAnimation { duration: 200 }
}
Behavior on opacity {
NumberAnimation { duration: 200 }
}
}
StateLayer {
color: DynamicColors.palette.m3onSurface
onClicked: {
console.log(`Selected date: ${parent.modelData.day}/${parent.modelData.month + 1}/${parent.modelData.year}`);
}
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
ColumnLayout {
id: root
spacing: 4
readonly property var weekNumbers: Calendar.getWeekNumbers(Calendar.displayMonth, Calendar.displayYear)
Repeater {
model: ScriptModel {
values: root.weekNumbers
}
Item {
id: weekItem
Layout.preferredHeight: 40
Layout.preferredWidth: 20
Layout.alignment: Qt.AlignHCenter
required property int index
required property var modelData
CustomText {
id: weekText
anchors.centerIn: parent
text: weekItem.modelData
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: 0.5
font.pointSize: 10
}
}
}
}
+8
View File
@@ -4,6 +4,7 @@ import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import QtQuick import QtQuick
import qs.Config import qs.Config
import qs.Modules.Calendar
Item { Item {
id: root id: root
@@ -69,6 +70,13 @@ Item {
} }
} }
} }
Popout {
name: "calendar"
sourceComponent: CalendarPopup {
wrapper: root.wrapper
}
}
} }
component Popout: Loader { component Popout: Loader {
+2 -6
View File
@@ -62,7 +62,6 @@ WlSessionLockSurface {
onTextChanged: text = "" onTextChanged: text = ""
} }
ScreencopyView { ScreencopyView {
id: background id: background
@@ -84,14 +83,11 @@ WlSessionLockSurface {
} }
} }
Image { CachingImage {
id: backgroundImage id: backgroundImage
anchors.fill: parent anchors.fill: parent
asynchronous: false asynchronous: false
cache: false path: WallpaperPath.currentWallpaperPath
source: WallpaperPath.currentWallpaperPath
sourceSize.width: root.screen.width
sourceSize.height: root.screen.height
visible: Config.lock.useWallpaper visible: Config.lock.useWallpaper
Component.onCompleted: { Component.onCompleted: {
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""
Calendar cache generator for z-bar-qt
Generates a JSON file containing date numbers for all 52 weeks of 3 years (last, current, next)
Structure: { "2024": { "week_0": [1,2,3,4,5,6,7], ... }, "2025": {...}, ... }
"""
import json
import sys
from datetime import datetime, timedelta
from pathlib import Path
def get_week_start_day():
"""Returns the first day of the week (0=Sunday, 1=Monday, etc.) - hardcoded to Monday"""
return 1 # Monday
def get_weeks_for_year(year, week_start_day=1):
"""
Generate week data for a given year.
Returns a dict with 52 weeks, each containing 7 date numbers.
"""
weeks = {}
# Find the first day of the year
jan_1 = datetime(year, 1, 1)
# Find the first week's start date (adjust based on week_start_day)
first_date = jan_1 - timedelta(days=(jan_1.weekday() - week_start_day) % 7)
# Generate 52 weeks
for week_num in range(52):
week_dates = []
week_start = first_date + timedelta(weeks=week_num)
for day_offset in range(7):
current_date = week_start + timedelta(days=day_offset)
week_dates.append(current_date.day)
weeks[f"week_{week_num}"] = week_dates
return weeks
def generate_calendar_cache(year=None):
"""Generate cache for last year, current year, and next year"""
if year is None:
year = datetime.now().year
cache = {}
for offset_year in [-1, 0, 1]:
target_year = year + offset_year
cache[str(target_year)] = get_weeks_for_year(target_year)
return cache
def write_cache_file(cache_data):
"""Write cache to the same location as Paths.cache in QML"""
import os
# Use XDG_CACHE_HOME or ~/.cache, then add /zshell (matching Paths singleton)
xdg_cache_home = os.environ.get("XDG_CACHE_HOME")
if xdg_cache_home:
cache_dir = Path(xdg_cache_home) / "zshell"
else:
cache_dir = Path.home() / ".cache" / "zshell"
cache_dir.mkdir(parents=True, exist_ok=True)
cache_file = cache_dir / "calendar_cache.json"
with open(cache_file, "w") as f:
json.dump(cache_data, f, indent=2)
print(f"Calendar cache written to: {cache_file}")
return cache_file
def main():
try:
# Generate cache for current year and ±1 year
cache = generate_calendar_cache()
# Write to file
cache_file = write_cache_file(cache)
print("Cache structure:")
print(" - Keys: year (e.g., '2024', '2025', '2026')")
print(" - Values: dict with 52 weeks")
print(" - Each week: array of 7 date numbers")
print(f"\nExample (first week of 2025):")
print(f" {cache['2025']['week_0']}")
return 0
except Exception as e:
print(f"Error generating calendar cache: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())