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:
commit
fdd457c0fa
86 files changed
+2545
-207
No files matched your search
+10
-1
@@ -601,5 +601,14 @@ var table = {
|
||||
"Pause": "暂停",
|
||||
"Play": "播放",
|
||||
"Next": "下一首",
|
||||
"Previous": "上一首"
|
||||
"Previous": "上一首",
|
||||
"Disable Passwordless Sudo": "关闭免密码 sudo",
|
||||
"Passwordless Sudo": "免密码 sudo",
|
||||
"Could not disable passwordless sudo": "无法关闭免密码 sudo",
|
||||
"Check the sudo configuration and try again.": "请检查 sudo 配置后重试。",
|
||||
"Click to set up an input language": "点击设置输入语言",
|
||||
"Click to switch input language": "点击切换输入语言",
|
||||
"Ctrl + Shift + Space to toggle input": "Ctrl + Shift + Space 切换输入",
|
||||
"Japanese: type a word, then Space twice for character choices": "日语:输入词语后按两次空格选字",
|
||||
"Right-click to switch keyboard layout": "右键切换键盘布局"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property var state: ({})
|
||||
|
||||
function cycle() {
|
||||
if (watcher.running) watcher.write("cycle\n")
|
||||
}
|
||||
|
||||
// One reader and pending selection for every monitor's keyboard widget.
|
||||
Process {
|
||||
id: watcher
|
||||
command: ["/usr/bin/python", Quickshell.env("OMARCHY_PATH") + "/default/input-methods/indicator.py"]
|
||||
running: true
|
||||
stdinEnabled: true
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
try { root.state = JSON.parse(data) } catch (e) {}
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
root.state = ({})
|
||||
restart.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: restart
|
||||
interval: 5000
|
||||
onTriggered: watcher.running = true
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ singleton Border 1.0 Border.qml
|
||||
singleton Color 1.0 Color.qml
|
||||
singleton I18n 1.0 I18n.qml
|
||||
singleton IpcRegistry 1.0 IpcRegistry.qml
|
||||
singleton InputMethodState 1.0 InputMethodState.qml
|
||||
singleton Style 1.0 Style.qml
|
||||
singleton Util 1.0 Util.qml
|
||||
ShellIpc 1.0 ShellIpc.qml
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user