Merge #14433 input methods and remove Rime

The keyboard choice and omarchy-setup-input own Chinese input now. The
new passwordless sudo and input tooltips get Chinese text.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-08 23:30:41 -04:00
commit fdd457c0fa
86 files changed
+2545 -207

No files matched your search

+3 -2
View File
@@ -19,8 +19,9 @@ Every subscription on one page, limits first.
per limit window: its meter and the time until it resets (the exact percentage
on hover). A model-scoped allowance on the same clock (Claude's Fable weekly
limit) is a tick on that window's meter rather than a line of its own; the
row's tooltip names it. Sign-in and endpoint trouble shows under the name in the urgent
color. Limits kept from an earlier check after a failed one dim, and their
row's tooltip names it. A lapsed or missing sign-in shows a _Sign-in required_ link that signs
that account in again from the panel; other endpoint trouble shows under the
name in the urgent color. Limits kept from an earlier check after a failed one dim, and their
tooltip says how old they are.
- **Accounts** — an agent with more than one subscription account (see
`omarchy agent account`) lists each: name and plan on one line (the email on hover), and its own limit
@@ -0,0 +1,72 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Ui
BarIndicator {
id: root
property bool granted: false
property bool refreshPending: false
active: granted
activeText: "󰟵"
inactiveText: "󰟵"
activeTooltipText: I18n.tr("Disable Passwordless Sudo")
inactiveTooltipText: I18n.tr("Passwordless Sudo")
useActiveColor: true
activeColor: Color.urgent
function refresh() {
if (!root.bar) return
if (statusProc.running) {
root.refreshPending = true
return
}
root.refreshPending = false
statusProc.running = true
}
onBarChanged: refresh()
Component.onCompleted: refresh()
Connections {
target: root.indicatorHost
ignoreUnknownSignals: true
function onRefreshRequested() { root.refresh() }
}
Timer {
interval: 5000
repeat: true
running: !!root.bar
onTriggered: root.refresh()
}
Process {
id: statusProc
command: ["omarchy-sudo-passwordless", "--active"]
onExited: function(exitCode, exitStatus) {
root.granted = exitCode === 0 && exitStatus === 0
if (root.refreshPending) Qt.callLater(root.refresh)
}
}
Process {
id: disableProc
command: ["omarchy-sudo-passwordless", "--disable"]
onExited: function(exitCode, exitStatus) {
if ((exitCode !== 0 || exitStatus !== 0) && root.bar)
Quickshell.execDetached([Quickshell.env("OMARCHY_PATH") + "/bin/omarchy-notification-send", I18n.tr("Could not disable passwordless sudo"), I18n.tr("Check the sudo configuration and try again.")])
if (root.indicatorHost) root.indicatorHost.refresh()
else root.refresh()
}
}
onPressed: function() {
if (!root.bar || disableProc.running) return
if (root.granted) disableProc.running = true
else root.bar.run("omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless")
}
}
@@ -26,6 +26,11 @@
"placeholderText": "Search indicators...",
"emptyText": "No indicators",
"options": [
{
"value": "PasswordlessSudo",
"label": "Passwordless sudo",
"description": "Warning while passwordless root access is enabled"
},
{
"value": "Dictation",
"label": "Dictation",
+1 -1
View File
@@ -8,7 +8,7 @@ BarWidget {
id: root
moduleName: "omarchy.indicators"
readonly property var defaultIndicatorEntries: [ "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var defaultIndicatorEntries: [ "PasswordlessSudo", "Dictation", "ScreenRecording", "Reminder", "NightLight", "Dnd", "StayAwake" ]
readonly property var indicatorEntries: indicatorEntriesFromSettings(settings)
property var activeIndicatorIds: []
property var indicatorActiveStates: ({})
@@ -4,7 +4,7 @@
"name": "Keyboard layout",
"version": "1.0.0",
"author": "Omarchy",
"description": "Current xkb layout, click cycles",
"description": "Current keyboard layout and input language, click cycles",
"kinds": [
"bar-widget"
],
@@ -13,7 +13,7 @@
},
"barWidget": {
"displayName": "Keyboard layout",
"description": "Current xkb layout, click cycles",
"description": "Current keyboard layout and input language, click cycles",
"category": "Compositor",
"allowMultiple": false
},
+15 -4
View File
@@ -36,6 +36,9 @@ BarWidget {
// xkb's own table rather than maintained by hand.
property var layoutBriefs: ({})
readonly property string layoutLabel: KeyboardLayoutModel.shortLabel(layoutFull, layoutBriefs)
readonly property var inputState: InputMethodState.state
readonly property bool multipleInputs: (inputState.methods || []).length > 1
readonly property string inputLabel: KeyboardLayoutModel.inputLabel(inputState, layoutLabel)
// A query already in flight was started before this event, so it may read the
// layout the switch replaced. Remember the request and re-run once it lands
@@ -228,7 +231,7 @@ BarWidget {
onTriggered: root.refresh()
}
visible: layoutLabel !== "" && multipleLayouts
visible: inputLabel !== "" && KeyboardLayoutModel.showIndicator(layoutLabel, multipleLayouts, multipleInputs)
implicitWidth: button.implicitWidth
implicitHeight: button.implicitHeight
@@ -236,10 +239,18 @@ BarWidget {
id: button
anchors.fill: parent
bar: root.bar
text: root.layoutLabel
text: root.inputLabel
fontSize: Style.font.caption
horizontalMargin: 6
tooltipText: root.layoutFull
onPressed: function() { root.cycleLayout() }
tooltipText: KeyboardLayoutModel.inputTooltip(root.inputState, root.layoutFull, root.multipleLayouts).split("\n").map(line => I18n.tr(line)).join("\n")
onPressed: function(button) {
if (root.multipleInputs && button === Qt.LeftButton) {
InputMethodState.cycle()
} else if (root.multipleLayouts) {
root.cycleLayout()
} else if (!root.multipleInputs && root.bar) {
root.bar.run("omarchy-launch-floating-terminal-with-presentation 'omarchy-setup-input'")
}
}
}
}
@@ -110,6 +110,28 @@ function selectKeyboard(typed, namedByEvent) {
}, keyboards[0])
}
function inputLabel(state, fallback) {
if (!state || !state.current || state.current.indexOf("keyboard-") === 0) return fallback
if (state.label) return String(state.label).substring(0, 3)
var labels = { mozc: "あ", hangul: "한", pinyin: "拼", chewing: "注" }
return labels[state.current] || shortLabel(state.language || state.name, {})
}
function inputTooltip(state, layout, multipleLayouts) {
if (!state || !state.methods || state.methods.length < 2)
return layout + (multipleLayouts ? "" : "\nClick to set up an input language")
var text = state.name + "\nClick to switch input language\nCtrl + Shift + Space to toggle input"
if (state.methods.indexOf("mozc") !== -1)
text += "\nJapanese: type a word, then Space twice for character choices"
if (multipleLayouts) text += "\nRight-click to switch keyboard layout"
return text
}
function showIndicator(layoutLabel, multipleLayouts, multipleInputs) {
return multipleInputs || (layoutLabel !== "" &&
(multipleLayouts || (layoutLabel !== "EN" && layoutLabel !== "ENG")))
}
function layoutIndex(keyboard) {
return (keyboard && keyboard.active_layout_index) || 0
}
@@ -120,6 +142,9 @@ if (typeof module !== "undefined") {
isTypedKeyboard: isTypedKeyboard,
layoutBriefs: layoutBriefs,
selectKeyboard: selectKeyboard,
shortLabel: shortLabel
shortLabel: shortLabel,
inputLabel: inputLabel,
inputTooltip: inputTooltip,
showIndicator: showIndicator
}
}
@@ -185,6 +185,12 @@ function snapshotOf(notification, timestamp) {
var id = n.id || 0
var expireTimeout = Number(n.expireTimeout || 0)
if (!isFinite(expireTimeout) || expireTimeout < 0) expireTimeout = 0
var actions = []
for (var i = 0; n.actions && i < n.actions.length; i++) {
var action = n.actions[i]
if (action && action.identifier && action.identifier !== "default")
actions.push({ identifier: String(action.identifier), text: String(action.text || action.identifier) })
}
return {
id: id,
originalId: id,
@@ -195,6 +201,7 @@ function snapshotOf(notification, timestamp) {
image: n.image || "",
glyph: glyphFromHints(n.hints),
execArgv: execArgvFromHints(n.hints),
actionsJson: JSON.stringify(actions),
urgency: n.urgency,
expireTimeout: expireTimeout,
timestamp: timestamp === undefined ? Date.now() : timestamp
@@ -203,7 +210,7 @@ function snapshotOf(notification, timestamp) {
// Everything the popup card draws, and therefore everything an in-place
// update has to write through to the row and its file.
var POPUP_ROLES = ["app", "appIcon", "summary", "body", "image", "glyph", "execArgv", "urgency", "expireTimeout"]
var POPUP_ROLES = ["app", "appIcon", "summary", "body", "image", "glyph", "execArgv", "actionsJson", "urgency", "expireTimeout"]
function popupRoles() {
return POPUP_ROLES
@@ -263,6 +270,8 @@ function historyEntry(value, normalUrgency) {
image: e.image || "",
glyph: e.glyph || "",
execArgv: e.execArgv || "",
// Libnotify actions belong to a live sender; never replay stale buttons.
actionsJson: "[]",
urgency: typeof e.urgency === "number" ? e.urgency : normalUrgency,
expireTimeout: 0,
timestamp: e.timestamp || 0
+21 -1
View File
@@ -235,7 +235,7 @@ Item {
// about after the popup exists.
readonly property var updateSignals: [
"summaryChanged", "bodyChanged", "appNameChanged", "appIconChanged",
"imageChanged", "urgencyChanged", "expireTimeoutChanged", "hintsChanged"
"imageChanged", "urgencyChanged", "expireTimeoutChanged", "hintsChanged", "actionsChanged"
]
// A client that updates a notification through replaces_id does not produce
@@ -427,10 +427,26 @@ Item {
// libnotify action — they just expect clicking the notification to
// focus their window. Fall back to focusing the sending app by class so
// that click-to-jump actually works.
if (!invoked && entry.actionsJson && entry.actionsJson !== "[]") return
if (!invoked) focusApp(entry)
dismissPopup(index)
}
function invokePopupAction(index, identifier) {
if (index < 0 || index >= popupModel.count) return
var entry = popupModel.get(index)
var ref = entry && !isRestoredRow(entry) ? liveRefs[entry.originalId] : null
if (!ref || !ref.actions) return
for (var i = 0; i < ref.actions.length; i++) {
var action = ref.actions[i]
if (action && action.identifier === identifier) {
action.invoke()
dismissPopup(index)
return
}
}
}
// Try to focus an existing Hyprland window matching the notification's
// sender. The helper handles case-insensitive class matching.
function focusApp(entry) {
@@ -726,6 +742,7 @@ Item {
image: "",
glyph: "󰂚",
execArgv: "",
actionsJson: "[]",
urgency: NotificationUrgency.Low,
expireTimeout: 0,
timestamp: Date.now()
@@ -1044,6 +1061,7 @@ Item {
required property string body
required property string image
required property string glyph
required property string actionsJson
required property int urgency
required property double expireTimeout
required property double timestamp
@@ -1095,9 +1113,11 @@ Item {
cornerRadius: service.cornerRadius
fontFamily: service.shell && service.shell.bar ? service.shell.bar.fontFamily : ""
glyph: cardSlot.glyph
actions: JSON.parse(cardSlot.actionsJson || "[]")
onCloseRequested: service.dismissPopup(cardSlot.index)
onCardClicked: service.invokePopupDefault(cardSlot.index)
onActionClicked: identifier => service.invokePopupAction(cardSlot.index, identifier)
}
}
}
@@ -26,6 +26,7 @@ BorderSurface {
property int urgency: 1
property double timestamp: 0
property int cornerRadius: 0
property var actions: []
// System monospace font injected by the container.
property string fontFamily: ""
@@ -34,6 +35,7 @@ BorderSurface {
signal closeRequested()
signal cardClicked()
signal actionClicked(string identifier)
// Prefer per-notification media/avatar data, then fall back to the app icon.
// The `check` flag avoids Qt's missing-texture placeholder for unknown names.
readonly property string smallIconSource: image.length > 0 ? image : iconSource(appIcon)
@@ -193,6 +195,27 @@ BorderSurface {
}
}
}
Flow {
Layout.fillWidth: true
Layout.leftMargin: Style.space(12)
Layout.rightMargin: Style.space(12)
Layout.bottomMargin: visible ? Style.space(10) : 0
visible: root.actions.length > 0
spacing: Style.space(8)
Repeater {
model: root.actions
delegate: Button {
required property var modelData
text: modelData.text
bordered: true
foreground: Color.notifications.text
fontFamily: root.fontFamily
onClicked: root.actionClicked(modelData.identifier)
}
}
}
}
// Hover-revealed close. Stacked after mainColumn so its MouseArea sits