基本界面达成

This commit is contained in:
2026-05-11 00:21:16 +08:00
parent 649677f564
commit 65f221a5d8
13 changed files with 1818 additions and 347 deletions

View File

@@ -15,63 +15,60 @@ class AIOPrrintSystemAPI:
except:
return None
def print(self, *args, **kwargs):
pass
def get_status(self):
test_data = {
'job': {
'job': {
'estimatedPrintTime': 1234,
'filament': {'length': 765, 'volume': 24356},
'file': {'display_name': 'Test File','date': None, 'name': '20260414135441_42bff5215c6148b8b5f4d8c4f15d5ddc.gcode', 'origin': 'local', 'path': None, 'size': 1468987},
'lastPrintTime': None,
'user': None
},
'progress': {
'completion': 74.8,
'filepos': 1234,
'printTime': 1235,
'printTimeLeft': 6353,
'printTimeLeftOrigin': 5366
},
'state': 'Operational'
},
'status': {
'sd': {'ready': False},
'state': {
'error': '',
'flags': {
'cancelling': False,
'closedOrError': False,
'error': False,
'finishing': False,
'operational': True,
'paused': False,
'pausing': False,
'printing': False,
'ready': True,
'resuming': False,
'sdReady': False
},
'text': 'Operational test'
},
'temperature': {
'bed': {'actual': 85, 'offset': 0, 'target': 56},
'tool0': {'actual': 0.0, 'offset': 0, 'target': 340}
}
}
}
return test_data
# test_data = {
# 'job': {
# 'job': {
# 'estimatedPrintTime': 1234,
# 'filament': {'length': 765, 'volume': 24356},
# 'file': {'display_name': 'Test File','date': None, 'name': '20260414135441_42bff5215c6148b8b5f4d8c4f15d5ddc.gcode', 'origin': 'local', 'path': None, 'size': 1468987},
# 'lastPrintTime': None,
# 'user': None
# },
# 'progress': {
# 'completion': 74.8,
# 'filepos': 1234,
# 'printTime': 1235,
# 'printTimeLeft': 6353,
# 'printTimeLeftOrigin': 5366
# },
# 'state': 'Operational'
# },
# 'status': {
# 'sd': {'ready': False},
# 'state': {
# 'error': '',
# 'flags': {
# 'cancelling': False,
# 'closedOrError': False,
# 'error': False,
# 'finishing': False,
# 'operational': True,
# 'paused': False,
# 'pausing': False,
# 'printing': False,
# 'ready': True,
# 'resuming': False,
# 'sdReady': False
# },
# 'text': 'Operational test'
# },
# 'temperature': {
# 'bed': {'actual': 85, 'offset': 0, 'target': 56},
# 'tool0': {'actual': 0.0, 'offset': 0, 'target': 340}
# }
# }
# }
# return test_data
# url = f"{self.api_url}/status"
# try:
# r = requests.get(url, headers=self.headers, timeout=5)
# r.raise_for_status()
# return r.json()
# except:
# return {"status": {}, "job": {}}
url = f"{self.api_url}/status"
try:
r = requests.get(url, headers=self.headers, timeout=5)
r.raise_for_status()
return r.json()
except:
return {"status": {}, "job": {}}
def pause_print(self):
return self._post_action("pause_print", action="pause")
@@ -79,4 +76,16 @@ class AIOPrrintSystemAPI:
def stop_print(self):
return self._post_action("cancel_print")
def home_axes(self, axes=["x", "y", "z"]):
return self._post_action("home_axes", axes=axes)
def auto_leveling(self):
return self._post_action("auto_leveling")
def send_gcode(self, gcode):
return self._post_action("send_gcode", gcode=gcode)
def off_motors(self):
return self.send_gcode("M84")

40
utils/auto_fan_status.py Normal file
View File

@@ -0,0 +1,40 @@
import json
from PyQt6.QtCore import QTimer
class AutoFanStatus:
def __init__(self, update_interval_ms=1000):
self.cpu_temp = 0.0
self.fan_speed = 0
self.fan_state = "Unknown"
self.fan_rpm = 0
self.is_auto_fan_service_running = False
self._last_temp = 0.0
self._same_counter = 0
self._update_interval_ms = update_interval_ms
self._timer = QTimer()
self._timer.setSingleShot(False)
self._timer.timeout.connect(self.update_status)
self._timer.start(self._update_interval_ms)
def update_status(self):
try:
with open("/dev/shm/fan_status.json", "r") as f:
data = json.load(f)
self.cpu_temp = data.get("temp", 0.0)
self.fan_speed = data.get("pwm", 0)
self.fan_state = "Stalled" if data.get("is_stalled", False) else "Running"
self.fan_rpm = data.get("rpm", 0)
if self.cpu_temp == self._last_temp:
self._same_counter += 1
else:
self._same_counter = 0
self._last_temp = self.cpu_temp
self.is_auto_fan_service_running = self._same_counter < 5
except (FileNotFoundError, json.JSONDecodeError, OSError):
self.cpu_temp = 0.0
self.fan_speed = 0
self.fan_state = "Unknown"
self.fan_rpm = 0
self.is_auto_fan_service_running = False

71
utils/config_parse.py Normal file
View File

@@ -0,0 +1,71 @@
import json
import os
from PyQt6.QtCore import QObject, pyqtSignal, QFileSystemWatcher
class ConfigParse(QObject):
"""配置文件解析与自动重载"""
config_changed = pyqtSignal(object) # 发送自身实例
def __init__(self, config_file="config.json"):
super().__init__()
self.config_file = config_file
self.api_url = None
self.api_key = None
self.gcode_dir = None
self.move_axis_area = None
self.move_max_speed = None
self.config = self._load_config()
self._parse_config()
# 使用 QFileSystemWatcher 监视文件变化
self._watcher = QFileSystemWatcher(self)
self._watch_path = os.path.abspath(self.config_file)
self._last_mtime = 0
self._start_watching()
def _start_watching(self):
"""启动文件监视"""
if not os.path.isfile(self._watch_path):
return
self._last_mtime = os.path.getmtime(self._watch_path)
if self._watch_path not in self._watcher.files():
self._watcher.addPath(self._watch_path)
self._watcher.fileChanged.connect(self._on_file_changed)
def _on_file_changed(self, path):
"""文件变化时重新加载(防止保存中多次触发,用 mtime 去抖动)"""
try:
mtime = os.path.getmtime(path)
if mtime <= self._last_mtime:
return
self._last_mtime = mtime
except OSError:
return
print("Config changed")
old_config = self.config
new_config = self._load_config()
if new_config == old_config:
return
self.config = new_config
self._parse_config()
self.config_changed.emit(self)
def _load_config(self):
try:
with open(self.config_file, "r") as f:
return json.load(f)
except Exception as e:
print(f"Error loading config: {e}")
return {}
def _parse_config(self):
self.api_url = self.config.get("api_url", None)
self.api_key = self.config.get("api_key", None)
self.gcode_dir = self.config.get("gcode_dir", None)
self.move_axis_area = self.config.get("move_axis_area", None)
self.move_max_speed = self.config.get("move_max_speed", {"x": 3000, "y": 3000, "z": 200})
self.move_max_speed = self.config.get("move_max_speed", None)

View File

@@ -8,12 +8,17 @@
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QApplication, QLineEdit, QTextEdit,
QApplication, QLineEdit, QTextEdit, QGridLayout
)
from PyQt6.QtCore import Qt, QEvent, QPoint, pyqtSignal
from PyQt6.QtCore import Qt, QEvent, QPoint, QTimer, pyqtSignal
from PyQt6.QtGui import QMouseEvent, QTextCursor
# ─── 长按重复配置 ─────────────────────────────────────────
LONG_PRESS_DELAY = 400 # 按住多少毫秒后开始重复
LONG_PRESS_REPEAT = 80 # 开始重复后每多少毫秒触发一次
# ─── 键盘布局定义 ──────────────────────────────────────────────
KEY_ROWS = {
@@ -31,14 +36,18 @@ KEY_ROWS = {
NUMBER_ROW = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
# 符号键:按 Shift 或符号切换时数字行互换
# 符号键:按 ?!(. 切换时替换数字行
SYMBOL_ROW = ["!", "@", "#", "$", "%", "^", "&", "*", "(", ")"]
# 显示用Qt 中 & 需要写 && 才能显示一个 &
SYMBOL_ROW_DISPLAY = ["!", "@", "#", "$", "%", "^", "&&", "*", "(", ")"]
# 右侧额外符号(放在字母区行末或单独行)
# 额外符号:切换到符号键盘后显示在字母区
EXTRA_SYMBOLS = [
["-", "=", "[", "]", "\\", ";", "'", ",", ".", "/"],
["_", "+", "{", "}", "|", ":", "\"", "<", ">", "?"],
]
# 常见后缀(放在符号键盘多余的字母位置上)
COMMON_SUFFIXES = [".com", ".cn", ".org", ".net", ".edu", ".co"]
KEY_STYLE = """
QPushButton {
@@ -64,7 +73,7 @@ QPushButton:pressed {
CTRL_KEY_STYLE = """
QPushButton {
min-width: 72px;
min-width: 52px;
min-height: 52px;
font-size: 18px;
font-weight: 600;
@@ -84,29 +93,9 @@ QPushButton:pressed {
}
"""
SPACE_STYLE = """
QPushButton {
min-width: 280px;
min-height: 52px;
font-size: 18px;
color: transparent;
background-color: #4a4a4a;
border: 2px solid #646464;
border-radius: 8px;
}
QPushButton:hover {
background-color: #5a5a5a;
border-color: #888888;
}
QPushButton:pressed {
background-color: #2f6f91;
border-color: #5a9fcf;
}
"""
ACTIVE_CTRL_STYLE = """
QPushButton {
min-width: 72px;
min-width: 52px;
min-height: 52px;
font-size: 18px;
font-weight: 600;
@@ -136,6 +125,9 @@ class FloatingKeyboard(QWidget):
| Qt.WindowType.Tool
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
# 防止键盘窗口/按钮抢走输入框焦点
self.setAttribute(Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.setStyleSheet("background: transparent;")
self._drag_pos = None
@@ -154,9 +146,9 @@ class FloatingKeyboard(QWidget):
outer.setStyleSheet(
"background-color: #333333; border: 2px solid #555555; border-radius: 12px;"
)
layout = QVBoxLayout(outer)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(4)
outer_layout = QVBoxLayout(outer)
outer_layout.setContentsMargins(8, 8, 8, 8)
outer_layout.setSpacing(4)
# ── 顶部拖拽条 ──
title_bar = QWidget()
@@ -168,8 +160,8 @@ class FloatingKeyboard(QWidget):
title_layout.setContentsMargins(8, 0, 8, 0)
drag_label = QPushButton("≡ 键盘")
drag_label.setStyleSheet(
"""
drag_label.setFocusPolicy(Qt.FocusPolicy.NoFocus)
drag_label.setStyleSheet("""
QPushButton {
background: transparent;
color: #aaaaaa;
@@ -177,15 +169,14 @@ class FloatingKeyboard(QWidget):
border: none;
text-align: left;
}
"""
)
""")
title_layout.addWidget(drag_label)
title_layout.addStretch()
close_btn = QPushButton("")
close_btn.setFixedSize(28, 28)
close_btn.setStyleSheet(
"""
close_btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
close_btn.setStyleSheet("""
QPushButton {
background: transparent;
color: #aaaaaa;
@@ -197,107 +188,175 @@ class FloatingKeyboard(QWidget):
background-color: #555555;
color: #ffffff;
}
"""
)
""")
close_btn.clicked.connect(self.hide)
title_layout.addWidget(close_btn)
layout.addWidget(title_bar)
outer_layout.addWidget(title_bar)
# ── 数字/符号行 ──
self.num_layout = QHBoxLayout()
self.num_layout.setSpacing(4)
# ── 内容区:左侧 4 行按键 + 右侧功能键 ──
content = QHBoxLayout()
content.setSpacing(6)
# ====== 左侧QGridLayout 4行×10列等宽列 ======
grid = QGridLayout()
grid.setSpacing(4)
for col in range(10):
grid.setColumnStretch(col, 1)
# Row 0: 数字行
self.num_buttons = []
for ch in NUMBER_ROW:
btn = self._make_key(ch)
for col, ch in enumerate(NUMBER_ROW):
btn = self._make_key(ch, ch)
self.num_buttons.append(btn)
self.num_layout.addWidget(btn)
layout.addLayout(self.num_layout)
grid.addWidget(btn, 0, col)
# ── 字母行 ──
self._letter_buttons = [] # flat list for shift toggle
for row_keys in KEY_ROWS["normal"]:
row_layout = QHBoxLayout()
row_layout.setSpacing(4)
row_layout.addStretch()
for ch in row_keys:
btn = self._make_key(ch)
self._letter_buttons.append(btn)
row_layout.addWidget(btn)
row_layout.addStretch()
layout.addLayout(row_layout)
# Row 1: 字母行 q ~ p
self._letter_buttons = []
for col, ch in enumerate(KEY_ROWS["normal"][0]):
btn = self._make_key(ch, ch)
self._letter_buttons.append(btn)
grid.addWidget(btn, 1, col)
# ── 额外符号行(- = [ ] 等) ──
self.extra_layout = QHBoxLayout()
self.extra_layout.setSpacing(4)
self.extra_layout.addStretch()
self.extra_buttons = []
for ch in EXTRA_SYMBOLS[0]:
btn = self._make_key(ch)
self.extra_buttons.append(btn)
self.extra_layout.addWidget(btn)
self.extra_layout.addStretch()
layout.addLayout(self.extra_layout)
# Row 2: 字母行 a ~ l (9个第10列空位留给符号模式)
for col, ch in enumerate(KEY_ROWS["normal"][1]):
btn = self._make_key(ch, ch)
self._letter_buttons.append(btn)
grid.addWidget(btn, 2, col)
# ── 功能键行 ──
ctrl_layout = QHBoxLayout()
ctrl_layout.setSpacing(4)
# Row 3: [?!(.] + 字母 z ~ m + [⇧] + [Caps]
self.sym_btn = self._make_ctrl_btn("?!(.", self._toggle_symbol)
grid.addWidget(self.sym_btn, 3, 0)
self.shift_btn = QPushButton("")
self.shift_btn.setStyleSheet(CTRL_KEY_STYLE)
self.shift_btn.clicked.connect(self._toggle_shift)
ctrl_layout.addWidget(self.shift_btn)
for i, ch in enumerate(KEY_ROWS["normal"][2]):
btn = self._make_key(ch, ch)
self._letter_buttons.append(btn)
grid.addWidget(btn, 3, i + 1)
self.caps_btn = QPushButton("A/a")
self.caps_btn.setStyleSheet(CTRL_KEY_STYLE)
self.caps_btn.clicked.connect(self._toggle_caps)
ctrl_layout.addWidget(self.caps_btn)
self.shift_btn = self._make_ctrl_btn("", self._toggle_shift)
grid.addWidget(self.shift_btn, 3, 8)
self.sym_btn = QPushButton("?123")
self.sym_btn.setStyleSheet(CTRL_KEY_STYLE)
self.sym_btn.clicked.connect(self._toggle_symbol)
ctrl_layout.addWidget(self.sym_btn)
self.caps_btn = self._make_ctrl_btn("Caps", self._toggle_caps)
grid.addWidget(self.caps_btn, 3, 9)
backspace_btn = QPushButton("")
backspace_btn.setStyleSheet(CTRL_KEY_STYLE)
# 各行等高分占布局
for row in range(4):
grid.setRowStretch(row, 1)
content.addLayout(grid)
# ====== 右侧4个竖向功能键拉伸填满高度 ======
right_col = QVBoxLayout()
right_col.setSpacing(4)
RIGHT_BTN_STYLE = """
QPushButton {
min-width: 80px;
min-height: 52px;
font-size: 18px;
font-weight: 600;
color: #f2f2f2;
background-color: #555555;
border: 2px solid #707070;
border-radius: 8px;
padding: 4px 8px;
}
QPushButton:hover {
background-color: #666666;
border-color: #909090;
}
QPushButton:pressed {
background-color: #2f6f91;
border-color: #5a9fcf;
}
"""
backspace_btn = QPushButton("⌫ 退格")
backspace_btn.setStyleSheet(RIGHT_BTN_STYLE)
backspace_btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
backspace_btn.clicked.connect(lambda: self._send_key("\b"))
ctrl_layout.addWidget(backspace_btn)
self._enable_long_press(backspace_btn, lambda: self._send_key("\b"))
right_col.addWidget(backspace_btn, stretch=1)
ctrl_layout.addStretch()
del_btn = QPushButton("Del")
del_btn.setStyleSheet(RIGHT_BTN_STYLE)
del_btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
del_btn.clicked.connect(lambda: self._send_key("\x7f"))
self._enable_long_press(del_btn, lambda: self._send_key("\x7f"))
right_col.addWidget(del_btn, stretch=1)
enter_btn = QPushButton("↵ 回车")
enter_btn.setStyleSheet(CTRL_KEY_STYLE)
enter_btn.setStyleSheet(RIGHT_BTN_STYLE)
enter_btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
enter_btn.clicked.connect(lambda: self._send_key("\n"))
ctrl_layout.addWidget(enter_btn)
self._enable_long_press(enter_btn, lambda: self._send_key("\n"))
right_col.addWidget(enter_btn, stretch=1)
layout.addLayout(ctrl_layout)
# ── 空格行 ──
space_layout = QHBoxLayout()
space_layout.setSpacing(4)
space_layout.addStretch()
self.space_btn = QPushButton(" ") # full-width space as placeholder
self.space_btn.setStyleSheet(SPACE_STYLE)
self.space_btn = QPushButton(" 空格")
self.space_btn.setStyleSheet(RIGHT_BTN_STYLE)
self.space_btn.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.space_btn.clicked.connect(lambda: self._send_key(" "))
space_layout.addWidget(self.space_btn)
self._enable_long_press(self.space_btn, lambda: self._send_key(" "))
right_col.addWidget(self.space_btn, stretch=1)
space_layout.addStretch()
layout.addLayout(space_layout)
content.addLayout(right_col)
outer_layout.addLayout(content)
outer_layout = QVBoxLayout(self)
outer_layout.setContentsMargins(0, 0, 0, 0)
outer_layout.addWidget(outer)
outer_container = QVBoxLayout(self)
outer_container.setContentsMargins(0, 0, 0, 0)
outer_container.addWidget(outer)
self.setFixedWidth(680)
self.setFixedWidth(840)
# ── 按键工厂 ──
def _make_key(self, text):
def _make_key(self, text, send_text=None):
"""创建按键send_text 为实际发送字符(如 & 按钮显示 && 但发送 &"""
btn = QPushButton(text)
btn.setStyleSheet(KEY_STYLE)
btn.clicked.connect(lambda checked, t=text: self._send_key(t))
btn.setFocusPolicy(Qt.FocusPolicy.NoFocus) # 不抢输入框焦点
btn._real_text = send_text if send_text is not None else text
btn.clicked.connect(lambda: self._send_key(btn._real_text))
# 普通按键自动加长按
self._enable_long_press(btn, lambda: self._send_key(btn._real_text))
return btn
@staticmethod
def _make_ctrl_btn(text, callback):
"""创建控制类按钮(?!(./⇧/Caps等"""
btn = QPushButton(text)
btn.setStyleSheet(CTRL_KEY_STYLE)
btn.setFocusPolicy(Qt.FocusPolicy.NoFocus) # 不抢输入框焦点
btn.clicked.connect(callback)
return btn
def _enable_long_press(self, btn, callback):
"""为按键添加长按连续触发功能(按住 LONG_PRESS_DELAY ms 后每 LONG_PRESS_REPEAT ms 重复)
callback 为点击/重复时执行的 callable无参数
"""
timer = QTimer(btn)
timer.setSingleShot(True)
btn._repeat_timer = timer
def on_pressed():
timer.setSingleShot(True)
timer.setInterval(LONG_PRESS_DELAY)
timer.start()
def on_released():
timer.stop()
def on_timer():
# 首次超时:进入快速重复模式
if timer.isSingleShot():
timer.setSingleShot(False)
timer.setInterval(LONG_PRESS_REPEAT)
timer.start()
callback()
btn.pressed.connect(on_pressed)
btn.released.connect(on_released)
timer.timeout.connect(on_timer)
# ── 按键发送 ──
def _send_key(self, text):
@@ -305,6 +364,9 @@ class FloatingKeyboard(QWidget):
if text == "\b":
self._backspace()
return
if text == "\x7f":
self._delete_forward()
return
if self._symbol_mode:
self._toggle_symbol()
@@ -323,6 +385,10 @@ class FloatingKeyboard(QWidget):
tc.insertText(text)
widget.setTextCursor(tc)
# 上档键:激活一次后自动取消
if self._shift_on:
self._toggle_shift()
def _backspace(self):
widget = self._get_target()
if widget is None:
@@ -344,6 +410,25 @@ class FloatingKeyboard(QWidget):
tc.movePosition(QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor)
tc.removeSelectedText()
def _delete_forward(self):
"""向前删除(删除光标后的字符)"""
widget = self._get_target()
if widget is None:
return
if isinstance(widget, QLineEdit):
cursor = widget.cursorPosition()
current = widget.text()
if cursor < len(current):
new_text = current[:cursor] + current[cursor + 1:]
widget.setText(new_text)
widget.setCursorPosition(cursor)
elif isinstance(widget, QTextEdit):
tc = widget.textCursor()
if not tc.hasSelection():
tc.movePosition(QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor)
tc.removeSelectedText()
def _get_target(self):
"""获取当前有效的目标输入控件"""
if self._target_widget and self._target_widget.hasFocus():
@@ -368,26 +453,47 @@ class FloatingKeyboard(QWidget):
def _toggle_symbol(self):
self._symbol_mode = not self._symbol_mode
if self._symbol_mode:
for btn, ch in zip(self.num_buttons, SYMBOL_ROW):
btn.setText(ch)
for btn, ch in zip(self.extra_buttons, EXTRA_SYMBOLS[1]):
btn.setText(ch)
# 进入符号键盘
# Row 0: 数字行 → SYMBOL_ROW
for btn, ch, disp in zip(self.num_buttons, SYMBOL_ROW, SYMBOL_ROW_DISPLAY):
btn.setText(disp)
btn._real_text = ch
# Row 1: 字母行 q-p (indices 0-9) → EXTRA_SYMBOLS[0]
for i, ch in enumerate(EXTRA_SYMBOLS[0]):
self._letter_buttons[i].setText(ch)
self._letter_buttons[i]._real_text = ch
# Row 2: 字母行 a-l (indices 10-18) → EXTRA_SYMBOLS[1][0:9]
for i, ch in enumerate(EXTRA_SYMBOLS[1][:9]):
self._letter_buttons[10 + i].setText(ch)
self._letter_buttons[10 + i]._real_text = ch
# Row 3: z-m (indices 19-25) → EXTRA_SYMBOLS[1][9] + 后缀
self._letter_buttons[19].setText(EXTRA_SYMBOLS[1][9])
self._letter_buttons[19]._real_text = EXTRA_SYMBOLS[1][9]
for i, suffix in enumerate(COMMON_SUFFIXES):
self._letter_buttons[20 + i].setText(suffix)
self._letter_buttons[20 + i]._real_text = suffix
self.sym_btn.setStyleSheet(ACTIVE_CTRL_STYLE)
self.sym_btn.setText("ABC")
else:
# 退出符号键盘 → 恢复数字和字母
for btn, ch in zip(self.num_buttons, NUMBER_ROW):
btn.setText(ch)
for btn, ch in zip(self.extra_buttons, EXTRA_SYMBOLS[0]):
btn.setText(ch)
btn._real_text = ch
# 字母区根据当前 shift/caps 状态恢复
self._apply_shift_caps()
self.sym_btn.setStyleSheet(CTRL_KEY_STYLE)
self.sym_btn.setText("?123")
self.sym_btn.setText("?!(.")
def _apply_shift_caps(self):
if self._symbol_mode:
return # 符号键盘下不改变字母区显示
use_shift = self._shift_on != self._caps_on # XOR
rows = KEY_ROWS["shift"] if use_shift else KEY_ROWS["normal"]
flat = [ch for row in rows for ch in row]
for btn, ch in zip(self._letter_buttons, flat):
btn.setText(ch)
btn._real_text = ch
# ── 窗口拖拽 ──

View File

@@ -17,13 +17,13 @@ class WifiManager:
return ""
def list_saved_networks(self):
"""列出已保存的 wifi"""
"""列出已保存的 wifiwpa_cli list_networks 以 Tab 分隔)"""
output = self._run_wpa_cli("list_networks")
networks = []
lines = output.splitlines()
if len(lines) > 1:
for line in lines[1:]: # skip header
parts = line.split()
parts = line.split('\t')
if len(parts) >= 4:
networks.append({
"network_id": parts[0],