Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 647d0878de | |||
| d4627f82d5 | |||
| a12a106791 | |||
| 02c6d055ce | |||
| 9a02f10eb8 | |||
| 4c6bc6f689 | |||
| f7157e15e8 | |||
| 22900737b0 | |||
| ef37fbbcbf |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(JNetApplet VERSION 1.0.1 LANGUAGES CXX)
|
project(JNetApplet VERSION 1.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|||||||
@@ -19,7 +19,24 @@ QML 前端渲染界面,实时显示当前网卡的上行/下行速率与累计
|
|||||||
|
|
||||||
## 构建与安装
|
## 构建与安装
|
||||||
|
|
||||||
需要配置期存在 `DDEShell` CMake 包(来自 `dde-shell` 开发包)。
|
### 构建依赖
|
||||||
|
|
||||||
|
构建需要以下开发包:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install build-essential cmake pkg-config qt6-base-dev qt6-declarative-dev libdtkcommon-dev libdde-shell-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
各包与 CMake `find_package` 的对应关系:
|
||||||
|
|
||||||
|
| 依赖包 | 提供 | 对应 CMake find_package |
|
||||||
|
|---|---|---|
|
||||||
|
| qt6-base-dev | Qt6 Core / DBus / Network | Qt6 Core DBus Network |
|
||||||
|
| qt6-declarative-dev | Qt6 Quick | Qt6 Quick |
|
||||||
|
| libdtkcommon-dev | Dtk6 Core | Dtk6 Core |
|
||||||
|
| libdde-shell-dev | DDEShell | DDEShell |
|
||||||
|
|
||||||
|
> 运行时仅需 `dde-shell` 及 Qt6/Dtk6 运行库,上述 `-dev` 包仅构建时需要。
|
||||||
|
|
||||||
### 方式一:从源码安装
|
### 方式一:从源码安装
|
||||||
|
|
||||||
|
|||||||
+48
-8
@@ -6,6 +6,25 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# 运行构建步骤:成功时仅显示末尾 3 行,失败时打印完整输出后返回非零
|
||||||
|
# 设计原因:原脚本用 | tail -3 管道会吞掉 find_package 等关键错误,
|
||||||
|
# 导致缺依赖时用户看不到真正原因(CMake 只在末尾报 Configuring incomplete)
|
||||||
|
run_step() {
|
||||||
|
local desc="$1"; shift
|
||||||
|
local log
|
||||||
|
log=$(mktemp)
|
||||||
|
if "$@" > "$log" 2>&1; then
|
||||||
|
tail -3 "$log"
|
||||||
|
rm -f "$log"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "错误:${desc}失败,完整输出如下:" >&2
|
||||||
|
cat "$log" >&2
|
||||||
|
rm -f "$log"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 项目根目录
|
# 项目根目录
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
@@ -41,22 +60,43 @@ for cmd in cmake dpkg-deb; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
echo "[1/6] 检查构建依赖..."
|
||||||
|
|
||||||
|
# 检查构建依赖:确认开发包已安装,缺失时提示安装命令并退出
|
||||||
|
# 设计原因:用户 clone 后直接运行 build-deb.sh 经常因缺少 -dev 包导致
|
||||||
|
# CMake 配置失败,但原脚本用 tail -3 吞掉关键错误,用户无法定位问题。
|
||||||
|
# 在构建前主动预检可提前给出明确指引。
|
||||||
|
DEPS=(qt6-base-dev qt6-declarative-dev libdtkcommon-dev libdde-shell-dev)
|
||||||
|
MISSING=()
|
||||||
|
for pkg in "${DEPS[@]}"; do
|
||||||
|
if ! dpkg -s "$pkg" &> /dev/null 2>&1; then
|
||||||
|
MISSING+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||||
|
echo "错误:缺少构建依赖包:${MISSING[*]}" >&2
|
||||||
|
echo "请执行以下命令安装:" >&2
|
||||||
|
echo " sudo apt install ${MISSING[*]} build-essential cmake" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "所有构建依赖已就绪"
|
||||||
|
|
||||||
# 清理并创建构建目录
|
# 清理并创建构建目录
|
||||||
echo "[1/5] 构建 CMake 项目..."
|
echo "[2/6] 构建 CMake 项目..."
|
||||||
rm -rf build
|
rm -rf build
|
||||||
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release 2>&1 | tail -3
|
run_step "CMake 配置" cmake -B build -S . -DCMAKE_BUILD_TYPE=Release || exit 1
|
||||||
cmake --build build -j"$(nproc)" 2>&1 | tail -3
|
run_step "CMake 构建" cmake --build build -j"$(nproc)" || exit 1
|
||||||
|
|
||||||
# 创建 staging 目录(模拟安装根目录)
|
# 创建 staging 目录(模拟安装根目录)
|
||||||
STAGE_DIR="$(mktemp -d)"
|
STAGE_DIR="$(mktemp -d)"
|
||||||
trap "rm -rf '$STAGE_DIR'" EXIT
|
trap "rm -rf '$STAGE_DIR'" EXIT
|
||||||
|
|
||||||
echo "[2/5] 安装到 staging 目录..."
|
echo "[3/6] 安装到 staging 目录..."
|
||||||
# DESTDIR 指定安装根目录前缀,cmake 会将 /usr/... 安装到 $STAGE_DIR/usr/...
|
# DESTDIR 指定安装根目录前缀,cmake 会将 /usr/... 安装到 $STAGE_DIR/usr/...
|
||||||
DESTDIR="$STAGE_DIR" cmake --install build 2>&1 | tail -3
|
run_step "安装到 staging" env DESTDIR="$STAGE_DIR" cmake --install build || exit 1
|
||||||
|
|
||||||
# 生成 DEBIAN/control
|
# 生成 DEBIAN/control
|
||||||
echo "[3/5] 生成 control 文件..."
|
echo "[4/6] 生成 control 文件..."
|
||||||
mkdir -p "$STAGE_DIR/DEBIAN"
|
mkdir -p "$STAGE_DIR/DEBIAN"
|
||||||
|
|
||||||
# 计算安装后大小(KB)
|
# 计算安装后大小(KB)
|
||||||
@@ -109,12 +149,12 @@ EOF
|
|||||||
chmod 755 "$STAGE_DIR/DEBIAN/prerm"
|
chmod 755 "$STAGE_DIR/DEBIAN/prerm"
|
||||||
|
|
||||||
# 修正文件权限(so 文件需要 755,qml/json 需要 644)
|
# 修正文件权限(so 文件需要 755,qml/json 需要 644)
|
||||||
echo "[4/5] 修正文件权限..."
|
echo "[5/6] 修正文件权限..."
|
||||||
find "$STAGE_DIR/usr" -type f -name "*.so" -exec chmod 755 {} \;
|
find "$STAGE_DIR/usr" -type f -name "*.so" -exec chmod 755 {} \;
|
||||||
find "$STAGE_DIR/usr" -type f \( -name "*.qml" -o -name "*.json" \) -exec chmod 644 {} \;
|
find "$STAGE_DIR/usr" -type f \( -name "*.qml" -o -name "*.json" \) -exec chmod 644 {} \;
|
||||||
|
|
||||||
# 构建 deb
|
# 构建 deb
|
||||||
echo "[5/5] 构建 deb 包..."
|
echo "[6/6] 构建 deb 包..."
|
||||||
rm -f "$DEB_FILE"
|
rm -f "$DEB_FILE"
|
||||||
dpkg-deb --build --root-owner-group "$STAGE_DIR" "$DEB_FILE"
|
dpkg-deb --build --root-owner-group "$STAGE_DIR" "$DEB_FILE"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,422 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 MyCompany
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
// 设置窗口:独立顶层窗口,桌面居中弹出,提供网络接口选择、字体颜色选择、插件卸载功能
|
||||||
|
// 设计要点:白色圆角卡片 + 自定义标题栏可拖动,接口单选切换,卸载按钮带复制命令功能
|
||||||
|
// 对外依赖:applet(C++ 后端对象)、networkInterfaces(接口列表)、activeInterface(当前接口)、
|
||||||
|
// accentColor(强调色),由 networkview.qml 实例化时传入,触发方式为 show()/raise()/requestActivate()
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
|
||||||
|
Window {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// 对外依赖:C++ 后端对象,用于获取/设置接口、字体颜色等
|
||||||
|
property var applet: null
|
||||||
|
|
||||||
|
// 网络接口名称列表,由父组件从 C++ 后端 applet.networkInterfaces 传入
|
||||||
|
property var networkInterfaces: []
|
||||||
|
|
||||||
|
// 当前活动接口名称,由父组件从 C++ 后端 applet.activeInterface 传入
|
||||||
|
property string activeInterface: ""
|
||||||
|
|
||||||
|
// 强调色(红色),关闭按钮 hover 态文字高亮色,由父组件传入
|
||||||
|
// 默认值与 networkview.qml 中 root.accentRed 一致,确保独立可用
|
||||||
|
property color accentColor: Qt.rgba(220 / 255, 38 / 255, 38 / 255, 1)
|
||||||
|
|
||||||
|
// 卸载按钮文字:复制命令后临时显示提示,定时器到期后还原
|
||||||
|
property string uninstallButtonText: qsTr("卸载插件")
|
||||||
|
|
||||||
|
width: 350
|
||||||
|
height: 390
|
||||||
|
minimumWidth: 350
|
||||||
|
maximumWidth: 350
|
||||||
|
minimumHeight: 390
|
||||||
|
maximumHeight: 390
|
||||||
|
visible: false
|
||||||
|
flags: Qt.FramelessWindowHint | Qt.Window
|
||||||
|
// NonModal:不阻塞桌面其他区域,用户可同时操作任务栏
|
||||||
|
modality: Qt.NonModal
|
||||||
|
// 窗口透明:让圆角外的区域不显示,由内部 Rectangle 提供可见背景
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// 根据接口名返回类型描述,用于设置窗口网络接口列表
|
||||||
|
// 设计原因:用户面对多个网口时难以仅凭 enp3s0/wlp3s0 等命名判断用途,
|
||||||
|
// 加一行类型说明(有线/无线/VPN 等)降低认知负担
|
||||||
|
function interfaceDescription(name) {
|
||||||
|
if (name === "lo") return qsTr("本地回环")
|
||||||
|
if (name === "Meta") return qsTr("虚拟接口")
|
||||||
|
if (/^enp|^eth/.test(name)) return qsTr("有线网络")
|
||||||
|
if (/^wlp|^wlan/.test(name)) return qsTr("无线网络")
|
||||||
|
if (/^docker|^veth/.test(name)) return qsTr("容器网络")
|
||||||
|
if (/^br/.test(name)) return qsTr("桥接")
|
||||||
|
if (/^tun|^tap/.test(name)) return qsTr("VPN")
|
||||||
|
if (/^virbr/.test(name)) return qsTr("虚拟桥接")
|
||||||
|
return qsTr("其他")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示时居中到桌面
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
x = (Screen.width - width) / 2
|
||||||
|
y = (Screen.height - height) / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主容器:提供不透明背景 + 圆角 + 边框
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#f5f5f5"
|
||||||
|
radius: 12
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 0
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// 自定义标题栏(可拖动)
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 44
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
// 拖动层:声明在关闭按钮之前(位于其下方),避免遮挡按钮点击与 hover
|
||||||
|
// 用 startSystemMove() 系统级窗口拖动,由窗口管理器接管移动,
|
||||||
|
// 这是 frameless Window 的正确做法,pressed 即触发,丝滑无抖动
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.OpenHandCursor
|
||||||
|
onPressed: root.startSystemMove()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题文字
|
||||||
|
Text {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: qsTr("设置")
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: "#333333"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭按钮
|
||||||
|
Rectangle {
|
||||||
|
id: closeButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 28
|
||||||
|
height: 28
|
||||||
|
radius: 14
|
||||||
|
color: closeMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : "transparent"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "×"
|
||||||
|
font.pixelSize: 20
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: closeMouse.containsMouse ? accentColor : "#666666"
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: closeMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
color: "#e0e0e0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 16
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
// 网络接口选择区
|
||||||
|
Text {
|
||||||
|
text: qsTr("网络接口")
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: "#666666"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口列表容器
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: networkInterfaces.length > 0 ? 40 + networkInterfaces.length * 28 : 80
|
||||||
|
color: "#ffffff"
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// 接口列表:RadioButton 单选切换活动接口
|
||||||
|
Repeater {
|
||||||
|
model: networkInterfaces
|
||||||
|
delegate: Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 28
|
||||||
|
color: "transparent"
|
||||||
|
radius: 6
|
||||||
|
|
||||||
|
RadioButton {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: modelData
|
||||||
|
checked: modelData === activeInterface
|
||||||
|
onToggled: {
|
||||||
|
if (applet) applet.setActiveInterface(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接口类型描述:右对齐显示,与接口名分居两侧
|
||||||
|
Text {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: interfaceDescription(modelData)
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#999999"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无接口提示
|
||||||
|
Text {
|
||||||
|
text: qsTr("未检测到网络接口")
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#999999"
|
||||||
|
visible: networkInterfaces.length === 0
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字体颜色选择区:预设色板 + 跟随系统选项
|
||||||
|
// 设计原因:用户选择的颜色通过 applet.textColor 持久化到
|
||||||
|
// ~/.config/jnetapplet/settings.ini,重启 dde-shell 后仍生效
|
||||||
|
Text {
|
||||||
|
text: qsTr("字体颜色")
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: "#666666"
|
||||||
|
}
|
||||||
|
|
||||||
|
TextColorPicker {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
currentColor: applet ? applet.textColor : ""
|
||||||
|
onColorSelected: if (applet) applet.textColor = color
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.preferredHeight: 4 }
|
||||||
|
|
||||||
|
// 卸载插件按钮:文字可动态切换(复制命令后显示提示)
|
||||||
|
// 提示状态期间按钮不可点击,文字变绿色
|
||||||
|
Rectangle {
|
||||||
|
id: uninstallButton
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 40
|
||||||
|
// 提示状态时背景和边框变绿
|
||||||
|
readonly property bool isStatus: uninstallButtonText !== qsTr("卸载插件")
|
||||||
|
color: isStatus
|
||||||
|
? Qt.rgba(22/255, 163/255, 74/255, 0.08)
|
||||||
|
: (uninstallMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : Qt.rgba(220/255, 38/255, 38/255, 0.08))
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : accentColor
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: 8
|
||||||
|
text: uninstallButtonText
|
||||||
|
font.pixelSize: uninstallButton.isStatus ? 11 : 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
// 提示状态时文字绿色,否则红色
|
||||||
|
color: uninstallButton.isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : accentColor
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: uninstallMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
// 提示状态期间禁用点击
|
||||||
|
enabled: !uninstallButton.isStatus
|
||||||
|
onClicked: uninstallConfirmDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载确认对话框:在设置窗口内水平居中
|
||||||
|
Dialog {
|
||||||
|
id: uninstallConfirmDialog
|
||||||
|
width: 320
|
||||||
|
height: 260
|
||||||
|
modal: true
|
||||||
|
visible: false
|
||||||
|
// 在设置窗口中心显示
|
||||||
|
x: (root.width - width) / 2
|
||||||
|
y: (root.height - height) / 2
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: "#f5f5f5"
|
||||||
|
radius: 12
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 16
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 20
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("警告")
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: accentColor
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: qsTr("以下命令用于卸载插件并重启 dde-shell,请复制到终端中执行:")
|
||||||
|
font.pixelSize: 12
|
||||||
|
color: "#333333"
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.fillWidth: true
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// 命令显示区域
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 60
|
||||||
|
color: "#e8e8e8"
|
||||||
|
radius: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 10
|
||||||
|
text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE"
|
||||||
|
font.pixelSize: 10
|
||||||
|
font.family: "monospace"
|
||||||
|
color: "#666666"
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
// 按钮区域
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
color: cancelUninstallMouse.containsMouse ? "#f0f0f0" : "#ffffff"
|
||||||
|
radius: 8
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: qsTr("取消")
|
||||||
|
font.pixelSize: 13
|
||||||
|
color: "#333333"
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelUninstallMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: uninstallConfirmDialog.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制命令按钮
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
color: confirmUninstallMouse.containsMouse ? accentColor : Qt.rgba(220/255, 38/255, 38/255, 0.8)
|
||||||
|
radius: 8
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: qsTr("复制命令")
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: confirmUninstallMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
// 复制卸载命令到剪贴板
|
||||||
|
clipboardHelper.selectAll()
|
||||||
|
clipboardHelper.copy()
|
||||||
|
uninstallConfirmDialog.close()
|
||||||
|
// 卸载按钮文字临时替换为复制成功提示,5 秒后还原
|
||||||
|
uninstallButtonText = qsTr("卸载命令已复制到剪贴板,请在终端中粘贴执行")
|
||||||
|
statusHideTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏的 TextEdit 用于复制卸载命令到剪贴板
|
||||||
|
// QML 没有直接剪贴板 API,用 TextEdit.selectAll()+copy() 实现
|
||||||
|
TextEdit {
|
||||||
|
id: clipboardHelper
|
||||||
|
visible: false
|
||||||
|
text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载按钮文字还原定时器:复制命令后 5 秒将按钮文字从提示还原为"卸载插件"
|
||||||
|
Timer {
|
||||||
|
id: statusHideTimer
|
||||||
|
interval: 5000
|
||||||
|
onTriggered: uninstallButtonText = qsTr("卸载插件")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 MyCompany
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
// 字体颜色选择器:用于设置窗口,提供预设色板与"跟随系统"选项
|
||||||
|
// 设计要点:白色圆角卡片与设置窗口其他分区视觉一致;圆形色块 22x22,
|
||||||
|
// 当前选中色加 2px 深色边框高亮;点击只发射 colorSelected 信号,
|
||||||
|
// 由父层(networkview.qml)回写 currentColor 并同步到 C++ 后端持久化
|
||||||
|
// 属性语义:
|
||||||
|
// currentColor - 当前选中颜色字符串,空串表示"跟随系统"(默认)
|
||||||
|
// colorSelected(string) - 用户点击色块时发射,参数为空串或 #RRGGBB
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// 当前选中颜色:空串=跟随系统,用于显示选中态高亮
|
||||||
|
property string currentColor: ""
|
||||||
|
|
||||||
|
// 用户点击色块时发射,父层据此回写 currentColor 并持久化
|
||||||
|
signal colorSelected(string color)
|
||||||
|
|
||||||
|
// 预设色板:8 个常用色,覆盖白/黑/暖/冷,克制实用
|
||||||
|
property var presetColors: [
|
||||||
|
"#FFFFFF", "#000000", "#DC2626", "#F59E0B",
|
||||||
|
"#16A34A", "#1450A0", "#7C3AED", "#0891B2"
|
||||||
|
]
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 52
|
||||||
|
color: "#ffffff"
|
||||||
|
radius: 10
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
// 预设色块
|
||||||
|
Repeater {
|
||||||
|
model: root.presetColors
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: 26
|
||||||
|
height: 26
|
||||||
|
radius: 13
|
||||||
|
color: "transparent"
|
||||||
|
// 当前选中态:2px 深色边框
|
||||||
|
border.width: root.currentColor === modelData ? 2 : 0
|
||||||
|
border.color: "#333333"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 11
|
||||||
|
// 白色色块在白卡上需细边框可见
|
||||||
|
color: modelData
|
||||||
|
border.width: modelData === "#FFFFFF" ? 1 : 0
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.colorSelected(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跟随系统选项:斜线色块表示恢复默认
|
||||||
|
Rectangle {
|
||||||
|
width: 26
|
||||||
|
height: 26
|
||||||
|
radius: 13
|
||||||
|
color: "transparent"
|
||||||
|
// currentColor 为空串时高亮(跟随系统选中态)
|
||||||
|
border.width: root.currentColor === "" ? 2 : 0
|
||||||
|
border.color: "#333333"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
radius: 11
|
||||||
|
color: "#f5f5f5"
|
||||||
|
border.width: 1
|
||||||
|
border.color: "#e0e0e0"
|
||||||
|
|
||||||
|
// 斜线:表示"无自定义色/跟随系统"
|
||||||
|
Canvas {
|
||||||
|
anchors.fill: parent
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d")
|
||||||
|
ctx.reset()
|
||||||
|
ctx.strokeStyle = "#999999"
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(2, height - 2)
|
||||||
|
ctx.lineTo(width - 2, 2)
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.colorSelected("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹性空间,让色块左对齐
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -338,13 +338,17 @@ Window {
|
|||||||
var ul = root.applet ? root.applet.speedHistoryUpload : []
|
var ul = root.applet ? root.applet.speedHistoryUpload : []
|
||||||
var n = dl.length
|
var n = dl.length
|
||||||
|
|
||||||
// ---- Y 轴上限计算(规范 §4.4)----
|
// ---- Y 轴上限计算(动态适应最近活动)----
|
||||||
// 1. vMax = 所有采样点 max(download, upload)
|
// 使用最近 60 秒的数据计算 Y 轴上限,而非全量 5 分钟缓冲。
|
||||||
// 2. vMax == 0 时上限取 1024(1 KB/s)避免除零
|
// 设计原因:若使用全量缓冲,当历史中存在大流量峰值(如 12MB/s 下载)时,
|
||||||
// 3. 否则取 vMax * 1.2 向上取整到 1/2/5 × 10^n 序列
|
// 即使当前网速已降至 KB/s 级别,Y 轴仍保持高位,导致当前曲线被压到底部
|
||||||
|
// 无法观察波动。改用 60 秒窗口后,峰值滑出窗口时 Y 轴自动缩小,
|
||||||
|
// 始终为当前活动提供合适的显示比例。
|
||||||
var vMax = 0
|
var vMax = 0
|
||||||
var i
|
var i
|
||||||
for (i = 0; i < n; i++) {
|
var recentWindow = 60 // 秒,Y 轴基于最近 1 分钟的活动计算
|
||||||
|
var recentStart = Math.max(0, n - recentWindow)
|
||||||
|
for (i = recentStart; i < n; i++) {
|
||||||
if (dl[i].y > vMax) vMax = dl[i].y
|
if (dl[i].y > vMax) vMax = dl[i].y
|
||||||
if (i < ul.length && ul[i].y > vMax) vMax = ul[i].y
|
if (i < ul.length && ul[i].y > vMax) vMax = ul[i].y
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-378
@@ -81,6 +81,7 @@ AppletItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 格式化速度显示(带单位,用于弹出面板,信息更完整)
|
// 格式化速度显示(带单位,用于弹出面板,信息更完整)
|
||||||
// 最小单位为 KB,与 formatSpeedShort 保持一致;所有级别保留 2 位小数
|
// 最小单位为 KB,与 formatSpeedShort 保持一致;所有级别保留 2 位小数
|
||||||
function formatSpeed(bytesPerSec) {
|
function formatSpeed(bytesPerSec) {
|
||||||
@@ -135,6 +136,11 @@ AppletItem {
|
|||||||
// 上传值颜色:固定绿色,不做高速警示
|
// 上传值颜色:固定绿色,不做高速警示
|
||||||
readonly property color uploadValueColor: accentGreen
|
readonly property color uploadValueColor: accentGreen
|
||||||
|
|
||||||
|
// 任务栏网速数值字体色:用户自定义色优先(持久化),未设置时跟随系统主题
|
||||||
|
// 设计原因:primaryText 派生自 DockPalette,深浅色任务栏自动适配;
|
||||||
|
// 用户主动选色后覆盖,空串回退到 primaryText 保持自适应
|
||||||
|
readonly property color speedTextColor: (applet && applet.textColor.length > 0) ? applet.textColor : primaryText
|
||||||
|
|
||||||
// 任务栏图标区:双行紧凑数值,箭头与数值紧贴、左对齐
|
// 任务栏图标区:双行紧凑数值,箭头与数值紧贴、左对齐
|
||||||
// 设计原因:箭头与数值作为一组固定在左侧,数值左对齐紧贴箭头;
|
// 设计原因:箭头与数值作为一组固定在左侧,数值左对齐紧贴箭头;
|
||||||
// 不给每行固定高度,让 RowLayout 按内容自然高度排列,
|
// 不给每行固定高度,让 RowLayout 按内容自然高度排列,
|
||||||
@@ -153,7 +159,7 @@ AppletItem {
|
|||||||
Text {
|
Text {
|
||||||
text: "↓"
|
text: "↓"
|
||||||
font.pixelSize: root.dockSize * 0.20
|
font.pixelSize: root.dockSize * 0.20
|
||||||
color: root.secondaryText
|
color: root.speedTextColor
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +167,7 @@ AppletItem {
|
|||||||
text: root.formatSpeedShort(root.downloadSpeed)
|
text: root.formatSpeedShort(root.downloadSpeed)
|
||||||
font.pixelSize: root.dockSize * 0.25
|
font.pixelSize: root.dockSize * 0.25
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: root.primaryText
|
color: root.speedTextColor
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
@@ -174,7 +180,7 @@ AppletItem {
|
|||||||
Text {
|
Text {
|
||||||
text: "↑"
|
text: "↑"
|
||||||
font.pixelSize: root.dockSize * 0.20
|
font.pixelSize: root.dockSize * 0.20
|
||||||
color: root.secondaryText
|
color: root.speedTextColor
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +188,7 @@ AppletItem {
|
|||||||
text: root.formatSpeedShort(root.uploadSpeed)
|
text: root.formatSpeedShort(root.uploadSpeed)
|
||||||
font.pixelSize: root.dockSize * 0.25
|
font.pixelSize: root.dockSize * 0.25
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: root.primaryText
|
color: root.speedTextColor
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
@@ -207,7 +213,7 @@ AppletItem {
|
|||||||
Text {
|
Text {
|
||||||
text: "↓"
|
text: "↓"
|
||||||
font.pixelSize: root.dockSize * 0.20
|
font.pixelSize: root.dockSize * 0.20
|
||||||
color: root.secondaryText
|
color: root.speedTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +226,7 @@ AppletItem {
|
|||||||
font.pixelSize: root.dockSize * 0.25
|
font.pixelSize: root.dockSize * 0.25
|
||||||
height: font.pixelSize
|
height: font.pixelSize
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: root.primaryText
|
color: root.speedTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +240,7 @@ AppletItem {
|
|||||||
Text {
|
Text {
|
||||||
text: "↑"
|
text: "↑"
|
||||||
font.pixelSize: root.dockSize * 0.20
|
font.pixelSize: root.dockSize * 0.20
|
||||||
color: root.secondaryText
|
color: root.speedTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +251,7 @@ AppletItem {
|
|||||||
font.pixelSize: root.dockSize * 0.25
|
font.pixelSize: root.dockSize * 0.25
|
||||||
height: font.pixelSize
|
height: font.pixelSize
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
color: root.primaryText
|
color: root.speedTextColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,377 +709,12 @@ AppletItem {
|
|||||||
// 设置窗口:独立顶层窗口,在桌面中间弹出
|
// 设置窗口:独立顶层窗口,在桌面中间弹出
|
||||||
// 设计原因:Dialog 使用父窗口(dock layer-surface)的 overlay,仅覆盖任务栏区域,
|
// 设计原因:Dialog 使用父窗口(dock layer-surface)的 overlay,仅覆盖任务栏区域,
|
||||||
// 无法在桌面中间显示。改用 Window 创建独立顶层窗口,可在桌面任意位置弹出。
|
// 无法在桌面中间显示。改用 Window 创建独立顶层窗口,可在桌面任意位置弹出。
|
||||||
Window {
|
SettingsWindow {
|
||||||
id: settingsWindow
|
id: settingsWindow
|
||||||
width: 340
|
applet: root.applet
|
||||||
height: 350
|
networkInterfaces: root.networkInterfaces
|
||||||
visible: false
|
activeInterface: root.activeInterface
|
||||||
flags: Qt.FramelessWindowHint | Qt.Window
|
accentColor: root.accentRed
|
||||||
// NonModal:不阻塞桌面其他区域,用户可同时操作任务栏
|
|
||||||
modality: Qt.NonModal
|
|
||||||
// 卸载按钮文字:复制命令后临时显示提示,定时器到期后还原
|
|
||||||
property string uninstallButtonText: qsTr("卸载插件")
|
|
||||||
// 窗口透明:让圆角外的区域不显示,由内部 Rectangle 提供可见背景
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
// 显示时居中到桌面
|
|
||||||
onVisibleChanged: {
|
|
||||||
if (visible) {
|
|
||||||
x = (Screen.width - width) / 2
|
|
||||||
y = (Screen.height - height) / 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主容器:提供不透明背景 + 圆角 + 边框
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: "#f5f5f5"
|
|
||||||
radius: 12
|
|
||||||
border.width: 1
|
|
||||||
border.color: "#e0e0e0"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 0
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// 自定义标题栏(可拖动)
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 44
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
// 拖动区域
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
drag.target: settingsWindow
|
|
||||||
cursorShape: Qt.ArrowCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标题文字
|
|
||||||
Text {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: 16
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: qsTr("设置")
|
|
||||||
font.pixelSize: 15
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: "#333333"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭按钮
|
|
||||||
Rectangle {
|
|
||||||
id: closeButton
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 12
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: 28
|
|
||||||
height: 28
|
|
||||||
radius: 14
|
|
||||||
color: closeMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : "transparent"
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: "×"
|
|
||||||
font.pixelSize: 20
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: closeMouse.containsMouse ? root.accentRed : "#666666"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: settingsWindow.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分隔线
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
color: "#e0e0e0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 内容区域
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 16
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
// 网络接口选择区
|
|
||||||
Text {
|
|
||||||
text: qsTr("网络接口")
|
|
||||||
font.pixelSize: 13
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: "#666666"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接口列表容器
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: root.networkInterfaces.length > 0 ? 40 + root.networkInterfaces.length * 28 : 80
|
|
||||||
color: "#ffffff"
|
|
||||||
radius: 10
|
|
||||||
border.width: 1
|
|
||||||
border.color: "#e0e0e0"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
// 接口列表:RadioButton 单选切换活动接口
|
|
||||||
Repeater {
|
|
||||||
model: root.networkInterfaces
|
|
||||||
delegate: Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 28
|
|
||||||
color: "transparent"
|
|
||||||
radius: 6
|
|
||||||
|
|
||||||
RadioButton {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
text: modelData
|
|
||||||
checked: modelData === root.activeInterface
|
|
||||||
onToggled: {
|
|
||||||
if (root.applet) root.applet.setActiveInterface(modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无接口提示
|
|
||||||
Text {
|
|
||||||
text: qsTr("未检测到网络接口")
|
|
||||||
font.pixelSize: 12
|
|
||||||
color: "#999999"
|
|
||||||
visible: root.networkInterfaces.length === 0
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillHeight: true }
|
|
||||||
|
|
||||||
// 卸载插件按钮:文字可动态切换(复制命令后显示提示)
|
|
||||||
// 提示状态期间按钮不可点击,文字变绿色
|
|
||||||
Rectangle {
|
|
||||||
id: uninstallButton
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
// 提示状态时背景和边框变绿
|
|
||||||
readonly property bool isStatus: settingsWindow.uninstallButtonText !== qsTr("卸载插件")
|
|
||||||
color: isStatus
|
|
||||||
? Qt.rgba(22/255, 163/255, 74/255, 0.08)
|
|
||||||
: (uninstallMouse.containsMouse ? Qt.rgba(220/255, 38/255, 38/255, 0.15) : Qt.rgba(220/255, 38/255, 38/255, 0.08))
|
|
||||||
radius: 10
|
|
||||||
border.width: 1
|
|
||||||
border.color: isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : root.accentRed
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.margins: 8
|
|
||||||
text: settingsWindow.uninstallButtonText
|
|
||||||
font.pixelSize: uninstallButton.isStatus ? 11 : 13
|
|
||||||
font.weight: Font.Medium
|
|
||||||
// 提示状态时文字绿色,否则红色
|
|
||||||
color: uninstallButton.isStatus ? Qt.rgba(22/255, 163/255, 74/255, 1) : root.accentRed
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: uninstallMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
// 提示状态期间禁用点击
|
|
||||||
enabled: !uninstallButton.isStatus
|
|
||||||
onClicked: uninstallConfirmDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭按钮
|
|
||||||
Rectangle {
|
|
||||||
id: bottomCloseButton
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
color: bottomCloseMouse.containsMouse ? root.accentBlueLight : "#ffffff"
|
|
||||||
radius: 10
|
|
||||||
border.width: 1
|
|
||||||
border.color: bottomCloseMouse.containsMouse ? root.accentBlue : "#e0e0e0"
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: qsTr("关闭")
|
|
||||||
font.pixelSize: 13
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: bottomCloseMouse.containsMouse ? root.accentBlue : "#333333"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: bottomCloseMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: settingsWindow.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卸载确认对话框:在设置窗口内水平居中
|
|
||||||
Dialog {
|
|
||||||
id: uninstallConfirmDialog
|
|
||||||
width: 320
|
|
||||||
height: 260
|
|
||||||
modal: true
|
|
||||||
visible: false
|
|
||||||
// 在设置窗口中心显示
|
|
||||||
x: (settingsWindow.width - width) / 2
|
|
||||||
y: (settingsWindow.height - height) / 2
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: "#f5f5f5"
|
|
||||||
radius: 12
|
|
||||||
border.width: 1
|
|
||||||
border.color: "#e0e0e0"
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
spacing: 16
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 20
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("警告")
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: root.accentRed
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: qsTr("以下命令用于卸载插件并重启 dde-shell,请复制到终端中执行:")
|
|
||||||
font.pixelSize: 12
|
|
||||||
color: "#333333"
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
// 命令显示区域
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 60
|
|
||||||
color: "#e8e8e8"
|
|
||||||
radius: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 10
|
|
||||||
text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE"
|
|
||||||
font.pixelSize: 10
|
|
||||||
font.family: "monospace"
|
|
||||||
color: "#666666"
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item { Layout.fillHeight: true }
|
|
||||||
|
|
||||||
// 按钮区域
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 36
|
|
||||||
color: cancelUninstallMouse.containsMouse ? "#f0f0f0" : "#ffffff"
|
|
||||||
radius: 8
|
|
||||||
border.width: 1
|
|
||||||
border.color: "#e0e0e0"
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: qsTr("取消")
|
|
||||||
font.pixelSize: 13
|
|
||||||
color: "#333333"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelUninstallMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: uninstallConfirmDialog.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制命令按钮
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 36
|
|
||||||
color: confirmUninstallMouse.containsMouse ? root.accentRed : Qt.rgba(220/255, 38/255, 38/255, 0.8)
|
|
||||||
radius: 8
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: qsTr("复制命令")
|
|
||||||
font.pixelSize: 13
|
|
||||||
font.weight: Font.Medium
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: confirmUninstallMouse
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
// 复制卸载命令到剪贴板
|
|
||||||
clipboardHelper.selectAll()
|
|
||||||
clipboardHelper.copy()
|
|
||||||
uninstallConfirmDialog.close()
|
|
||||||
// 卸载按钮文字临时替换为复制成功提示,5 秒后还原
|
|
||||||
settingsWindow.uninstallButtonText = qsTr("卸载命令已复制到剪贴板,请在终端中粘贴执行")
|
|
||||||
statusHideTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏的 TextEdit 用于复制卸载命令到剪贴板
|
|
||||||
// QML 没有直接剪贴板 API,用 TextEdit.selectAll()+copy() 实现
|
|
||||||
TextEdit {
|
|
||||||
id: clipboardHelper
|
|
||||||
visible: false
|
|
||||||
text: "sudo rm -rf /usr/share/dde-shell/space.jokul.JNetApplet/ && systemctl --user restart dde-shell@DDE"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卸载按钮文字还原定时器:复制命令后 5 秒将按钮文字从提示还原为"卸载插件"
|
|
||||||
Timer {
|
|
||||||
id: statusHideTimer
|
|
||||||
interval: 5000
|
|
||||||
onTriggered: settingsWindow.uninstallButtonText = qsTr("卸载插件")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击处理
|
// 点击处理
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
DS_BEGIN_NAMESPACE
|
DS_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -25,7 +28,22 @@ NetworkMonitorApplet::NetworkMonitorApplet(QObject *parent)
|
|||||||
, m_totalUpload(0)
|
, m_totalUpload(0)
|
||||||
, m_ready(false)
|
, m_ready(false)
|
||||||
, m_firstUpdate(true)
|
, m_firstUpdate(true)
|
||||||
|
, m_textColor()
|
||||||
{
|
{
|
||||||
|
// 从独立配置文件读取持久化的字体颜色
|
||||||
|
// 设计原因:使用独立配置文件避免污染 dde-shell 的共享配置,
|
||||||
|
// 空串表示"跟随系统",由 QML 层回退到 primaryText 实现
|
||||||
|
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)
|
||||||
|
+ QStringLiteral("/jnetapplet/settings.ini");
|
||||||
|
QSettings settings(configPath, QSettings::IniFormat);
|
||||||
|
if (settings.contains(QStringLiteral("textColor"))) {
|
||||||
|
const QString saved = settings.value(QStringLiteral("textColor")).toString();
|
||||||
|
// 空串合法(跟随系统),非空则校验是否为合法颜色值
|
||||||
|
if (saved.isEmpty() || QColor::isValidColorName(saved)) {
|
||||||
|
m_textColor = saved;
|
||||||
|
}
|
||||||
|
// 若校验失败则保持空串默认值(跟随系统),不抛出也不记录
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkMonitorApplet::~NetworkMonitorApplet()
|
NetworkMonitorApplet::~NetworkMonitorApplet()
|
||||||
@@ -127,6 +145,42 @@ QString NetworkMonitorApplet::version() const
|
|||||||
return pluginMetaData().value("Version").toString();
|
return pluginMetaData().value("Version").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 返回任务栏网速字体颜色,空串表示跟随系统主题
|
||||||
|
QString NetworkMonitorApplet::textColor() const
|
||||||
|
{
|
||||||
|
return m_textColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置任务栏网速字体颜色,空串表示跟随系统主题
|
||||||
|
// 设计原因:
|
||||||
|
// - 空串语义:空串作为"跟随系统"的语义值,QML 层用 textColor ? textColor : primaryText 做回退,
|
||||||
|
// 避免后端硬编码系统色
|
||||||
|
// - 校验逻辑:非空颜色值通过 QColor::isValidColor 校验,无效值直接忽略,不存储不发射信号
|
||||||
|
// - 独立配置文件:使用 ~/.config/jnetapplet/settings.ini 而非 dde-shell 共享配置,
|
||||||
|
// 避免污染其他组件的配置空间
|
||||||
|
void NetworkMonitorApplet::setTextColor(const QString &color)
|
||||||
|
{
|
||||||
|
// 空串视为合法(表示跟随系统),不校验
|
||||||
|
if (!color.isEmpty() && !QColor::isValidColorName(color)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_textColor == color) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_textColor = color;
|
||||||
|
|
||||||
|
// 持久化到独立配置文件
|
||||||
|
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)
|
||||||
|
+ QStringLiteral("/jnetapplet/settings.ini");
|
||||||
|
QSettings settings(configPath, QSettings::IniFormat);
|
||||||
|
settings.setValue(QStringLiteral("textColor"), m_textColor);
|
||||||
|
settings.sync();
|
||||||
|
|
||||||
|
emit textColorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
// 返回当前活动接口的下行速度历史,转换为 QVariantList of QPointF 供 QML 使用
|
// 返回当前活动接口的下行速度历史,转换为 QVariantList of QPointF 供 QML 使用
|
||||||
// 设计原因:QPointF 仅携带 x/y 两个值,上传与下载分别对应两个列表
|
// 设计原因:QPointF 仅携带 x/y 两个值,上传与下载分别对应两个列表
|
||||||
QVariantList NetworkMonitorApplet::speedHistoryDownload() const
|
QVariantList NetworkMonitorApplet::speedHistoryDownload() const
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
DS_BEGIN_NAMESPACE
|
DS_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -56,6 +58,9 @@ class NetworkMonitorApplet : public DApplet
|
|||||||
Q_PROPERTY(QString ipv6Address READ ipv6Address NOTIFY ipv6AddressChanged)
|
Q_PROPERTY(QString ipv6Address READ ipv6Address NOTIFY ipv6AddressChanged)
|
||||||
// 插件版本号,从 dde-shell 元数据读取,版本唯一源为 CMakeLists.txt project(VERSION)
|
// 插件版本号,从 dde-shell 元数据读取,版本唯一源为 CMakeLists.txt project(VERSION)
|
||||||
Q_PROPERTY(QString version READ version CONSTANT)
|
Q_PROPERTY(QString version READ version CONSTANT)
|
||||||
|
// 任务栏网速数值字体颜色,空串表示跟随系统主题(由 QML 回退到 primaryText),
|
||||||
|
// 用户在设置窗口选择后持久化到 ~/.config/jnetapplet/settings.ini
|
||||||
|
Q_PROPERTY(QString textColor READ textColor WRITE setTextColor NOTIFY textColorChanged)
|
||||||
// 当前活动接口的下行速度历史采样点(QPointF 列表:x=时间戳秒, y=速度 bytes/sec)
|
// 当前活动接口的下行速度历史采样点(QPointF 列表:x=时间戳秒, y=速度 bytes/sec)
|
||||||
// 用于趋势图绘制下载折线,每次 calculateSpeed 追加一个点并发射 speedHistoryChanged
|
// 用于趋势图绘制下载折线,每次 calculateSpeed 追加一个点并发射 speedHistoryChanged
|
||||||
Q_PROPERTY(QVariantList speedHistoryDownload READ speedHistoryDownload NOTIFY speedHistoryChanged)
|
Q_PROPERTY(QVariantList speedHistoryDownload READ speedHistoryDownload NOTIFY speedHistoryChanged)
|
||||||
@@ -85,6 +90,10 @@ public:
|
|||||||
QVariantList speedHistoryUpload() const;
|
QVariantList speedHistoryUpload() const;
|
||||||
// 返回插件版本号,从 dde-shell 插件元数据(metadata.json)读取
|
// 返回插件版本号,从 dde-shell 插件元数据(metadata.json)读取
|
||||||
QString version() const;
|
QString version() const;
|
||||||
|
// 返回任务栏网速字体颜色,空串表示跟随系统主题
|
||||||
|
QString textColor() const;
|
||||||
|
// 设置任务栏网速字体颜色,空串表示跟随系统主题
|
||||||
|
void setTextColor(const QString &color);
|
||||||
|
|
||||||
Q_INVOKABLE void refresh();
|
Q_INVOKABLE void refresh();
|
||||||
Q_INVOKABLE void setActiveInterface(const QString &interface);
|
Q_INVOKABLE void setActiveInterface(const QString &interface);
|
||||||
@@ -99,6 +108,8 @@ signals:
|
|||||||
void ipAddressChanged();
|
void ipAddressChanged();
|
||||||
void ipv6AddressChanged();
|
void ipv6AddressChanged();
|
||||||
void speedHistoryChanged();
|
void speedHistoryChanged();
|
||||||
|
// 任务栏网速字体颜色变化时通知 QML 更新
|
||||||
|
void textColorChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void readNetworkStats();
|
void readNetworkStats();
|
||||||
@@ -131,6 +142,10 @@ private:
|
|||||||
|
|
||||||
bool m_ready;
|
bool m_ready;
|
||||||
bool m_firstUpdate;
|
bool m_firstUpdate;
|
||||||
|
// 任务栏网速字体颜色,空串表示跟随系统主题
|
||||||
|
// 设计原因:空串作为"跟随系统"的语义值,QML 层用 textColor ? textColor : primaryText
|
||||||
|
// 做回退,避免后端硬编码系统色
|
||||||
|
QString m_textColor;
|
||||||
|
|
||||||
// 速度历史环形缓冲:每个接口独立维护一份,key 为接口名
|
// 速度历史环形缓冲:每个接口独立维护一份,key 为接口名
|
||||||
// 设计原因:接口切换时趋势图不出现跳变,切回时仍能看到该接口历史
|
// 设计原因:接口切换时趋势图不出现跳变,切回时仍能看到该接口历史
|
||||||
|
|||||||
Reference in New Issue
Block a user