Merge remote-tracking branch 'origin/omarchy-shell' into omarchy-4
# Conflicts: # bin/omarchy-debug # bin/omarchy-upload-log
This commit is contained in:
@@ -58,7 +58,7 @@ fi
|
|||||||
|
|
||||||
charge_holding=false
|
charge_holding=false
|
||||||
if [[ $ac_online == "true" && -n $threshold_end ]]; then
|
if [[ $ac_online == "true" && -n $threshold_end ]]; then
|
||||||
if [[ $state == "discharging" || $state == "pending-charge" ]]; then
|
if [[ $state == "pending-charge" ]]; then
|
||||||
charge_holding=true
|
charge_holding=true
|
||||||
elif [[ $state == "fully-charged" ]] && (( percentage < 99 )); then
|
elif [[ $state == "fully-charged" ]] && (( percentage < 99 )); then
|
||||||
charge_holding=true
|
charge_holding=true
|
||||||
|
|||||||
+1
-1
@@ -75,7 +75,7 @@ ACTION=$(gum choose "${OPTIONS[@]}")
|
|||||||
case "$ACTION" in
|
case "$ACTION" in
|
||||||
"Upload log")
|
"Upload log")
|
||||||
echo "Uploading debug log to logs.omarchy.org..."
|
echo "Uploading debug log to logs.omarchy.org..."
|
||||||
URL=$(curl -sf -F "file=@$LOG_FILE" https://logs.omarchy.org/)
|
URL=$(curl -sf -F "file=@$LOG_FILE" -Fexpires=24 https://logs.omarchy.org/)
|
||||||
if (( $? == 0 )) && [[ -n $URL ]]; then
|
if (( $? == 0 )) && [[ -n $URL ]]; then
|
||||||
echo "✓ Log uploaded successfully!"
|
echo "✓ Log uploaded successfully!"
|
||||||
echo "Share this URL:"
|
echo "Share this URL:"
|
||||||
|
|||||||
+23
-1
@@ -94,6 +94,21 @@ require_command() {
|
|||||||
command -v "$command_name" >/dev/null 2>&1 || fail "$command_name is required"
|
command -v "$command_name" >/dev/null 2>&1 || fail "$command_name is required"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugin_discovered() {
|
||||||
|
local id="$1" plugins
|
||||||
|
plugins=$(omarchy-shell shell listPlugins 2>/dev/null) || return 1
|
||||||
|
jq -e --arg id "$id" 'any(.[]; .id == $id)' <<<"$plugins" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_plugin_discovery() {
|
||||||
|
local id="$1" attempt
|
||||||
|
for (( attempt = 0; attempt < 40; attempt++ )); do
|
||||||
|
plugin_discovered "$id" && return 0
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
section_valid() {
|
section_valid() {
|
||||||
[[ $1 =~ ^(left|center|right)$ ]]
|
[[ $1 =~ ^(left|center|right)$ ]]
|
||||||
}
|
}
|
||||||
@@ -181,7 +196,14 @@ plugin_enabled() {
|
|||||||
id=$(canonical_widget_id "$id")
|
id=$(canonical_widget_id "$id")
|
||||||
|
|
||||||
omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true
|
omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true
|
||||||
omarchy-shell shell setPluginEnabled "$id" "$enabled" || fail "could not update plugin; is omarchy-shell running?"
|
if [[ $enabled == "true" ]]; then
|
||||||
|
require_command jq
|
||||||
|
wait_for_plugin_discovery "$id" || fail "plugin '$id' was not discovered; is omarchy-shell running?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local result
|
||||||
|
result=$(omarchy-shell shell setPluginEnabled "$id" "$enabled") || fail "could not update plugin; is omarchy-shell running?"
|
||||||
|
[[ $result == "ok" ]] || fail "plugin '$id' is not known; run: omarchy plugin rescan"
|
||||||
|
|
||||||
if [[ $enabled == "true" && $# -gt 0 ]]; then
|
if [[ $enabled == "true" && $# -gt 0 ]]; then
|
||||||
mutate_bar move --id "$id" "$@"
|
mutate_bar move --id "$id" "$@"
|
||||||
|
|||||||
+18
-1
@@ -29,6 +29,21 @@ interactive() {
|
|||||||
[[ -t 0 && -t 1 ]]
|
[[ -t 0 && -t 1 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugin_discovered() {
|
||||||
|
local id="$1" plugins
|
||||||
|
plugins=$(omarchy-shell shell listPlugins 2>/dev/null) || return 1
|
||||||
|
jq -e --arg id "$id" 'any(.[]; .id == $id)' <<<"$plugins" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_plugin_discovery() {
|
||||||
|
local id="$1" attempt
|
||||||
|
for (( attempt = 0; attempt < 40; attempt++ )); do
|
||||||
|
plugin_discovered "$id" && return 0
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
PLUGIN_ID=""
|
PLUGIN_ID=""
|
||||||
FROM_SOURCE=""
|
FROM_SOURCE=""
|
||||||
ENABLE_AFTER="" # "", "true", or "false"
|
ENABLE_AFTER="" # "", "true", or "false"
|
||||||
@@ -280,7 +295,9 @@ if [[ -z $ENABLE_AFTER ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $ENABLE_AFTER == true ]]; then
|
if [[ $ENABLE_AFTER == true ]]; then
|
||||||
if omarchy-shell shell setPluginEnabled "$m_id" true >/dev/null 2>&1; then
|
if ! wait_for_plugin_discovery "$m_id"; then
|
||||||
|
echo "Could not enable $m_id because omarchy-shell did not discover it yet. Enable later with: omarchy plugin enable $m_id" >&2
|
||||||
|
elif result=$(omarchy-shell shell setPluginEnabled "$m_id" true) && [[ $result == "ok" ]]; then
|
||||||
echo "Enabled $m_id."
|
echo "Enabled $m_id."
|
||||||
if [[ $m_kinds == *bar-widget* ]]; then
|
if [[ $m_kinds == *bar-widget* ]]; then
|
||||||
echo "It was added to the bar; arrange it with: omarchy plugin bar move $m_id --section <left|center|right>"
|
echo "It was added to the bar; arrange it with: omarchy plugin bar move $m_id --section <left|center|right>"
|
||||||
|
|||||||
@@ -102,6 +102,5 @@ fi
|
|||||||
omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true
|
omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true
|
||||||
|
|
||||||
if [[ $was_enabled == "true" ]]; then
|
if [[ $was_enabled == "true" ]]; then
|
||||||
echo "Plugin was enabled; restart the shell to fully unload it:"
|
echo "Plugin was enabled and was unloaded from omarchy-shell."
|
||||||
echo " omarchy restart shell"
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -208,8 +208,7 @@ done
|
|||||||
|
|
||||||
if ((any_enabled)); then
|
if ((any_enabled)); then
|
||||||
echo
|
echo
|
||||||
echo "Updated plugins that were enabled may need a shell restart to fully reload:"
|
echo "Updated enabled plugins were hot-reloaded by omarchy-shell."
|
||||||
echo " omarchy restart shell"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit $rc
|
exit $rc
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ esac
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
URL=$(curl -sf -F "file=@$TEMP_LOG" https://logs.omarchy.org/)
|
URL=$(curl -sf -F "file=@$TEMP_LOG" -Fexpires=24 https://logs.omarchy.org/)
|
||||||
|
|
||||||
if (( $? == 0 )) && [[ -n $URL ]]; then
|
if (( $? == 0 )) && [[ -n $URL ]]; then
|
||||||
echo "✓ Log uploaded successfully!"
|
echo "✓ Log uploaded successfully!"
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ cp ~/.config/hypr/bindings.conf ~/.config/hypr/bindings.conf.bak.$(date +%s)
|
|||||||
|
|
||||||
# 4. Apply changes
|
# 4. Apply changes
|
||||||
# - Hyprland: auto-reloads on save, but MUST validate with `hyprctl reload` and `hyprctl configerrors`
|
# - Hyprland: auto-reloads on save, but MUST validate with `hyprctl reload` and `hyprctl configerrors`
|
||||||
# - Omarchy shell: shell.json hot-reloads; use `omarchy-restart-shell` for plugin/widget code changes
|
# - Omarchy shell: shell.json hot-reloads; use `omarchy plugin rescan` for plugin/widget code changes
|
||||||
# - Launcher: restart with `omarchy restart shell`
|
# - Launcher: restart with `omarchy restart shell`
|
||||||
# - Terminals: MUST restart with `omarchy restart terminal`
|
# - Terminals: MUST restart with `omarchy restart terminal`
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -78,9 +78,9 @@ individual plugins (`bar`, `image-selector`, …).
|
|||||||
| `hide <id>` | close a previously-summoned |
|
| `hide <id>` | close a previously-summoned |
|
||||||
| `toggle <id> <payloadJson>` | summon if closed, hide if open |
|
| `toggle <id> <payloadJson>` | summon if closed, hide if open |
|
||||||
| `call <id> <method> <arg>` | call an already-loaded plugin |
|
| `call <id> <method> <arg>` | call an already-loaded plugin |
|
||||||
| `rescanPlugins` | re-walk plugin dirs |
|
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
|
||||||
| `reloadConfig` | reload shell.json |
|
| `reloadConfig` | reload shell.json |
|
||||||
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit |
|
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
|
||||||
| `listPlugins` | JSON of every discovered plugin |
|
| `listPlugins` | JSON of every discovered plugin |
|
||||||
|
|
||||||
`setPluginEnabled` takes a string; only literal `"true"` enables.
|
`setPluginEnabled` takes a string; only literal `"true"` enables.
|
||||||
|
|||||||
+2
-2
@@ -172,9 +172,9 @@ running a separate Quickshell instance.
|
|||||||
| `hide <id>` | — | close a previously-summoned plugin |
|
| `hide <id>` | — | close a previously-summoned plugin |
|
||||||
| `toggle <id> <payloadJson>` | — | summon if closed, hide if open |
|
| `toggle <id> <payloadJson>` | — | summon if closed, hide if open |
|
||||||
| `call <id> <method> <arg>` | string | call a method on an already-loaded plugin |
|
| `call <id> <method> <arg>` | string | call a method on an already-loaded plugin |
|
||||||
| `rescanPlugins` | — | re-walk plugin dirs and pick up new/changed manifests |
|
| `rescanPlugins` | — | re-walk plugin dirs and hot-reload plugin code |
|
||||||
| `reloadConfig` | `ok` | reload `~/.config/omarchy/shell.json` |
|
| `reloadConfig` | `ok` | reload `~/.config/omarchy/shell.json` |
|
||||||
| `setPluginEnabled <id> <enabled>` | — | flip the persisted enabled bit (see note) |
|
| `setPluginEnabled <id> <enabled>` | `ok` / `unknown` | flip the persisted enabled bit (see note) |
|
||||||
| `listPlugins` | JSON | every discovered plugin (id, name, kinds, enabled) |
|
| `listPlugins` | JSON | every discovered plugin (id, name, kinds, enabled) |
|
||||||
|
|
||||||
Direct invocation:
|
Direct invocation:
|
||||||
|
|||||||
@@ -1257,6 +1257,12 @@ Item {
|
|||||||
readonly property bool hovered: moduleHover.hovered
|
readonly property bool hovered: moduleHover.hovered
|
||||||
readonly property bool dragSource: root.barDragSource === slot
|
readonly property bool dragSource: root.barDragSource === slot
|
||||||
readonly property bool panelOpen: root.activePopout === slot.activeItem
|
readonly property bool panelOpen: root.activePopout === slot.activeItem
|
||||||
|
readonly property real openIndicatorInlineOffset: {
|
||||||
|
var item = slot.activeItem
|
||||||
|
if (!item || !("openIndicatorInlineOffset" in item)) return 0
|
||||||
|
var offset = Number(item.openIndicatorInlineOffset)
|
||||||
|
return isFinite(offset) ? offset : 0
|
||||||
|
}
|
||||||
|
|
||||||
implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
|
implicitWidth: activeItem && activeItem.visible ? (root.vertical ? root.barSize : activeItem.implicitWidth) : 0
|
||||||
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
|
implicitHeight: activeItem && activeItem.visible ? activeItem.implicitHeight : 0
|
||||||
@@ -1329,8 +1335,12 @@ Item {
|
|||||||
radius: Math.min(width, height) / 2
|
radius: Math.min(width, height) / 2
|
||||||
width: root.vertical ? Style.space(2) : Math.max(Style.space(10), Math.round(parent.width * 0.55))
|
width: root.vertical ? Style.space(2) : Math.max(Style.space(10), Math.round(parent.width * 0.55))
|
||||||
height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2)
|
height: root.vertical ? Math.max(Style.space(10), Math.round(parent.height * 0.55)) : Style.space(2)
|
||||||
x: root.vertical ? (root.position === "left" ? parent.width - width - inset : inset) : Math.round((parent.width - width) / 2)
|
x: root.vertical
|
||||||
y: root.vertical ? Math.round((parent.height - height) / 2) : (root.position === "top" ? parent.height - height - inset : inset)
|
? (root.position === "left" ? parent.width - width - inset : inset)
|
||||||
|
: (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.width - width) / 2) : (parent.width - width) / 2 + slot.openIndicatorInlineOffset)
|
||||||
|
y: root.vertical
|
||||||
|
? (slot.openIndicatorInlineOffset === 0 ? Math.round((parent.height - height) / 2) : (parent.height - height) / 2 + slot.openIndicatorInlineOffset)
|
||||||
|
: (root.position === "top" ? parent.height - height - inset : inset)
|
||||||
z: 50
|
z: 50
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
|
|||||||
@@ -35,14 +35,17 @@ BarWidget {
|
|||||||
root.bar.run("hyprctl dispatch " + Util.shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
root.bar.run("hyprctl dispatch " + Util.shellQuote("hl.dsp.focus({ workspace = \"" + id + "\" })"))
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitWidth: grid.implicitWidth
|
readonly property real trailingGap: root.vertical ? 0 : Style.spaceReal(1.5)
|
||||||
|
|
||||||
|
implicitWidth: grid.implicitWidth + trailingGap
|
||||||
implicitHeight: grid.implicitHeight
|
implicitHeight: grid.implicitHeight
|
||||||
|
|
||||||
GridLayout {
|
GridLayout {
|
||||||
id: grid
|
id: grid
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: root.trailingGap
|
||||||
columns: root.vertical ? 1 : root.workspaceIds().length
|
columns: root.vertical ? 1 : root.workspaceIds().length
|
||||||
columnSpacing: root.vertical ? 0 : Style.space(2)
|
columnSpacing: root.vertical ? 0 : Style.space(1)
|
||||||
rowSpacing: root.vertical ? Style.space(2) : 0
|
rowSpacing: root.vertical ? Style.space(2) : 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -60,7 +63,7 @@ BarWidget {
|
|||||||
opacity: occupied || focused ? 1 : 0.5
|
opacity: occupied || focused ? 1 : 0.5
|
||||||
horizontalMargin: 6
|
horizontalMargin: 6
|
||||||
verticalPadding: 6
|
verticalPadding: 6
|
||||||
fixedWidth: root.vertical ? root.barSize : Style.space(22)
|
fixedWidth: root.vertical ? root.barSize : Style.space(20)
|
||||||
fixedHeight: root.barSize
|
fixedHeight: root.barSize
|
||||||
onPressed: function() { root.focusWorkspace(modelData) }
|
onPressed: function() { root.focusWorkspace(modelData) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -528,6 +528,8 @@ Panel {
|
|||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.outputIcon()
|
text: root.outputIcon()
|
||||||
fontSize: Style.font.body
|
fontSize: Style.font.body
|
||||||
|
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||||||
|
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||||
onPressed: function(b) {
|
onPressed: function(b) {
|
||||||
if (b === Qt.RightButton) root.toggleOutputMute()
|
if (b === Qt.RightButton) root.toggleOutputMute()
|
||||||
else root.toggle()
|
else root.toggle()
|
||||||
|
|||||||
@@ -406,6 +406,8 @@ Panel {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.icon
|
text: root.icon
|
||||||
|
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||||||
|
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||||
onPressed: function(b) {
|
onPressed: function(b) {
|
||||||
if (b === Qt.RightButton) root.toggleBluetooth()
|
if (b === Qt.RightButton) root.toggleBluetooth()
|
||||||
else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth")
|
else if (b === Qt.MiddleButton) root.bar.run("omarchy-launch-bluetooth")
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ Panel {
|
|||||||
Item {
|
Item {
|
||||||
id: button
|
id: button
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(32)
|
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27)
|
||||||
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26))
|
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26))
|
||||||
|
|
||||||
property var registeredBar: null
|
property var registeredBar: null
|
||||||
|
|||||||
@@ -343,6 +343,9 @@ Panel {
|
|||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.displays.length > 1 ? "" : ""
|
text: root.displays.length > 1 ? "" : ""
|
||||||
fontSize: Style.font.subtitle
|
fontSize: Style.font.subtitle
|
||||||
|
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||||||
|
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||||
|
rightExtraMargin: 4
|
||||||
onPressed: function(b) { root.toggle() }
|
onPressed: function(b) { root.toggle() }
|
||||||
onWheelMoved: function(delta) {
|
onWheelMoved: function(delta) {
|
||||||
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5))
|
if (root.brightnessAvailable) root.setBrightness(root.brightnessPercent + (delta > 0 ? 5 : -5))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Networking
|
import Quickshell.Networking
|
||||||
@@ -584,8 +585,9 @@ Panel {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.icon
|
text: root.icon
|
||||||
horizontalMargin: 8.5
|
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||||||
rightExtraMargin: 2
|
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||||
|
rightExtraMargin: 5.5
|
||||||
|
|
||||||
onPressed: function(b) {
|
onPressed: function(b) {
|
||||||
if (root.opened) root.close()
|
if (root.opened) root.close()
|
||||||
@@ -794,48 +796,41 @@ Panel {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.spacing.labelGap
|
spacing: Style.spacing.labelGap
|
||||||
|
|
||||||
Row {
|
GridLayout {
|
||||||
visible: root.info.rx_bytes !== undefined
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(20)
|
columns: 4
|
||||||
|
columnSpacing: Style.space(20)
|
||||||
|
rowSpacing: Style.spacing.labelGap
|
||||||
|
|
||||||
Column {
|
InfoLabel { visible: root.info.rx_bytes !== undefined; text: "Receiving" }
|
||||||
width: (parent.width - parent.spacing) / 2
|
DetailValue { visible: root.info.rx_bytes !== undefined; text: root.formatRate(root.downloadRate) }
|
||||||
spacing: Style.spacing.labelGap
|
InfoLabel { visible: root.info.rx_bytes !== undefined; text: "Sending" }
|
||||||
InfoPair { label: "Receiving"; value: root.formatRate(root.downloadRate) }
|
DetailValue { visible: root.info.rx_bytes !== undefined; text: root.formatRate(root.uploadRate) }
|
||||||
InfoPair { label: "Downloaded"; value: root.formatBytes(parseFloat(root.info.rx_bytes || "0")) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
InfoLabel { visible: root.info.rx_bytes !== undefined; text: "Downloaded" }
|
||||||
width: (parent.width - parent.spacing) / 2
|
DetailValue { visible: root.info.rx_bytes !== undefined; text: root.formatBytes(parseFloat(root.info.rx_bytes || "0")) }
|
||||||
spacing: Style.spacing.labelGap
|
InfoLabel { visible: root.info.rx_bytes !== undefined; text: "Uploaded" }
|
||||||
InfoPair { label: "Sending"; value: root.formatRate(root.uploadRate) }
|
DetailValue { visible: root.info.rx_bytes !== undefined; text: root.formatBytes(parseFloat(root.info.tx_bytes || "0")) }
|
||||||
InfoPair { label: "Uploaded"; value: root.formatBytes(parseFloat(root.info.tx_bytes || "0")) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
InfoLabel {
|
||||||
width: parent.width
|
visible: !!root.info.ip || !!root.info.gateway
|
||||||
spacing: Style.space(20)
|
text: root.info.ip ? "IP Address" : ""
|
||||||
Column {
|
|
||||||
width: (parent.width - parent.spacing) / 2
|
|
||||||
InfoPair {
|
|
||||||
visible: !!root.info.ip
|
|
||||||
label: "IP Address"
|
|
||||||
value: root.info.ip || ""
|
|
||||||
copyable: true
|
|
||||||
tooltipText: "Copy IP"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Column {
|
DetailValue {
|
||||||
width: (parent.width - parent.spacing) / 2
|
visible: !!root.info.ip || !!root.info.gateway
|
||||||
InfoPair {
|
text: root.info.ip || ""
|
||||||
visible: !!root.info.gateway
|
copyable: !!root.info.ip
|
||||||
label: "Gateway"
|
tooltipText: "Copy IP"
|
||||||
value: root.info.gateway || ""
|
}
|
||||||
copyable: true
|
InfoLabel {
|
||||||
tooltipText: "Copy gateway"
|
visible: !!root.info.ip || !!root.info.gateway
|
||||||
}
|
text: root.info.gateway ? "Gateway" : ""
|
||||||
|
}
|
||||||
|
DetailValue {
|
||||||
|
visible: !!root.info.ip || !!root.info.gateway
|
||||||
|
text: root.info.gateway || ""
|
||||||
|
copyable: !!root.info.gateway
|
||||||
|
tooltipText: "Copy gateway"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1313,35 +1308,26 @@ Panel {
|
|||||||
onTriggered: if (!networkProc.running) networkProc.running = true
|
onTriggered: if (!networkProc.running) networkProc.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
component InfoPair: Row {
|
component DetailValue: InfoValue {
|
||||||
property string label: ""
|
|
||||||
property string value: ""
|
|
||||||
property bool copyable: false
|
property bool copyable: false
|
||||||
property string tooltipText: "Copy to clipboard"
|
property string tooltipText: "Copy to clipboard"
|
||||||
|
|
||||||
width: parent.width
|
Layout.fillWidth: true
|
||||||
spacing: Style.space(8)
|
horizontalAlignment: Text.AlignRight
|
||||||
|
|
||||||
InfoLabel { text: label }
|
MouseArea {
|
||||||
Item { width: Math.max(0, parent.width - parent.children[0].implicitWidth - valueText.implicitWidth - parent.spacing * 2); height: 1 }
|
id: valueMouse
|
||||||
InfoValue {
|
anchors.fill: parent
|
||||||
id: valueText
|
enabled: copyable && parent.text !== ""
|
||||||
text: value
|
hoverEnabled: enabled
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
onClicked: root.copyToClipboard(parent.text)
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
PanelToolTip {
|
||||||
id: valueMouse
|
visible: valueMouse.enabled && valueMouse.containsMouse
|
||||||
anchors.fill: parent
|
text: tooltipText
|
||||||
enabled: copyable && valueText.text !== ""
|
fontFamily: root.bar.fontFamily
|
||||||
hoverEnabled: enabled
|
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
onClicked: root.copyToClipboard(valueText.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelToolTip {
|
|
||||||
visible: valueMouse.enabled && valueMouse.containsMouse
|
|
||||||
text: tooltipText
|
|
||||||
fontFamily: root.bar.fontFamily
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ function chargeThresholdActive(device, onBattery, states) {
|
|||||||
if (!(d && d.isPresent && !onBattery)) return false
|
if (!(d && d.isPresent && !onBattery)) return false
|
||||||
|
|
||||||
var fraction = batteryFraction(d)
|
var fraction = batteryFraction(d)
|
||||||
if (d.state === s.Discharging || d.state === s.PendingCharge) return true
|
if (d.state === s.Discharging) return false
|
||||||
|
if (d.state === s.PendingCharge) return true
|
||||||
if (d.state === s.FullyCharged && fraction < 0.99) return true
|
if (d.state === s.FullyCharged && fraction < 0.99) return true
|
||||||
if (d.state !== s.Charging || fraction >= 0.99) return false
|
if (d.state !== s.Charging || fraction >= 0.99) return false
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ function batteryIcon(device, onBattery, states) {
|
|||||||
if (threshold) return defaultIcons[index]
|
if (threshold) return defaultIcons[index]
|
||||||
if (d.state === states.FullyCharged) return ""
|
if (d.state === states.FullyCharged) return ""
|
||||||
if (d.state === states.Charging) return chargingIcons[index]
|
if (d.state === states.Charging) return chargingIcons[index]
|
||||||
|
if (d.state === states.Discharging) return defaultIcons[index]
|
||||||
if (!onBattery) return ""
|
if (!onBattery) return ""
|
||||||
return defaultIcons[index]
|
return defaultIcons[index]
|
||||||
}
|
}
|
||||||
@@ -85,7 +87,7 @@ function modeLabel(device, onBattery, states) {
|
|||||||
var percentage = d.isPresent ? d.percentage : 0
|
var percentage = d.isPresent ? d.percentage : 0
|
||||||
if (chargeThresholdActive(d, onBattery, states)) return "Threshold"
|
if (chargeThresholdActive(d, onBattery, states)) return "Threshold"
|
||||||
if (!onBattery && percentage >= 1) return "Fully charged"
|
if (!onBattery && percentage >= 1) return "Fully charged"
|
||||||
if (onBattery) return "On battery"
|
if (onBattery || d.state === states.Discharging) return "On battery"
|
||||||
return "Charging"
|
return "Charging"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ Panel {
|
|||||||
|
|
||||||
function batteryIcon() {
|
function batteryIcon() {
|
||||||
var device = UPower.displayDevice
|
var device = UPower.displayDevice
|
||||||
return Model.batteryIcon(device, UPower.onBattery, upowerStates())
|
return Model.batteryIcon(device, root.discharging, upowerStates())
|
||||||
}
|
}
|
||||||
|
|
||||||
function modeLabel() {
|
function modeLabel() {
|
||||||
var device = UPower.displayDevice
|
var device = UPower.displayDevice
|
||||||
return Model.modeLabel(device, UPower.onBattery, upowerStates())
|
return Model.modeLabel(device, root.discharging, upowerStates())
|
||||||
}
|
}
|
||||||
|
|
||||||
function profileIcon(name) {
|
function profileIcon(name) {
|
||||||
@@ -57,11 +57,15 @@ Panel {
|
|||||||
var device = UPower.displayDevice
|
var device = UPower.displayDevice
|
||||||
return device && device.isPresent && device.state === UPowerDeviceState.FullyCharged && !root.chargeThresholdActive
|
return device && device.isPresent && device.state === UPowerDeviceState.FullyCharged && !root.chargeThresholdActive
|
||||||
}
|
}
|
||||||
|
readonly property bool discharging: {
|
||||||
|
var device = UPower.displayDevice
|
||||||
|
return !!(device && device.isPresent && (UPower.onBattery || device.state === UPowerDeviceState.Discharging))
|
||||||
|
}
|
||||||
readonly property bool chargeThresholdActive: {
|
readonly property bool chargeThresholdActive: {
|
||||||
var device = UPower.displayDevice
|
var device = UPower.displayDevice
|
||||||
return Model.chargeThresholdActive(device, UPower.onBattery, upowerStates())
|
return Model.chargeThresholdActive(device, root.discharging, upowerStates())
|
||||||
}
|
}
|
||||||
readonly property bool batteryFull: fullyCharged || (!UPower.onBattery && batteryFraction >= 1)
|
readonly property bool batteryFull: fullyCharged || (!root.discharging && batteryFraction >= 1)
|
||||||
readonly property bool batteryFlowIdle: batteryFull || chargeThresholdActive
|
readonly property bool batteryFlowIdle: batteryFull || chargeThresholdActive
|
||||||
|
|
||||||
// 0..1 charge level, used by the visual progress bar.
|
// 0..1 charge level, used by the visual progress bar.
|
||||||
@@ -70,14 +74,12 @@ Panel {
|
|||||||
return Model.batteryFraction(d)
|
return Model.batteryFraction(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool batteryLow: UPower.onBattery && batteryFraction > 0 && batteryFraction <= 0.2
|
|
||||||
readonly property bool charging: {
|
readonly property bool charging: {
|
||||||
var d = UPower.displayDevice
|
var d = UPower.displayDevice
|
||||||
return d && d.isPresent && d.state === UPowerDeviceState.Charging && !root.chargeThresholdActive
|
return d && d.isPresent && d.state === UPowerDeviceState.Charging && !root.chargeThresholdActive
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property color batteryFillColor: {
|
readonly property color batteryFillColor: {
|
||||||
if (batteryLow) return Color.urgent
|
|
||||||
return root.bar ? root.bar.foreground : Color.foreground
|
return root.bar ? root.bar.foreground : Color.foreground
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +113,7 @@ Panel {
|
|||||||
readonly property var activePhrases: {
|
readonly property var activePhrases: {
|
||||||
if (fullyCharged) return []
|
if (fullyCharged) return []
|
||||||
if (charging) return chargingPhrases
|
if (charging) return chargingPhrases
|
||||||
if (UPower.onBattery || chargeThresholdActive) return onBatteryPhrases
|
if (discharging) return onBatteryPhrases
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
readonly property bool rotatingPhrases: activePhrases.length > 0
|
readonly property bool rotatingPhrases: activePhrases.length > 0
|
||||||
@@ -252,9 +254,8 @@ Panel {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: root.batteryIcon()
|
text: root.batteryIcon()
|
||||||
horizontalMargin: 8.5
|
fixedWidth: root.bar && root.bar.vertical ? -1 : Style.space(27)
|
||||||
rightExtraMargin: 2
|
fixedHeight: root.bar && root.bar.vertical ? Style.space(26) : -1
|
||||||
active: root.batteryPresent && UPower.displayDevice.percentage <= 0.2 && UPower.onBattery
|
|
||||||
tooltipText: ""
|
tooltipText: ""
|
||||||
onPressed: function(b) { if (root.batteryPresent) root.toggle() }
|
onPressed: function(b) { if (root.batteryPresent) root.toggle() }
|
||||||
}
|
}
|
||||||
@@ -296,7 +297,7 @@ Panel {
|
|||||||
Text {
|
Text {
|
||||||
id: heroIcon
|
id: heroIcon
|
||||||
text: root.batteryIcon()
|
text: root.batteryIcon()
|
||||||
color: root.batteryLow ? Color.urgent : root.bar.foreground
|
color: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
font.pixelSize: Style.font.display
|
font.pixelSize: Style.font.display
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -340,7 +341,7 @@ Panel {
|
|||||||
Text {
|
Text {
|
||||||
id: heroPercent
|
id: heroPercent
|
||||||
text: root.batteryInfo.percentage || "—"
|
text: root.batteryInfo.percentage || "—"
|
||||||
color: root.batteryLow ? Color.urgent : root.bar.foreground
|
color: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
font.pixelSize: Style.font.displayLarge
|
font.pixelSize: Style.font.displayLarge
|
||||||
font.bold: true
|
font.bold: true
|
||||||
@@ -408,17 +409,21 @@ Panel {
|
|||||||
width: (parent.width - parent.spacing) / 2
|
width: (parent.width - parent.spacing) / 2
|
||||||
spacing: Style.spacing.labelGap
|
spacing: Style.spacing.labelGap
|
||||||
InfoPair {
|
InfoPair {
|
||||||
label: root.chargeThresholdActive ? "Charge limit" : (UPower.onBattery ? "Time left" : "Time to full")
|
label: root.chargeThresholdActive ? "Charge limit" : (root.discharging ? "Time left" : "Time to full")
|
||||||
value: root.chargeThresholdActive ? (root.batteryInfo.threshold || "-") : (root.batteryFlowIdle ? "-" : (root.batteryInfo.time || "—"))
|
value: root.chargeThresholdActive ? (root.batteryInfo.threshold || "-") : (root.batteryFlowIdle ? "-" : (root.batteryInfo.time || "—"))
|
||||||
}
|
}
|
||||||
InfoPair {
|
InfoPair {
|
||||||
label: root.chargeThresholdActive ? "Battery state" : (UPower.onBattery ? "Discharging" : "Charging")
|
label: root.chargeThresholdActive ? "Battery state" : (root.discharging ? "Discharging" : "Charging")
|
||||||
value: root.chargeThresholdActive ? "Holding" : (root.batteryFull ? "-" : (root.batteryInfo.rate || ""))
|
value: root.chargeThresholdActive ? "Holding" : (root.batteryFull ? "-" : (root.batteryInfo.rate || ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Power profile picker ----------
|
// ---------- Power profile picker ----------
|
||||||
|
PanelSeparator {
|
||||||
|
foreground: root.bar.foreground
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(10)
|
spacing: Style.space(10)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Panel {
|
|||||||
ipcTarget: "omarchy.tailscale"
|
ipcTarget: "omarchy.tailscale"
|
||||||
manageIpc: false
|
manageIpc: false
|
||||||
|
|
||||||
|
readonly property real openIndicatorInlineOffset: bar && bar.vertical ? 0 : Style.spaceReal(1.5)
|
||||||
property string focusSection: "header"
|
property string focusSection: "header"
|
||||||
property int headerIndex: 0
|
property int headerIndex: 0
|
||||||
property int accountIndex: 0
|
property int accountIndex: 0
|
||||||
@@ -238,7 +239,7 @@ Panel {
|
|||||||
if (focusSection === "header") {
|
if (focusSection === "header") {
|
||||||
tailscale.toggleTailscale()
|
tailscale.toggleTailscale()
|
||||||
} else if (focusSection === "auth") {
|
} else if (focusSection === "auth") {
|
||||||
tailscale.authorizeProfileSwitching()
|
tailscale.authorizeTailscaleOperator()
|
||||||
} else if (focusSection === "accounts") {
|
} else if (focusSection === "accounts") {
|
||||||
var account = selectedAccount()
|
var account = selectedAccount()
|
||||||
if (account) tailscale.switchAccount(account.id)
|
if (account) tailscale.switchAccount(account.id)
|
||||||
@@ -362,7 +363,7 @@ Panel {
|
|||||||
Item {
|
Item {
|
||||||
id: button
|
id: button
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(32)
|
implicitWidth: root.bar && root.bar.vertical ? root.bar.barSize : Style.space(27)
|
||||||
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26))
|
implicitHeight: root.bar && root.bar.vertical ? Style.space(26) : (root.bar ? root.bar.barSize : Style.space(26))
|
||||||
|
|
||||||
property var registeredBar: null
|
property var registeredBar: null
|
||||||
@@ -389,6 +390,7 @@ Panel {
|
|||||||
|
|
||||||
TailscaleIcon {
|
TailscaleIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
anchors.horizontalCenterOffset: root.openIndicatorInlineOffset
|
||||||
anchors.verticalCenterOffset: -Style.space(1)
|
anchors.verticalCenterOffset: -Style.space(1)
|
||||||
iconSize: Style.space(12) * 0.85
|
iconSize: Style.space(12) * 0.85
|
||||||
color: root.barIconColor
|
color: root.barIconColor
|
||||||
@@ -748,7 +750,7 @@ Panel {
|
|||||||
cursorShape: tailscale.busy ? Qt.ArrowCursor : Qt.PointingHandCursor
|
cursorShape: tailscale.busy ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||||
enabled: !tailscale.busy
|
enabled: !tailscale.busy
|
||||||
onEntered: root.setAuthCursor()
|
onEntered: root.setAuthCursor()
|
||||||
onClicked: tailscale.authorizeProfileSwitching()
|
onClicked: tailscale.authorizeTailscaleOperator()
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -774,7 +776,7 @@ Panel {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: "Authorize switching"
|
text: "Authorize Tailscale operator"
|
||||||
color: root.foreground
|
color: root.foreground
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
@@ -783,7 +785,7 @@ Panel {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: "Allow this user to see and switch Tailscale connections"
|
text: "Allow this user to operate this Tailscale profile"
|
||||||
color: root.dim
|
color: root.dim
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: Style.font.caption
|
||||||
@@ -791,14 +793,6 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelActionButton {
|
|
||||||
iconText: ""
|
|
||||||
foreground: root.foreground
|
|
||||||
fontFamily: root.fontFamily
|
|
||||||
enabled: !tailscale.busy
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
onClicked: tailscale.authorizeProfileSwitching()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -994,14 +988,7 @@ Panel {
|
|||||||
modal: false
|
modal: false
|
||||||
focus: true
|
focus: true
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
onOpenedChanged: {
|
function handleKey(event) {
|
||||||
root.copyMenuOpen = opened
|
|
||||||
if (opened) {
|
|
||||||
peerRow.clampCopyIndex()
|
|
||||||
forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Keys.onPressed: function(event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
close()
|
close()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
@@ -1022,6 +1009,15 @@ Panel {
|
|||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onOpenedChanged: {
|
||||||
|
root.copyMenuOpen = opened
|
||||||
|
if (opened) {
|
||||||
|
peerRow.clampCopyIndex()
|
||||||
|
Qt.callLater(function() { copyPopupContent.forceActiveFocus() })
|
||||||
|
} else if (root.opened) {
|
||||||
|
Qt.callLater(function() { keyCatcher.forceActiveFocus() })
|
||||||
|
}
|
||||||
|
}
|
||||||
background: BorderSurface {
|
background: BorderSurface {
|
||||||
color: Color.background
|
color: Color.background
|
||||||
borderSpec: Border.flat(root.dim, 1)
|
borderSpec: Border.flat(root.dim, 1)
|
||||||
@@ -1029,7 +1025,11 @@ Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Column {
|
contentItem: Column {
|
||||||
|
id: copyPopupContent
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
focus: true
|
||||||
|
Keys.priority: Keys.BeforeItem
|
||||||
|
Keys.onPressed: function(event) { copyPopup.handleKey(event) }
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: peerRow.copyOptions
|
model: peerRow.copyOptions
|
||||||
@@ -1102,6 +1102,7 @@ Panel {
|
|||||||
readonly property bool activeExitNode: peer && peer.ExitNode === true
|
readonly property bool activeExitNode: peer && peer.ExitNode === true
|
||||||
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
|
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
|
||||||
readonly property string peerName: peer ? String(peer.DisplayName || peer.HostName || "Unknown") : "Unknown"
|
readonly property string peerName: peer ? String(peer.DisplayName || peer.HostName || "Unknown") : "Unknown"
|
||||||
|
readonly property string actionTooltip: addMullvad ? "" : (activeExitNode ? "Disconnect" : "Connect")
|
||||||
|
|
||||||
hasCursor: root.cursorActive && root.focusSection === "exitNodes" && root.exitNodeIndex === rowIndex
|
hasCursor: root.cursorActive && root.focusSection === "exitNodes" && root.exitNodeIndex === rowIndex
|
||||||
current: activeExitNode || settingExitNode || (addMullvad && root.mullvadPickerOpen)
|
current: activeExitNode || settingExitNode || (addMullvad && root.mullvadPickerOpen)
|
||||||
@@ -1154,12 +1155,19 @@ Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: exitNodeMouse
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onEntered: root.setExitNodeCursor(exitNodeRow.rowIndex)
|
onEntered: root.setExitNodeCursor(exitNodeRow.rowIndex)
|
||||||
onClicked: root.chooseExitNode(exitNodeRow.peer)
|
onClicked: root.chooseExitNode(exitNodeRow.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelToolTip {
|
||||||
|
visible: exitNodeRow.actionTooltip !== "" && exitNodeMouse.containsMouse
|
||||||
|
text: exitNodeRow.actionTooltip
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component MullvadRegionRow: CursorSurface {
|
component MullvadRegionRow: CursorSurface {
|
||||||
@@ -1172,6 +1180,7 @@ Panel {
|
|||||||
readonly property bool activeExitNode: peer && peer.ExitNode === true
|
readonly property bool activeExitNode: peer && peer.ExitNode === true
|
||||||
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
|
readonly property bool settingExitNode: peer && tailscale.settingExitNodeId === String(peer.id || "")
|
||||||
readonly property bool selectedRegion: root.mullvadPickerOpen && root.mullvadRegionIndex === rowIndex
|
readonly property bool selectedRegion: root.mullvadPickerOpen && root.mullvadRegionIndex === rowIndex
|
||||||
|
readonly property string actionTooltip: activeExitNode ? "Disconnect" : "Connect"
|
||||||
|
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fill: root.hoverFill
|
fill: root.hoverFill
|
||||||
@@ -1226,11 +1235,18 @@ Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
id: regionMouse
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onEntered: root.mullvadRegionIndex = regionRow.rowIndex
|
onEntered: root.mullvadRegionIndex = regionRow.rowIndex
|
||||||
onClicked: root.chooseExitNode(regionRow.peer)
|
onClicked: root.chooseExitNode(regionRow.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelToolTip {
|
||||||
|
visible: regionMouse.containsMouse
|
||||||
|
text: regionRow.actionTooltip
|
||||||
|
fontFamily: root.fontFamily
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ Renders the Tailscale mark natively as a theme-colored 3×3 dot grid, matching t
|
|||||||
|
|
||||||
## Add to the bar
|
## Add to the bar
|
||||||
|
|
||||||
This widget ships as first-party plugin `omarchy.tailscale`. Add it with `omarchy plugin bar add omarchy.tailscale`, or add an entry such as `{ "id": "omarchy.tailscale" }` to one of the `bar.layout` sections in `~/.config/omarchy/shell.json` and restart the shell.
|
This widget ships as first-party plugin `omarchy.tailscale`. Add it with `omarchy plugin bar add omarchy.tailscale`, or add an entry such as `{ "id": "omarchy.tailscale" }` to one of the `bar.layout` sections in `~/.config/omarchy/shell.json`; the shell reloads `shell.json` automatically.
|
||||||
|
|||||||
@@ -291,11 +291,11 @@ Item {
|
|||||||
exitNodeProcess.running = true
|
exitNodeProcess.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function authorizeProfileSwitching() {
|
function authorizeTailscaleOperator() {
|
||||||
if (!installed || operatorProcess.running || userName === "") return
|
if (!installed || operatorProcess.running || userName === "") return
|
||||||
_operatorOutput = ""
|
_operatorOutput = ""
|
||||||
_operatorError = ""
|
_operatorError = ""
|
||||||
actionStatus = "Authorizing Tailscale profiles…"
|
actionStatus = "Authorizing Tailscale operator..."
|
||||||
operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName]
|
operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName]
|
||||||
operatorProcess.running = true
|
operatorProcess.running = true
|
||||||
}
|
}
|
||||||
@@ -414,7 +414,7 @@ Item {
|
|||||||
root.parseAccounts("")
|
root.parseAccounts("")
|
||||||
if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) {
|
if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) {
|
||||||
root.accountsAccessDenied = true
|
root.accountsAccessDenied = true
|
||||||
root.lastError = "Authorize Tailscale profiles to show connections"
|
root.lastError = "Authorize Tailscale operator to show connections"
|
||||||
} else {
|
} else {
|
||||||
root.lastError = elideStatus(stderr || stdout || "Could not list Tailscale connections")
|
root.lastError = elideStatus(stderr || stdout || "Could not list Tailscale connections")
|
||||||
}
|
}
|
||||||
@@ -534,7 +534,7 @@ Item {
|
|||||||
} else {
|
} else {
|
||||||
root.accountsAccessDenied = false
|
root.accountsAccessDenied = false
|
||||||
root.lastError = ""
|
root.lastError = ""
|
||||||
root.actionStatus = "Tailscale profiles authorized"
|
root.actionStatus = "Tailscale operator authorized"
|
||||||
actionStatusTimer.restart()
|
actionStatusTimer.restart()
|
||||||
root._lastAccountsRefreshMs = 0
|
root._lastAccountsRefreshMs = 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ BarWidget {
|
|||||||
bar: root.bar
|
bar: root.bar
|
||||||
text: panelLoader.item ? panelLoader.item.label : ""
|
text: panelLoader.item ? panelLoader.item.label : ""
|
||||||
active: panelLoader.item && panelLoader.item.klass === "active"
|
active: panelLoader.item && panelLoader.item.klass === "active"
|
||||||
horizontalMargin: 5
|
horizontalMargin: 2.5
|
||||||
// Tooltip suppressed because the panel is the detail view.
|
// Tooltip suppressed because the panel is the detail view.
|
||||||
tooltipText: ""
|
tooltipText: ""
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ QtObject {
|
|||||||
property bool scanning: false
|
property bool scanning: false
|
||||||
|
|
||||||
signal pluginsChanged()
|
signal pluginsChanged()
|
||||||
|
signal scanFinished()
|
||||||
signal pluginLoadFailed(string id, string error)
|
signal pluginLoadFailed(string id, string error)
|
||||||
|
|
||||||
// ---------------------------------------------------------------- helpers
|
// ---------------------------------------------------------------- helpers
|
||||||
@@ -157,9 +158,13 @@ QtObject {
|
|||||||
var key = Util.canonicalWidgetId(String(id))
|
var key = Util.canonicalWidgetId(String(id))
|
||||||
if (!shellConfigMutator) {
|
if (!shellConfigMutator) {
|
||||||
console.warn("PluginRegistry.setEnabled called before shellConfigMutator wired")
|
console.warn("PluginRegistry.setEnabled called before shellConfigMutator wired")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
var manifest = installedPlugins[key]
|
var manifest = installedPlugins[key]
|
||||||
|
if (value && !manifest) {
|
||||||
|
console.warn("PluginRegistry.setEnabled: unknown plugin " + key)
|
||||||
|
return false
|
||||||
|
}
|
||||||
var isBarOption = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar") !== -1
|
var isBarOption = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar") !== -1
|
||||||
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
var isBarWidget = manifest && Array.isArray(manifest.kinds) && manifest.kinds.indexOf("bar-widget") !== -1
|
||||||
shellConfigMutator(function(config) {
|
shellConfigMutator(function(config) {
|
||||||
@@ -196,6 +201,7 @@ QtObject {
|
|||||||
})
|
})
|
||||||
registryRevision++
|
registryRevision++
|
||||||
pluginsChanged()
|
pluginsChanged()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- scanning
|
// ---------------------------------------------------------------- scanning
|
||||||
@@ -269,6 +275,7 @@ QtObject {
|
|||||||
registryRevision++
|
registryRevision++
|
||||||
scanning = false
|
scanning = false
|
||||||
pluginsChanged()
|
pluginsChanged()
|
||||||
|
scanFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
property Process scanProcess: Process {
|
property Process scanProcess: Process {
|
||||||
|
|||||||
+69
-8
@@ -54,6 +54,8 @@ ShellRoot {
|
|||||||
property var defaultsConfig: builtinShellConfig
|
property var defaultsConfig: builtinShellConfig
|
||||||
property var shellConfig: builtinShellConfig
|
property var shellConfig: builtinShellConfig
|
||||||
property bool suppressUserReload: false
|
property bool suppressUserReload: false
|
||||||
|
property bool pluginReloading: false
|
||||||
|
property bool pluginReloadPending: false
|
||||||
|
|
||||||
onShellConfigChanged: {
|
onShellConfigChanged: {
|
||||||
if (failedBarId !== "") failedBarId = ""
|
if (failedBarId !== "") failedBarId = ""
|
||||||
@@ -245,11 +247,11 @@ ShellRoot {
|
|||||||
Loader {
|
Loader {
|
||||||
id: pluginBarLoader
|
id: pluginBarLoader
|
||||||
|
|
||||||
active: shell.activeBarId !== shell.defaultBarId && shell.activeBarSourceUrl !== ""
|
active: !shell.pluginReloading && shell.activeBarId !== shell.defaultBarId && shell.activeBarSourceUrl !== ""
|
||||||
source: shell.activeBarId !== shell.defaultBarId ? shell.activeBarSourceUrl : ""
|
source: shell.activeBarId !== shell.defaultBarId ? shell.activeBarSourceUrl : ""
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
onLoaded: shell.configureBar(item, shell.activeBarManifest)
|
onLoaded: shell.configureBar(item, shell.activeBarManifest)
|
||||||
onActiveChanged: if (!active && shell.activeBarId === shell.defaultBarId) shell.bar = null
|
onActiveChanged: if (!active) shell.bar = null
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Loader.Error) {
|
if (status === Loader.Error) {
|
||||||
var detail = errorString && errorString() ? errorString() : ""
|
var detail = errorString && errorString() ? errorString() : ""
|
||||||
@@ -344,9 +346,17 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unloadPluginServices() {
|
||||||
|
for (var existingId in _services) {
|
||||||
|
var inst = _services[existingId]
|
||||||
|
if (inst && typeof inst.destroy === "function") inst.destroy()
|
||||||
|
}
|
||||||
|
_services = ({})
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: shell.pluginRegistry
|
target: shell.pluginRegistry
|
||||||
function onPluginsChanged() { shell._syncServices() }
|
function onPluginsChanged() { if (!shell.pluginReloading) shell._syncServices() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes inline settings to a bar layout entry or top-level plugin entry in
|
// Writes inline settings to a bar layout entry or top-level plugin entry in
|
||||||
@@ -484,6 +494,14 @@ ShellRoot {
|
|||||||
panelLoaders = next
|
panelLoaders = next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unloadPanels() {
|
||||||
|
for (var id in panelLoaders) hide(id)
|
||||||
|
panelEntries = []
|
||||||
|
panelLoaders = ({})
|
||||||
|
pendingPayloads = ({})
|
||||||
|
openPanelIds = ({})
|
||||||
|
}
|
||||||
|
|
||||||
function deliverIfLoaded(pluginId) {
|
function deliverIfLoaded(pluginId) {
|
||||||
var loader = panelLoaders[pluginId]
|
var loader = panelLoaders[pluginId]
|
||||||
if (!loader || !loader.item) return
|
if (!loader || !loader.item) return
|
||||||
@@ -549,7 +567,7 @@ ShellRoot {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: shell.pluginRegistry
|
target: shell.pluginRegistry
|
||||||
function onPluginsChanged() { shell.panelEntries = shell.computePanelEntries() }
|
function onPluginsChanged() { if (!shell.pluginReloading) shell.panelEntries = shell.computePanelEntries() }
|
||||||
}
|
}
|
||||||
|
|
||||||
Instantiator {
|
Instantiator {
|
||||||
@@ -606,7 +624,7 @@ ShellRoot {
|
|||||||
// widgets use the same first-party manifest contract as third-party widgets.
|
// widgets use the same first-party manifest contract as third-party widgets.
|
||||||
Connections {
|
Connections {
|
||||||
target: shell.pluginRegistry
|
target: shell.pluginRegistry
|
||||||
function onPluginsChanged() { shell.syncPluginWidgets() }
|
function onPluginsChanged() { if (!shell.pluginReloading) shell.syncPluginWidgets() }
|
||||||
}
|
}
|
||||||
|
|
||||||
property var pluginWidgetComponents: ({})
|
property var pluginWidgetComponents: ({})
|
||||||
@@ -670,6 +688,49 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function unloadPluginWidgets() {
|
||||||
|
for (var id in pluginWidgetComponents) shell.barWidgetRegistry.unregister(id)
|
||||||
|
pluginWidgetComponents = ({})
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadPlugins() {
|
||||||
|
if (shell.pluginReloading || shell.pluginRegistry.scanning) {
|
||||||
|
shell.pluginReloadPending = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shell.pluginReloading = true
|
||||||
|
shell.unloadPanels()
|
||||||
|
shell.unloadPluginServices()
|
||||||
|
shell.unloadPluginWidgets()
|
||||||
|
Qt.callLater(shell.finishPluginReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishPluginReload() {
|
||||||
|
if (!shell.pluginReloading) return
|
||||||
|
if (shell.pluginRegistry.scanning) {
|
||||||
|
shell.pluginReloadPending = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof Qt.clearComponentCache === "function") Qt.clearComponentCache()
|
||||||
|
shell.pluginRegistry.rescan()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: shell.pluginRegistry
|
||||||
|
function onScanFinished() {
|
||||||
|
if (shell.pluginReloadPending) {
|
||||||
|
shell.pluginReloadPending = false
|
||||||
|
shell.pluginReloading = false
|
||||||
|
Qt.callLater(shell.reloadPlugins)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shell.pluginReloading = false
|
||||||
|
shell._syncServices()
|
||||||
|
shell.panelEntries = shell.computePanelEntries()
|
||||||
|
shell.syncPluginWidgets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function loadPluginWidget(registryKey, url, meta) {
|
function loadPluginWidget(registryKey, url, meta) {
|
||||||
var comp = Qt.createComponent(url, Component.Asynchronous)
|
var comp = Qt.createComponent(url, Component.Asynchronous)
|
||||||
function finalize() {
|
function finalize() {
|
||||||
@@ -712,7 +773,7 @@ ShellRoot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function rescanPlugins(): void {
|
function rescanPlugins(): void {
|
||||||
shell.pluginRegistry.rescan()
|
shell.reloadPlugins()
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadConfig(): string {
|
function reloadConfig(): string {
|
||||||
@@ -720,8 +781,8 @@ ShellRoot {
|
|||||||
return "ok"
|
return "ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPluginEnabled(id: string, enabled: string): void {
|
function setPluginEnabled(id: string, enabled: string): string {
|
||||||
shell.pluginRegistry.setEnabled(id, enabled === "true")
|
return shell.pluginRegistry.setEnabled(id, enabled === "true") ? "ok" : "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
function listPlugins(): string {
|
function listPlugins(): string {
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ assertEqual(power.batteryFraction({ isPresent: true, percentage: 1.5 }), 1, 'pow
|
|||||||
assert(power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.PendingCharge }, false, states), 'power detects threshold by pending charge state')
|
assert(power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.PendingCharge }, false, states), 'power detects threshold by pending charge state')
|
||||||
assert(power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.Charging, changeRate: 0.1, timeToFull: 120 }, false, states), 'power detects threshold by stalled charging')
|
assert(power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.Charging, changeRate: 0.1, timeToFull: 120 }, false, states), 'power detects threshold by stalled charging')
|
||||||
assert(!power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.Charging, changeRate: 1.0, timeToFull: 120 }, false, states), 'power does not flag active charging as threshold')
|
assert(!power.chargeThresholdActive({ isPresent: true, percentage: 0.8, state: states.Charging, changeRate: 1.0, timeToFull: 120 }, false, states), 'power does not flag active charging as threshold')
|
||||||
|
assert(!power.chargeThresholdActive({ isPresent: true, percentage: 0.5, state: states.Discharging }, false, states), 'power does not flag discharging as threshold')
|
||||||
assertEqual(power.modeLabel({ isPresent: true, percentage: 1, state: states.FullyCharged }, false, states), 'Fully charged', 'power labels full battery')
|
assertEqual(power.modeLabel({ isPresent: true, percentage: 1, state: states.FullyCharged }, false, states), 'Fully charged', 'power labels full battery')
|
||||||
assertEqual(power.modeLabel({ isPresent: true, percentage: 0.5, state: states.Discharging }, true, states), 'On battery', 'power labels battery mode')
|
assertEqual(power.modeLabel({ isPresent: true, percentage: 0.5, state: states.Discharging }, true, states), 'On battery', 'power labels battery mode')
|
||||||
|
assertEqual(power.modeLabel({ isPresent: true, percentage: 0.5, state: states.Discharging }, false, states), 'On battery', 'power labels discharging battery mode')
|
||||||
assert(power.batteryIcon({ isPresent: true, percentage: 0.4, state: states.Charging }, false, states).length > 0, 'power maps battery icons')
|
assert(power.batteryIcon({ isPresent: true, percentage: 0.4, state: states.Charging }, false, states).length > 0, 'power maps battery icons')
|
||||||
JS
|
JS
|
||||||
|
|||||||
Reference in New Issue
Block a user