Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/omarchy
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ GROUP_DESCRIPTIONS[audio]="Audio input and output controls"
GROUP_DESCRIPTIONS[bar]="Omarchy shell bar layout and settings"
GROUP_DESCRIPTIONS[battery]="Battery status helpers"
GROUP_DESCRIPTIONS[bluetooth]="Bluetooth device controls"
GROUP_DESCRIPTIONS[calendar]="Calendar and planning"
GROUP_DESCRIPTIONS[branch]="Omarchy git branch management"
GROUP_DESCRIPTIONS[branding]="About and screensaver branding"
GROUP_DESCRIPTIONS[brightness]="Display and keyboard brightness"
Expand Down
493 changes: 493 additions & 0 deletions bin/omarchy-calendar

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions install/omarchy-base.packages
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ obsidian
omacalc
omacut
omawrite
omarchy-calendar-solver
omarchy-nvim
pacman-contrib
pamixer
Expand Down
11 changes: 10 additions & 1 deletion manual/05-the-top-bar.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,19 @@ The panels aren't read-outs. They're where you actually do the thing:
- **Bluetooth** lists your devices with connect/disconnect and battery levels.
- **Power** shows battery stats, switches power profiles (it remembers a separate choice for battery and AC), and prints some system info.
- **Display** carries a brightness slider, text size, monitor scaling presets, and — when you have more than one screen — per-monitor controls. See [monitors](33-monitors.md) for the deeper story.
- **Clock** opens a month grid with ISO week numbers and month stepping.
- **Clock** opens a month grid with ISO week numbers and month stepping. The same popup also has **Agenda** for manual and applied events and **Plan** for an inbox of tasks and schedule suggestions.

The planner is local to Omarchy. Open **Plan → Settings** once to choose a timezone and at least one weekly availability window. In **Plan**, add a planning task, choose **Plan tasks**, then choose **Review schedule** to see the suggested times. The calendar is unchanged until you choose **Apply schedule**. Applied planner events can be returned to the inbox from their task or agenda entry.

The same planner is available from a terminal through `omarchy calendar`.
`status`, `events`, and `tasks` inspect the shared state; `add-event`,
`edit-event`, `delete-event`, `add-task`, `edit-task`, `delete-task`,
`dependency`, and `settings` manage it; `plan` generates and shows a suggested schedule and `apply` commits it. Add `--json` to any command for scripting, and use `omarchy calendar open agenda` or `omarchy calendar open plan` to open the corresponding view in this popup. The CLI and the popup use the same service, revision checks, and atomic state file.

Every panel takes the keyboard as well as the mouse: arrows move, Return activates, Tab steps to the neighbouring panel, and Escape closes.

Inside the Clock popup, `[` and `]` step months, `{` and `}` step years, `t` returns to today, and `w` toggles the week start. Press `a` for Agenda or `p` for Plan. Escape goes back one view before closing the popup.

`Super + Ctrl + 1-9` counts panels left to right in the right section, skipping the tray since it has no panel of its own. So the number matches the icon you'd point at.

### Tailscale and Dropbox
Expand Down
20 changes: 20 additions & 0 deletions manual/14-omarchy-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ Capture commands — Screenshots and screen recording:

Every command takes `--help` too, whether you ask a whole group (`omarchy capture --help`) or a single command (`omarchy capture screenshot --help`).

### Calendar and planning

The clock popup's local calendar and planner are also available through the
`calendar` group. It talks to the running Omarchy shell, so the terminal and
the popup always operate on the same versioned state:

```bash
omarchy calendar status
omarchy calendar add-event --title "Dentist" \\
--start 2026-09-07T10:00:00+02:00 --end 2026-09-07T11:00:00+02:00 \\
--timezone Europe/Rome
omarchy calendar add-task --title "Write proposal" --duration 90 --priority high
omarchy calendar settings --timezone Europe/Rome --availability monday=09:00-17:00
omarchy calendar plan
omarchy calendar apply
omarchy calendar open agenda
```

Use `events`, `tasks`, and `proposal` to inspect data, `edit-*` and `delete-*` to manage it, and `--json` for scripts. `plan` explicitly generates a fresh suggested schedule; it never changes the calendar. Planning is always a suggested schedule until `apply` is run; `return-inbox <task-id>` reverses an applied planner task without changing unrelated manual events.

### Opening the menu from the terminal

The Omarchy menu is scriptable as well, which is handy for your own keybindings. `omarchy menu` opens it at the root, and you can jump straight to any point in the tree by naming it: `omarchy menu summon style.theme` goes right to the theme picker, `omarchy menu toggle system` opens the system menu and closes it again if it's already up, and `omarchy menu close` puts it away.
3 changes: 3 additions & 0 deletions migrations/1788584591.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
echo "Install the native calendar planner solver"

omarchy-pkg-add omarchy-calendar-solver
24 changes: 21 additions & 3 deletions shell/Ui/MultiSelect.qml
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,16 @@ Item {
}

Keys.onPressed: function(event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter
// A popup can take a round trip through the window focus scope while
// it opens. If the trigger receives a printable key in that small
// handoff, keep the same searchable-input behavior instead of
// dropping the user's first characters.
if (popup.opened && event.text !== ""
&& !(event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier))) {
searchField.forceActiveFocus()
searchField.insert(searchField.cursorPosition, event.text)
event.accepted = true
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter
|| event.key === Qt.Key_Space || event.key === Qt.Key_Down) {
popup.opened ? popup.close() : popup.open()
event.accepted = true
Expand Down Expand Up @@ -387,9 +396,15 @@ Item {
searchField.text = ""
root.refresh()
root.recomputeFiltered()
Qt.callLater(function() { searchField.forceActiveFocus() })
searchField.forceActiveFocus()
Qt.callLater(function() {
if (popup.opened) searchField.forceActiveFocus()
})
}
onClosed: {
searchField.text = ""
trigger.forceActiveFocus()
}
onClosed: searchField.text = ""

contentItem: Column {
spacing: 0
Expand All @@ -408,6 +423,9 @@ Item {
id: searchField
width: parent.width - refreshButton.width - parent.spacing
height: parent.height
focus: true
activeFocusOnTab: true
Keys.priority: Keys.BeforeItem
placeholderText: root.placeholderText
foreground: root.foreground
accent: root.accent
Expand Down
2 changes: 1 addition & 1 deletion shell/plugins/bar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Example `shell.json` (bar subtree only shown):
|---|---|---|
| `omarchy.menu` | Omarchy menu launcher | left = menu · right = terminal |
| `omarchy.workspaces` | Hyprland workspace switcher | left = focus workspace |
| `omarchy.clock` | Date/time label + popup with a month grid, ISO week numbers, and month stepping | left = popup · right = cycle label format · middle = timezone selector |
| `omarchy.clock` | Date/time label + popup with Calendar, Agenda, and Plan views | left = popup · right = cycle label format · middle = timezone selector |
| `omarchy.media` | MPRIS now-playing — scrolling track + artist, cover-art popup | left = play/pause · middle = next · scroll = prev/next · right = popup |
| `omarchy.indicators` | Manual state indicators | left = indicator action |
| `omarchy.system-update` | Available update indicator | left = update |
Expand Down
141 changes: 141 additions & 0 deletions shell/plugins/panels/clock/AgendaView.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import QtQuick
import qs.Commons
import qs.Ui
import "PlannerModel.js" as PlannerModel

// Reusable agenda surface for the planner tab. Panel.qml keeps the original
// month grid untouched; this component owns the event-oriented view beside it.
Item {
id: root

property var service: null
property var bar: null
property color foreground: bar ? bar.foreground : Color.foreground
property string fontFamily: bar ? bar.fontFamily : Style.font.family
signal editEventRequested(var event)

implicitHeight: content.implicitHeight

function state() {
return root.service && root.service.calendarState
? root.service.calendarState
: { settings: {}, events: [] }
}

function dayKeys() {
var settings = state().settings || {}
var timezone = settings.timezone || ""
var grouped = PlannerModel.eventsByDay(state().events, timezone)
var today = PlannerModel.dayKey(new Date(), timezone)
var horizonDays = Math.max(1, Number(settings.horizonDays) || 14)
var end = PlannerModel.dayKey(new Date(Date.now() + horizonDays * 86400000), timezone)
return Object.keys(grouped).filter(function(key) { return key >= today && key <= end }).sort()
}

Column {
id: content
width: parent.width
spacing: Style.space(6)

Repeater {
model: root.dayKeys()
delegate: Column {
required property string modelData
property string dayKey: modelData
width: content.width
spacing: Style.space(5)

Text {
textFormat: Text.PlainText
text: PlannerModel.formatDayLabel(dayKey)
color: Qt.darker(root.foreground, 1.35)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: true
}

Repeater {
model: PlannerModel.eventsForDay(root.state().events, dayKey, root.state().settings.timezone)
delegate: Rectangle {
required property var modelData
width: content.width
height: eventColumn.implicitHeight + Style.space(16)
radius: Style.cornerRadius
color: Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.06)

Column {
id: eventColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Style.space(12)
anchors.rightMargin: Style.space(12)
spacing: Style.space(3)
Row {
width: parent.width
Button {
focusable: true
id: eventAction
visible: modelData.origin === "manual" || !!modelData.taskId
text: modelData.origin === "manual" ? "Edit" : "Return to inbox"
foreground: root.foreground
bordered: true
fontFamily: root.fontFamily
onClicked: {
if (modelData.origin === "manual") root.editEventRequested(modelData)
else root.service.returnToInbox(modelData.taskId)
}
}
Text {
textFormat: Text.PlainText
width: parent.width - (eventAction.visible ? eventAction.width : 0) - Style.space(8)
text: modelData.title
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
elide: Text.ElideRight
}
}
Text {
textFormat: Text.PlainText
text: Qt.formatDateTime(new Date(modelData.startAt), "ddd d MMM HH:mm")
+ " — " + Qt.formatDateTime(new Date(modelData.endAt), "HH:mm")
color: Qt.darker(root.foreground, 1.35)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
}
Text {
textFormat: Text.PlainText
visible: modelData.origin !== "manual"
text: "Scheduled from a planning task — manage it in Plan"
color: Color.accent
font.family: root.fontFamily
font.pixelSize: Style.font.caption
}
Text {
textFormat: Text.PlainText
visible: modelData.description !== ""
text: modelData.description
color: Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
width: parent.width
}
}
}
}
}
}

Text {
textFormat: Text.PlainText
visible: root.dayKeys().length === 0
text: "No events yet."
color: Qt.darker(root.foreground, 1.45)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
}
}
}
Loading