Compare commits

..
8 Commits
Author SHA1 Message Date
kushidou 75727afd24 优化日志采集工具 2026-06-17 09:51:22 +08:00
kushidou 2e56fee25f 调整日志采集脚本 2026-06-16 17:03:16 +08:00
kushidou fef52006a0 完善日志采集功能 2026-06-16 16:49:13 +08:00
tang1219 06e250e52e 更新Readme 2026-06-16 15:13:04 +08:00
tang1219 29b4d47355 重构项目源代码存储结构 2026-06-16 14:59:58 +08:00
tang1219 2dcac48623 优化打包流程 2026-06-16 14:51:34 +08:00
tang1219 34157e19da 优化usb外设插入时重复触发的问题 2026-06-16 14:07:40 +08:00
tang1219 98390053fd 添加设备名关键词匹配 2026-06-16 13:57:42 +08:00
25 changed files with 937 additions and 706 deletions
+4 -227
View File
@@ -1,4 +1,3 @@
<<<<<<< HEAD
# ---> C++ # ---> C++
# Prerequisites # Prerequisites
*.d *.d
@@ -8,28 +7,11 @@
*.lo *.lo
*.o *.o
*.obj *.obj
=======
# ---> C
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
>>>>>>> 07cb496a46dea18227a9ad14f0f91254054d9285
# Precompiled Headers # Precompiled Headers
*.gch *.gch
*.pch *.pch
<<<<<<< HEAD
# Compiled Dynamic libraries # Compiled Dynamic libraries
*.so *.so
*.dylib *.dylib
@@ -44,25 +26,11 @@
*.la *.la
*.a *.a
*.lib *.lib
=======
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
>>>>>>> 07cb496a46dea18227a9ad14f0f91254054d9285
# Executables # Executables
*.exe *.exe
*.out *.out
*.app *.app
<<<<<<< HEAD
.vscode .vscode
@@ -74,200 +42,9 @@ screen_ds_once
touch_ds touch_ds
touch_set touch_set
usb_ds usb_ds
======= check_save
*.i*86
*.x86_64
*.hex
# Debug files .codewhale
*.dSYM/ .mimocode
*.su
*.idb
*.pdb
# Kernel Module Compile Results *.deb
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
>>>>>>> 07cb496a46dea18227a9ad14f0f91254054d9285
+1 -1
View File
@@ -1,5 +1,5 @@
Package: ktouch Package: ktouch
Version: 2.0 Version: 2.1
Architecture: arm64 Architecture: arm64
Maintainer: Kushidou <kushidou@small09.top> Maintainer: Kushidou <kushidou@small09.top>
Depends: xinput, python3 Depends: xinput, python3
+128 -6
View File
@@ -1,7 +1,129 @@
# ktouchV2 # ktouch V2
<<<<<<< HEAD 适配 UOS 和麒麟 V10 的触摸屏映射校准工具。支持多屏环境下自动检测触摸屏与显示器对应关系,计算校准矩阵并写入系统,解决触摸紊乱问题。
适配UOS和银河麒麟的触摸屏设置工具。
======= ## 功能
适配UOS和麒麟的触摸屏校准设置工具。
>>>>>>> 07cb496a46dea18227a9ad14f0f91254054d9285 - **图形化配置** — 全屏窗口引导用户依次点击各显示器,自动建立触摸屏与显示器的映射
- **后台守护** — systemd 服务持续监控屏幕/触摸屏/USB 变化,自动触发重新校准
- **显示器管理** — 内置显示器设置工具(kdisplay),支持分辨率、旋转方向调整
- **旋转适配** — 校准矩阵自动适配屏幕旋转(横屏/竖屏/倒置)
- **签字笔支持** — 传统方式配置签字笔设备
- **现场诊断** — kscreen-debug 脚本快速排错,kscreen-log 采集日志
## 架构
```
opt/ktouch/
├── kscreen-remap-daemon # ← 主控进程(Python),systemd 服务入口
├── kscreen-setup # 配置脚本:拉起 screen_binder → 解析结果 → 写入 config
├── kscreen-remap # 单次校准:读取 config → 计算矩阵 → 写入 libinput
├── screen_binder # GUI:全屏窗口 + 触摸检测,输出显示器-触摸屏对应表
├── screen_ds # 显示器监控:检测分辨率/坐标变化,写入 screen.txt
├── screen_ds_once # 单次更新显示器信息(screen_ds 阉割版)
├── touch_ds # 触摸屏监控:检测矩阵变化,写入 touchmap.txt
├── touch_set # 校准计算:根据配置计算变换矩阵,写入 libinput
├── usb_ds # USB 监控:检测触摸屏插拔,写入 usbadd.txt
├── check_save # 二次确认窗口:允许用户放弃本次校准
├── kdisplay # 显示器设置 GUI 工具
├── display_monitor.py # 分辨率变化监控脚本
├── kscreen-debug # 现场排错脚本
├── kscreen-log # 日志采集脚本
├── kscreen-fix-daemon # 服务修复脚本
├── desktops/ # 桌面快捷方式
├── forest-light/ # 主题文件
└── config # 触摸屏配置文件
```
**运行流程:**
1. `kscreen-remap-daemon` 作为 systemd 服务启动,拉起各监控进程
2. 高频轮询 `screen.txt`(显示器变化)、`usbadd.txt`USB 插拔)
3. 低频轮询 `touch_ds` 检测触摸矩阵变化
4. 任一条件触发 → `kscreen-remap` 执行校准
5. 用户可通过快捷方式手动触发 `kscreen-setup` 重新配置映射
## 显示器设置(UOS 专属)
UOS 环境下可通过 `kdisplay` 调用系统显示服务(`com.deepin.daemon.Display`)调整显示器参数,与触摸校准联动。
```
kdisplay ───DBus──► com.deepin.daemon.Display
│ │
│ GUI 操作 ├── 分辨率 / 旋转 / 主屏切换
│ · 刷新检测 ├── SwitchMode 合并显示模式
│ · 设置分辨率 └── Save 持久化
│ · 旋转方向
│ · 指定主显示器 display_monitor.py
│ · 自定义分辨率 后台持续检测分辨率变化
│ 通知 kscreen-remap-daemon 重新校准
└── 应用并保存 → 重启显示服务
```
**主要功能:**
| 功能 | 说明 |
|------|------|
| 分辨率设置 | 列出当前显示器支持的 60Hz 模式,支持自定义分辨率 |
| 旋转方向 | 支持正常 / 左转90° / 右转90° / 翻转 |
| 主显示器 | 可指定任意已启用的显示器为主显示器 |
| 自定义分辨率 | 非标分辨率写入 `display_config/` 目录,重启后保持 |
| 排列示意图 | 右侧画布实时展示多屏几何排列 |
自定义分辨率配置文件存放在 `opt/ktouch/display_config/`,格式为 `<显示器名>`
**用法:**
```bash
# GUI 方式
/opt/ktouch/kdisplay
# 命令行快捷方式
kdisplay
```
> 仅在 UOS 环境下可用(依赖 D-Bus 服务 `com.deepin.daemon.Display`)。
> 麒麟 V10 不支持此功能。
## 编译
```bash
# 依赖
sudo apt install libinput-tools python3-evdev python3-pyudev
sudo apt install libevdev-dev libinput-dev libmtdev-dev libudev-dev libwacom-dev
# 编译
cd src
make # 仅编译到 src/
make install # 编译并安装到 ../opt/ktouch/
make clean
```
## 打包
```bash
# 先确保二进制已编译安装
cd src && make install
# 打包为 .deb
chmod +x build_deb.sh
./build_deb.sh # 输出到当前目录
./build_deb.sh ~/dist/ # 输出到指定目录
```
脚本自动从 `DEBIAN/control` 读取包名、版本、架构,生成 `ktouch_<版本>_<架构>.deb`
## 安装
```bash
sudo dpkg -i ktouch_*.deb
# postinst 会自动配置 systemd 服务、快捷方式、用户组权限
# 安装完成后需重启以使 input 组生效
```
## License
MIT
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
set -e
# ============ 打包 ktouch deb ============
# 用法: ./build_deb.sh [输出目录]
# 输出: ktouch_<版本>_<架构>.deb
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OUTPUT_DIR="${1:-$(pwd)}"
BUILD_DIR="/tmp/ktouch_deb_build"
# 从 control 读取版本和架构
CONTROL_FILE="$SCRIPT_DIR/DEBIAN/control"
if [ ! -f "$CONTROL_FILE" ]; then
echo "错误: 找不到 $CONTROL_FILE"
exit 1
fi
VERSION=$(grep -i "^Version:" "$CONTROL_FILE" | awk '{print $2}')
ARCH=$(grep -i "^Architecture:" "$CONTROL_FILE" | awk '{print $2}')
PACKAGE=$(grep -i "^Package:" "$CONTROL_FILE" | awk '{print $2}')
DEB_FILE="${PACKAGE}_${VERSION}_${ARCH}.deb"
echo "============================================"
echo " 打包 $PACKAGE v$VERSION ($ARCH)"
echo "============================================"
# 1. 清理旧构建目录
if [ -d "$BUILD_DIR" ]; then
echo "[1/5] 清理旧构建目录..."
rm -rf "$BUILD_DIR"
fi
# 2. 创建目录结构并拷贝文件
echo "[2/5] 创建构建目录并拷贝文件..."
mkdir -p "$BUILD_DIR/DEBIAN"
mkdir -p "$BUILD_DIR/opt"
mkdir -p "$BUILD_DIR/usr/share/doc/ktouch"
cp -r "$SCRIPT_DIR/DEBIAN/"* "$BUILD_DIR/DEBIAN/"
cp -r "$SCRIPT_DIR/opt/ktouch" "$BUILD_DIR/opt/"
# 排除不应打包的目录
rm -rf "$BUILD_DIR/opt/ktouch/.codewhale"
[ -f "$SCRIPT_DIR/changelog" ] && cp "$SCRIPT_DIR/changelog" "$BUILD_DIR/usr/share/doc/ktouch/changelog"
[ -f "$SCRIPT_DIR/LICENSE" ] && cp "$SCRIPT_DIR/LICENSE" "$BUILD_DIR/usr/share/doc/ktouch/copyright"
# 3. 设置权限
echo "[3/5] 设置文件权限..."
chmod 755 "$BUILD_DIR/DEBIAN"
chmod 644 "$BUILD_DIR/DEBIAN/control"
for script in preinst postinst prerm postrm; do
[ -f "$BUILD_DIR/DEBIAN/$script" ] && chmod 755 "$BUILD_DIR/DEBIAN/$script"
done
# # opt 下所有文件默认 644,目录 755
# find "$BUILD_DIR/opt" -type d -exec chmod 755 {} \;
# find "$BUILD_DIR/opt" -type f -exec chmod 644 {} \;
# # 给可执行程序设置 755
# EXE_LIST=(
# check_save
# screen_binder
# screen_ds
# screen_ds_once
# touch_ds
# touch_set
# usb_ds
# kdisplay
# kscreen-remap
# kscreen-remap-daemon
# kscreen-setup
# kscreen-debug
# kscreen-log
# kscreen-fix-daemon
# display_monitor.py
# )
# for exe in "${EXE_LIST[@]}"; do
# [ -f "$BUILD_DIR/opt/ktouch/$exe" ] && chmod 755 "$BUILD_DIR/opt/ktouch/$exe"
# done
# usr 下文档 644,目录 755
find "$BUILD_DIR/usr" -type d -exec chmod 755 {} \;
find "$BUILD_DIR/usr" -type f -exec chmod 644 {} \;
# 4. dpkg 打包
echo "[4/5] 执行 dpkg-deb 打包..."
dpkg-deb -b "$BUILD_DIR" "$DEB_FILE"
# 5. 清理
echo "[5/5] 清理临时目录..."
rm -rf "$BUILD_DIR"
# 移动到输出目录
if [ "$(realpath "$OUTPUT_DIR")" != "$(realpath "$(pwd)")" ]; then
mv "$DEB_FILE" "$OUTPUT_DIR/"
fi
echo ""
echo "============================================"
echo " 打包完成: $OUTPUT_DIR/$DEB_FILE"
echo " 大小: $(du -h "$OUTPUT_DIR/$DEB_FILE" | cut -f1)"
echo "============================================"
+16
View File
@@ -0,0 +1,16 @@
ktouch (2.1) unstable; urgency=medium
* 适配只支持鼠标事件的屏幕
* 优化开发架构,发布的程序不再包含源代码
-- Kushidou <kushidou@small09.top> Tue, 16 Jun 2026 14:00:00 +0800
ktouch (2.0) unstable; urgency=medium
* 重新设计交互流程,更傻瓜易用
* 重构程序架构,采用更多原生可执行程序
* 为UOS添加显示器分辨率和位置设置工具
* 程序本体适配UOS和麒麟v10
-- Kushidou <kushidou@small09.top> Mon, 25 Sep 2025 10:00:00 +0800
+190 -46
View File
@@ -1,64 +1,208 @@
#!/bin/bash #!/bin/bash
# 搜集日志和配置文件,用于debug分析 # ============================================
# ktouch 日志采集与打包脚本
# 按类别分目录存储,方便排查触摸屏问题
# ============================================
log_path="/opt/ktouch" set -e
now=$(date "+%m%d-%H%M%S")
LOG_PATH="/opt/ktouch"
NOW=$(date "+%m%d-%H%M%S")
TMP_BASE="/tmp/klog_$NOW"
tdir="/tmp/klog_$now" # ---------- 子目录定义 ----------
DIR_SYSTEM="$TMP_BASE/system"
DIR_HARDWARE="$TMP_BASE/hardware"
DIR_APP="$TMP_BASE/app"
DIR_PROGRAM="$TMP_BASE/program"
mkdir $tdir && cd $tdir mkdir -p "$DIR_SYSTEM" "$DIR_HARDWARE" "$DIR_APP" "$DIR_PROGRAM"
echo "正在集日志文件,请注意需要输入sudo密码。" echo "正在集日志(部分操作需 sudo..."
# cp /opt/ktouch/*log* $tdir # ============================================
# cp /opt/ktouch/config $tdir # 1. 系统日志 ─── system/
# cp /opt/ktouch/*.txt $tdir # ============================================
echo " 采集系统日志..."
# journalctl(系统服务日志)
# 采集系统信息 if command -v journalctl &>/dev/null; then
export DISPLAY=:0 sudo journalctl -b -n 5000 --no-pager > "$DIR_SYSTEM/journal_boot.txt" 2>/dev/null || true
xinput > $tdir/xinput_info.txt sudo journalctl -u ktouch_daemon --no-pager > "$DIR_SYSTEM/journal_ktouch.txt" 2>/dev/null || true
for id in $(xinput list --id-only); do sudo journalctl -u lightdm --no-pager > "$DIR_SYSTEM/journal_lightdm.txt" 2>/dev/null || true
xinput --list-props "$id" >> $tdir/xinput_props.txt sudo journalctl -u sddm --no-pager > "$DIR_SYSTEM/journal_sddm.txt" 2>/dev/null || true
echo -e "\n\n" $tdir/xinput_props.txt
done
xrandr > $tdir/xrandr_info.txt
sudo dmidecode > $tdir/dmidecode.txt
lsusb >> $tdir/lsusb.txt
lsusb -t >> $tdir/lsusb.txt
lsblk -f >> $tdir/lsblk.txt
lspci >> $tdir/lspci.txt
if [ -f /etc/kylin-release ]; then
cat /etc/os-release /etc/kylin-release 2>/dev/null >> $tdir/os-version.txt
elif [ -f /etc/uos-release ]; then
cat /etc/os-release /etc/uos-release /etc/os-version 2>/dev/null >> $tdir/os-version.txt
else
cat /etc/os-release 2>/dev/null >> $tdir/os-version.txt
fi fi
# 采集系统的日志 # dmesg 内核日志
sudo cp /var/log/messages* $tdir >/dev/null 2>&1 dmesg > "$DIR_SYSTEM/dmesg.txt" 2>/dev/null || true
sudo cp /var/log/boot* $tdir >/dev/null 2>&1
sudo cp /var/log/kern* $tdir >/dev/null 2>&1
sudo cp /var/log/Xorg* $tdir >/dev/null 2>&1
sudo cp /var/log/syslog* $tdir >/dev/null 2>&1
# 传统系统日志文件
sudo cp /var/log/syslog* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/messages* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/kern* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/boot* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/Xorg* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/dpkg* "$DIR_SYSTEM/" 2>/dev/null || true
sudo cp /var/log/apt/* "$DIR_SYSTEM/" 2>/dev/null || true
# 将程序和日志整体打包 # 显示管理器日志
sudo cp /etc/systemd/system/ktouch_daemon.service $tdir/ktouch_daemon.service [ -d /var/log/lightdm ] && sudo cp -r /var/log/lightdm "$DIR_SYSTEM/" 2>/dev/null || true
sudo chmod 777 -R $tdir [ -d /var/log/sddm ] && sudo cp -r /var/log/sddm "$DIR_SYSTEM/" 2>/dev/null || true
cp -r /opt/ktouch $tdir/ktouch
# 系统版本
if [ -f /etc/kylin-release ]; then
cat /etc/os-release /etc/kylin-release 2>/dev/null > "$DIR_SYSTEM/os_version.txt" || true
elif [ -f /etc/uos-release ]; then
cat /etc/os-release /etc/uos-release /etc/os-version 2>/dev/null > "$DIR_SYSTEM/os_version.txt" || true
else
cat /etc/os-release 2>/dev/null > "$DIR_SYSTEM/os_version.txt" || true
fi
ls -la ./ >> $tdir/fileinfo.txt # ============================================
ls -la ./ktouch >> $tdir/fileinfo.txt # 2. 硬件信息 ─── hardware/
# ============================================
echo " 采集硬件信息..."
export DISPLAY=:0
# X11 输入设备列表 + 属性(按设备分离)
xinput list > "$DIR_HARDWARE/xinput_list.txt" 2>/dev/null || true
for id in $(xinput list --id-only 2>/dev/null); do
echo "========== device id: $id ==========" >> "$DIR_HARDWARE/xinput_props.txt"
xinput list-props "$id" >> "$DIR_HARDWARE/xinput_props.txt" 2>/dev/null || true
echo "" >> "$DIR_HARDWARE/xinput_props.txt"
done
# xrandr 显示器信息
xrandr --verbose > "$DIR_HARDWARE/xrandr.txt" 2>/dev/null || true
# libinput 设备列表
libinput list-devices > "$DIR_HARDWARE/libinput_devices.txt" 2>/dev/null || true
# /proc/bus/input/devices(内核视角的输入设备)
cat /proc/bus/input/devices > "$DIR_HARDWARE/input_devices.txt" 2>/dev/null || true
# 每个 event 设备的 udev 信息
mkdir -p "$DIR_HARDWARE/evdev/"
for ev in /dev/input/event*; do
[ -e "$ev" ] || continue
name=$(basename "$ev")
echo "========== 设备路径: $ev ==========" >> "$DIR_HARDWARE/evdev/${name}_udev.txt"
udevadm info --query=all --name="$ev" >> "$DIR_HARDWARE/evdev/${name}_udev.txt" 2>/dev/null || true
# 尝试用 evtest 采集设备支持的事件类型(需要 root)
if command -v evtest &>/dev/null; then
sudo evtest --info "$ev" > "$DIR_HARDWARE/evdev/${name}_evtest.txt" 2>/dev/null || true &
fi
done
wait 2>/dev/null || true
# 每个 event 设备的能力位(capabilities
for ev in /dev/input/event*; do
[ -e "$ev" ] || continue
name=$(basename "$ev")
echo "========== $ev ==========" >> "$DIR_HARDWARE/evdev/${name}_caps.txt"
if command -v evemu-describe &>/dev/null; then
evemu-describe "$ev" >> "$DIR_HARDWARE/evdev/${name}_caps.txt" 2>/dev/null || true
fi
done
# DRM 显示器信息
mkdir -p "$DIR_HARDWARE/drm"
for card in /sys/class/drm/card*-*; do
[ -e "$card" ] || continue
card_name=$(basename "$card")
mkdir -p "$DIR_HARDWARE/drm/$card_name"
for attr in status enabled modes dpms edid; do
[ -f "$card/$attr" ] && cp "$card/$attr" "$DIR_HARDWARE/drm/$card_name/$attr" 2>/dev/null || true
done
done
# USB 设备树
lsusb > "$DIR_HARDWARE/lsusb.txt" 2>/dev/null || true
lsusb -t >> "$DIR_HARDWARE/lsusb.txt" 2>/dev/null || true
lsusb -v > "$DIR_HARDWARE/lsusb_verbose.txt" 2>/dev/null || true
# PCI 设备
lspci -k > "$DIR_HARDWARE/lspci.txt" 2>/dev/null || true
# 块设备
lsblk -f > "$DIR_HARDWARE/lsblk.txt" 2>/dev/null || true
# 硬件信息(需要 root
sudo dmidecode > "$DIR_HARDWARE/dmidecode.txt" 2>/dev/null || true
# udev 规则
[ -d /etc/udev/rules.d ] && cp -r /etc/udev/rules.d "$DIR_HARDWARE/udev_rules/" 2>/dev/null || true
[ -d /lib/udev/rules.d ] && cp -r /lib/udev/rules.d "$DIR_HARDWARE/udev_rules_lib/" 2>/dev/null || true
echo "程序版本: ktouch V2" > "$DIR_HARDWARE/ktouch_version.txt"
[ -f "$LOG_PATH/kscreen-remap-daemon" ] && head -5 "$LOG_PATH/kscreen-remap-daemon" >> "$DIR_HARDWARE/ktouch_version.txt" 2>/dev/null || true
# ============================================
# 3. 程序日志与配置 ─── app/
# ============================================
echo " 采集程序日志和配置..."
# 程序日志
[ -f "$LOG_PATH/touchscreen.log" ] && cp "$LOG_PATH/touchscreen.log" "$DIR_APP/touchscreen.log" 2>/dev/null || true
[ -f "$LOG_PATH/install.log" ] && cp "$LOG_PATH/install.log" "$DIR_APP/install.log" 2>/dev/null || true
[ -f "$LOG_PATH/sub_modules.log" ] && cp "$LOG_PATH/sub_modules.log" "$DIR_APP/sub_modules.log" 2>/dev/null || true
# 日志轮转文件
for f in "$LOG_PATH/"*.log.*; do
[ -f "$f" ] && cp "$f" "$DIR_APP/" 2>/dev/null || true
done
# 触摸屏配置
[ -f "$LOG_PATH/config" ] && cp "$LOG_PATH/config" "$DIR_APP/config" 2>/dev/null || true
[ -f "$LOG_PATH/touch_dis_table.txt" ] && cp "$LOG_PATH/touch_dis_table.txt" "$DIR_APP/" 2>/dev/null || true
# 显示器自定义分辨率配置
[ -d "$LOG_PATH/display_config" ] && cp -r "$LOG_PATH/display_config" "$DIR_APP/display_config" 2>/dev/null || true
# /tmp/ktouch 缓存文件
if [ -d /tmp/ktouch ]; then
mkdir -p "$DIR_APP/tmp_cache"
cp -r /tmp/ktouch/* "$DIR_APP/tmp_cache/" 2>/dev/null || true
fi
# systemd 服务文件
[ -f /etc/systemd/system/ktouch_daemon.service ] && sudo cp /etc/systemd/system/ktouch_daemon.service "$DIR_APP/service_file" 2>/dev/null || true
# ============================================
# 4. 程序本体 ─── program/
# ============================================
echo " 采集程序本体..."
# 完整拷贝 /opt/ktouch(检查文件完整性 + 配置文件正确性)
cp -r /opt/ktouch "$DIR_PROGRAM/ktouch"
# 同时生成 md5 清单,方便快速比对
find /opt/ktouch -type f -exec md5sum {} \; 2>/dev/null | sort > "$DIR_PROGRAM/file_md5.txt" || true
# ============================================
# 5. 整理 & 打包
# ============================================
echo " 整理并打包..."
# 统一权限
sudo chmod 755 -R "$TMP_BASE" 2>/dev/null || true
# 生成文件清单
find "$TMP_BASE" -type f | sort > "$TMP_BASE/_file_index.txt"
# 打包 # 打包
echo "正在打包文件,将会生成在主目录/家目录/个人文件夹" echo "正在生成压缩包..."
cd /tmp cd /tmp
tar -czf ~/ktouch_log-$now.tar.gz ./klog_$now tar -czf ~/ktouch_log-"$NOW".tar.gz "klog_$NOW"
rm -rf /tmp/klog # 清理
sudo rm -rf "$TMP_BASE"
echo ""
echo "============================================"
echo " 日志采集完成"
echo " 文件在 主目录 或 个人 文件夹中"
echo " 文件名: ktouch_log-${NOW}.tar.gz"
echo "============================================"
echo ""
+13 -9
View File
@@ -7,47 +7,51 @@ GTK_FLAGS = $(shell pkg-config --cflags --libs gtk+-3.0)
# 定义目标文件 # 定义目标文件
TARGETS = screen_binder screen_ds touch_ds usb_ds touch_set screen_ds_once check_save TARGETS = screen_binder screen_ds touch_ds usb_ds touch_set screen_ds_once check_save
# 默认目标 # 安装目标目录
DESTDIR = ../opt/ktouch
# 默认目标:只编译,不安装
all: $(TARGETS) all: $(TARGETS)
# 编译screen_binder # 编译screen_binder
screen_binder: screen_binder.c touch_listen.c screen_binder: screen_binder.c touch_listen.c
$(CC) -o $@ $^ $(GTK_FLAGS) -lpthread -lX11 -lXrandr $(CC) -o $@ $^ $(GTK_FLAGS) -lpthread -lX11 -lXrandr
install -m 755 $@ ../
# 编译screen_ds # 编译screen_ds
screen_ds: screen_ds.c screen_ds: screen_ds.c
$(CC) -o $@ $^ -lX11 -lXrandr $(CC) -o $@ $^ -lX11 -lXrandr
install -m 755 $@ ../
# 编译screen_ds_once # 编译screen_ds_once
screen_ds_once: screen_ds_once.c screen_ds_once: screen_ds_once.c
$(CC) -o $@ $^ -lX11 -lXrandr $(CC) -o $@ $^ -lX11 -lXrandr
install -m 755 $@ ../
# 编译touch_set # 编译touch_set
touch_set: touch_set.c touch_set: touch_set.c
$(CC) -o $@ $^ -lX11 -lXi -ludev $(CC) -o $@ $^ -lX11 -lXi -ludev
install -m 755 $@ ../
# 编译touch_ds # 编译touch_ds
touch_ds: touch_ds.c touch_ds: touch_ds.c
$(CC) -o $@ $^ -lX11 -lXi -ludev $(CC) -o $@ $^ -lX11 -lXi -ludev
install -m 755 $@ ../
# 编译usb_ds # 编译usb_ds
usb_ds: usb_ds.c usb_ds: usb_ds.c
$(CC) -o $@ $^ -ludev $(CC) -o $@ $^ -ludev
install -m 755 $@ ../
# 编译check_save # 编译check_save
check_save: check_save.c check_save: check_save.c
$(CC) -o $@ $^ $(GTK_FLAGS) $(CC) -o $@ $^ $(GTK_FLAGS)
install -m 755 $@ ../
# 安装:编译全部并复制到 opt/ktouch
install: all
@mkdir -p $(DESTDIR)
@for target in $(TARGETS); do \
install -m 755 $$target $(DESTDIR)/; \
done
@echo "已安装到 $(DESTDIR)/"
# 清理生成的文件 # 清理生成的文件
clean: clean:
rm -f $(TARGETS) rm -f $(TARGETS)
# 声明伪目标 # 声明伪目标
.PHONY: all clean .PHONY: all clean install
+394 -394
View File
@@ -1,395 +1,395 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <pthread.h> #include <pthread.h>
#include <time.h> #include <time.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <gdk/gdkx.h> #include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h> #include <gdk/gdkkeysyms.h>
#include <stdarg.h> #include <stdarg.h>
// 添加Xrandr头文件和X11显示类型头文件 // 添加Xrandr头文件和X11显示类型头文件
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
#include <X11/extensions/Xrandr.h> #include <X11/extensions/Xrandr.h>
#include <gdk/x11/gdkx11display.h> #include <gdk/x11/gdkx11display.h>
#endif #endif
int win_time_delay = 50; int win_time_delay = 50;
// 全局变量 // 全局变量
GtkWidget **info_windows = NULL; GtkWidget **info_windows = NULL;
int total_screens = 0; int total_screens = 0;
int *screen_widths = NULL; int *screen_widths = NULL;
int *screen_heights = NULL; int *screen_heights = NULL;
int *screen_x_offsets = NULL; int *screen_x_offsets = NULL;
int *screen_y_offsets = NULL; int *screen_y_offsets = NULL;
char **screen_names = NULL; char **screen_names = NULL;
FILE *log_file = NULL; FILE *log_file = NULL;
int current_test_round = 0; int current_test_round = 0;
int total_test_rounds = 0; int total_test_rounds = 0;
int space_pressed = 0; int space_pressed = 0;
guint create_timeout_id = 0; guint create_timeout_id = 0;
int current_window_index = 0; int current_window_index = 0;
// 函数声明 // 函数声明
void get_screen_info(); void get_screen_info();
void log_message(const char *format, ...); void log_message(const char *format, ...);
GtkWidget* create_info_window(int screen_index); GtkWidget* create_info_window(int screen_index);
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data); gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
gboolean create_next_window(gpointer user_data); gboolean create_next_window(gpointer user_data);
void close_all_windows(); void close_all_windows();
void verify_windows(); void verify_windows();
void start_next_test_round(); void start_next_test_round();
void on_info_window_destroy(GtkWidget *widget, gpointer user_data); void on_info_window_destroy(GtkWidget *widget, gpointer user_data);
// 日志函数 // 日志函数
void log_message(const char *format, ...) { void log_message(const char *format, ...) {
va_list args; va_list args;
va_start(args, format); va_start(args, format);
time_t now = time(NULL); time_t now = time(NULL);
struct tm *tm_info = localtime(&now); struct tm *tm_info = localtime(&now);
char time_str[20]; char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info); strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%s] screen_demo \t", time_str); printf("[%s] screen_demo \t", time_str);
vprintf(format, args); vprintf(format, args);
if (log_file) { if (log_file) {
fprintf(log_file, "[%s] screen_demo \t", time_str); fprintf(log_file, "[%s] screen_demo \t", time_str);
vfprintf(log_file, format, args); vfprintf(log_file, format, args);
fflush(log_file); fflush(log_file);
} }
va_end(args); va_end(args);
} }
// 获取屏幕信息 (使用Xrandr方法) // 获取屏幕信息 (使用Xrandr方法)
void get_screen_info() { void get_screen_info() {
GdkDisplay *gdk_display = gdk_display_get_default(); GdkDisplay *gdk_display = gdk_display_get_default();
#ifdef GDK_WINDOWING_X11 #ifdef GDK_WINDOWING_X11
if (!GDK_IS_X11_DISPLAY(gdk_display)) { if (!GDK_IS_X11_DISPLAY(gdk_display)) {
log_message("错误: 非X11显示环境\n"); log_message("错误: 非X11显示环境\n");
return; return;
} }
Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display); Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display);
Window xroot = GDK_WINDOW_XID(gdk_get_default_root_window()); Window xroot = GDK_WINDOW_XID(gdk_get_default_root_window());
int rr_event_base, rr_error_base; int rr_event_base, rr_error_base;
if (!XRRQueryExtension(xdisplay, &rr_event_base, &rr_error_base)) { if (!XRRQueryExtension(xdisplay, &rr_event_base, &rr_error_base)) {
log_message("错误: XRandR扩展不可用\n"); log_message("错误: XRandR扩展不可用\n");
return; return;
} }
XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(xdisplay, xroot); XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(xdisplay, xroot);
if (!screen_res) { if (!screen_res) {
log_message("错误: 无法获取屏幕资源\n"); log_message("错误: 无法获取屏幕资源\n");
return; return;
} }
// 计算连接的显示器数量 // 计算连接的显示器数量
total_screens = 0; total_screens = 0;
for (int i = 0; i < screen_res->noutput; i++) { for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]); XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) { if (output_info && output_info->connection == RR_Connected) {
total_screens++; total_screens++;
} }
if (output_info) XRRFreeOutputInfo(output_info); if (output_info) XRRFreeOutputInfo(output_info);
} }
log_message("找到 %d 个连接的显示器\n", total_screens); log_message("找到 %d 个连接的显示器\n", total_screens);
// 分配内存存储屏幕信息 // 分配内存存储屏幕信息
screen_widths = malloc(total_screens * sizeof(int)); screen_widths = malloc(total_screens * sizeof(int));
screen_heights = malloc(total_screens * sizeof(int)); screen_heights = malloc(total_screens * sizeof(int));
screen_x_offsets = malloc(total_screens * sizeof(int)); screen_x_offsets = malloc(total_screens * sizeof(int));
screen_y_offsets = malloc(total_screens * sizeof(int)); screen_y_offsets = malloc(total_screens * sizeof(int));
screen_names = malloc(total_screens * sizeof(char*)); screen_names = malloc(total_screens * sizeof(char*));
info_windows = malloc(total_screens * sizeof(GtkWidget*)); info_windows = malloc(total_screens * sizeof(GtkWidget*));
// 获取每个显示器的详细信息 // 获取每个显示器的详细信息
int screen_index = 0; int screen_index = 0;
for (int i = 0; i < screen_res->noutput; i++) { for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]); XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) { if (output_info && output_info->connection == RR_Connected) {
if (output_info->crtc) { if (output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(xdisplay, screen_res, output_info->crtc); XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(xdisplay, screen_res, output_info->crtc);
if (crtc_info) { if (crtc_info) {
screen_names[screen_index] = strdup(output_info->name); screen_names[screen_index] = strdup(output_info->name);
screen_widths[screen_index] = crtc_info->width; screen_widths[screen_index] = crtc_info->width;
screen_heights[screen_index] = crtc_info->height; screen_heights[screen_index] = crtc_info->height;
screen_x_offsets[screen_index] = crtc_info->x; screen_x_offsets[screen_index] = crtc_info->x;
screen_y_offsets[screen_index] = crtc_info->y; screen_y_offsets[screen_index] = crtc_info->y;
info_windows[screen_index] = NULL; info_windows[screen_index] = NULL;
log_message("显示器 %d: %s, 分辨率: %dx%d, 位置: %dx%d\n", log_message("显示器 %d: %s, 分辨率: %dx%d, 位置: %dx%d\n",
screen_index, screen_names[screen_index], screen_index, screen_names[screen_index],
screen_widths[screen_index], screen_heights[screen_index], screen_widths[screen_index], screen_heights[screen_index],
screen_x_offsets[screen_index], screen_y_offsets[screen_index]); screen_x_offsets[screen_index], screen_y_offsets[screen_index]);
XRRFreeCrtcInfo(crtc_info); XRRFreeCrtcInfo(crtc_info);
screen_index++; screen_index++;
} }
} }
} }
if (output_info) XRRFreeOutputInfo(output_info); if (output_info) XRRFreeOutputInfo(output_info);
} }
XRRFreeScreenResources(screen_res); XRRFreeScreenResources(screen_res);
#else #else
log_message("错误: 非X11环境,无法使用Xrandr\n"); log_message("错误: 非X11环境,无法使用Xrandr\n");
#endif #endif
} }
// 创建信息窗口 // 创建信息窗口
GtkWidget* create_info_window(int screen_index) { GtkWidget* create_info_window(int screen_index) {
// 计算窗口位置和大小:偏移(10,10),尺寸比屏幕小20 // 计算窗口位置和大小:偏移(10,10),尺寸比屏幕小20
int window_x = screen_x_offsets[screen_index] + 10; int window_x = screen_x_offsets[screen_index] + 10;
int window_y = screen_y_offsets[screen_index] + 10; int window_y = screen_y_offsets[screen_index] + 10;
int window_width = screen_widths[screen_index] - 20; int window_width = screen_widths[screen_index] - 20;
int window_height = screen_heights[screen_index] - 20; int window_height = screen_heights[screen_index] - 20;
// 确保窗口尺寸不会为负值 // 确保窗口尺寸不会为负值
if (window_width < 100) window_width = 100; if (window_width < 100) window_width = 100;
if (window_height < 100) window_height = 100; if (window_height < 100) window_height = 100;
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "屏幕信息演示"); gtk_window_set_title(GTK_WINDOW(window), "屏幕信息演示");
gtk_window_move(GTK_WINDOW(window), window_x, window_y); gtk_window_move(GTK_WINDOW(window), window_x, window_y);
gtk_window_set_default_size(GTK_WINDOW(window), window_width, window_height); gtk_window_set_default_size(GTK_WINDOW(window), window_width, window_height);
gtk_window_set_decorated(GTK_WINDOW(window), TRUE); gtk_window_set_decorated(GTK_WINDOW(window), TRUE);
gtk_window_set_keep_above(GTK_WINDOW(window), TRUE); gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
char message[512]; char message[512];
snprintf(message, sizeof(message), snprintf(message, sizeof(message),
"<span font='24' weight='bold'>屏幕 %d: %s</span>\n\n" "<span font='24' weight='bold'>屏幕 %d: %s</span>\n\n"
"<span font='18'>分辨率: %dx%d</span>\n" "<span font='18'>分辨率: %dx%d</span>\n"
"<span font='18'>位置: (%d, %d)</span>\n\n" "<span font='18'>位置: (%d, %d)</span>\n\n"
"<span font='18'>窗口位置: (%d, %d)</span>\n" "<span font='18'>窗口位置: (%d, %d)</span>\n"
"<span font='18'>窗口大小: %dx%d</span>\n\n" "<span font='18'>窗口大小: %dx%d</span>\n\n"
"<span font='18'>测试轮次: %d/%d</span>\n\n" "<span font='18'>测试轮次: %d/%d</span>\n\n"
"<span font='18' foreground='blue'>按空格键继续...</span>", "<span font='18' foreground='blue'>按空格键继续...</span>",
screen_index, screen_names[screen_index], screen_index, screen_names[screen_index],
screen_widths[screen_index], screen_heights[screen_index], screen_widths[screen_index], screen_heights[screen_index],
screen_x_offsets[screen_index], screen_y_offsets[screen_index], screen_x_offsets[screen_index], screen_y_offsets[screen_index],
window_x, window_y, window_width, window_height, window_x, window_y, window_width, window_height,
current_test_round + 1, total_test_rounds); current_test_round + 1, total_test_rounds);
GtkWidget *label = gtk_label_new(NULL); GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), message); gtk_label_set_markup(GTK_LABEL(label), message);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER); gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
// 添加容器使标签居中 // 添加容器使标签居中
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box); gtk_container_add(GTK_CONTAINER(window), box);
g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL); g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL);
g_signal_connect(window, "destroy", G_CALLBACK(on_info_window_destroy), GINT_TO_POINTER(screen_index)); g_signal_connect(window, "destroy", G_CALLBACK(on_info_window_destroy), GINT_TO_POINTER(screen_index));
return window; return window;
} }
// 键盘事件处理 // 键盘事件处理
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) { gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
if (event->keyval == GDK_KEY_space) { if (event->keyval == GDK_KEY_space) {
log_message("用户按下空格键\n"); log_message("用户按下空格键\n");
space_pressed = 1; space_pressed = 1;
// 关闭所有窗口并开始下一轮测试 // 关闭所有窗口并开始下一轮测试
close_all_windows(); close_all_windows();
return TRUE; return TRUE;
} }
return FALSE; return FALSE;
} }
// 信息窗口销毁事件处理 // 信息窗口销毁事件处理
void on_info_window_destroy(GtkWidget *widget, gpointer user_data) { void on_info_window_destroy(GtkWidget *widget, gpointer user_data) {
int screen_index = GPOINTER_TO_INT(user_data); int screen_index = GPOINTER_TO_INT(user_data);
log_message("屏幕 %d (%s) 的信息窗口已销毁\n", screen_index, screen_names[screen_index]); log_message("屏幕 %d (%s) 的信息窗口已销毁\n", screen_index, screen_names[screen_index]);
info_windows[screen_index] = NULL; info_windows[screen_index] = NULL;
} }
// 创建下一个窗口(间隔3秒) // 创建下一个窗口(间隔3秒)
gboolean create_next_window(gpointer user_data) { gboolean create_next_window(gpointer user_data) {
if (current_window_index >= total_screens) { if (current_window_index >= total_screens) {
log_message("所有窗口已创建完成\n"); log_message("所有窗口已创建完成\n");
verify_windows(); verify_windows();
create_timeout_id = 0; create_timeout_id = 0;
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
log_message("正在为屏幕 %d (%s) 创建信息窗口...\n", log_message("正在为屏幕 %d (%s) 创建信息窗口...\n",
current_window_index, screen_names[current_window_index]); current_window_index, screen_names[current_window_index]);
info_windows[current_window_index] = create_info_window(current_window_index); info_windows[current_window_index] = create_info_window(current_window_index);
gtk_widget_show_all(info_windows[current_window_index]); gtk_widget_show_all(info_windows[current_window_index]);
log_message("屏幕 %d 的信息窗口已显示\n", current_window_index); log_message("屏幕 %d 的信息窗口已显示\n", current_window_index);
current_window_index++; current_window_index++;
if (current_window_index < total_screens) { if (current_window_index < total_screens) {
// 设置3秒后创建下一个窗口 // 设置3秒后创建下一个窗口
// create_timeout_id = g_timeout_add_seconds(3, create_next_window, NULL); // create_timeout_id = g_timeout_add_seconds(3, create_next_window, NULL);
create_timeout_id = g_timeout_add(win_time_delay, create_next_window, NULL); create_timeout_id = g_timeout_add(win_time_delay, create_next_window, NULL);
} else { } else {
log_message("所有窗口已创建完成\n"); log_message("所有窗口已创建完成\n");
verify_windows(); verify_windows();
} }
return G_SOURCE_REMOVE; return G_SOURCE_REMOVE;
} }
// 关闭所有窗口 // 关闭所有窗口
void close_all_windows() { void close_all_windows() {
log_message("正在关闭所有窗口...\n"); log_message("正在关闭所有窗口...\n");
for (int i = 0; i < total_screens; i++) { for (int i = 0; i < total_screens; i++) {
if (info_windows[i] != NULL) { if (info_windows[i] != NULL) {
gtk_widget_destroy(info_windows[i]); gtk_widget_destroy(info_windows[i]);
info_windows[i] = NULL; info_windows[i] = NULL;
} }
} }
// 取消超时计时器 // 取消超时计时器
if (create_timeout_id > 0) { if (create_timeout_id > 0) {
g_source_remove(create_timeout_id); g_source_remove(create_timeout_id);
create_timeout_id = 0; create_timeout_id = 0;
} }
log_message("所有窗口已关闭\n"); log_message("所有窗口已关闭\n");
// 开始下一轮测试 // 开始下一轮测试
start_next_test_round(); start_next_test_round();
} }
// 校验窗口的显示状态、位置和大小 // 校验窗口的显示状态、位置和大小
void verify_windows() { void verify_windows() {
log_message("开始校验窗口状态...\n"); log_message("开始校验窗口状态...\n");
int all_windows_ok = 1; int all_windows_ok = 1;
for (int i = 0; i < total_screens; i++) { for (int i = 0; i < total_screens; i++) {
if (info_windows[i] == NULL) { if (info_windows[i] == NULL) {
log_message("错误: 屏幕 %d 的窗口未创建\n", i); log_message("错误: 屏幕 %d 的窗口未创建\n", i);
all_windows_ok = 0; all_windows_ok = 0;
continue; continue;
} }
if (!gtk_widget_get_visible(info_windows[i])) { if (!gtk_widget_get_visible(info_windows[i])) {
log_message("错误: 屏幕 %d 的窗口不可见\n", i); log_message("错误: 屏幕 %d 的窗口不可见\n", i);
all_windows_ok = 0; all_windows_ok = 0;
} }
// 获取窗口实际位置和大小 // 获取窗口实际位置和大小
int x, y, width, height; int x, y, width, height;
gtk_window_get_position(GTK_WINDOW(info_windows[i]), &x, &y); gtk_window_get_position(GTK_WINDOW(info_windows[i]), &x, &y);
gtk_window_get_size(GTK_WINDOW(info_windows[i]), &width, &height); gtk_window_get_size(GTK_WINDOW(info_windows[i]), &width, &height);
// 计算期望的位置和大小 // 计算期望的位置和大小
int expected_x = screen_x_offsets[i] + 10; int expected_x = screen_x_offsets[i] + 10;
int expected_y = screen_y_offsets[i] + 10; int expected_y = screen_y_offsets[i] + 10;
int expected_width = screen_widths[i] - 20; int expected_width = screen_widths[i] - 20;
int expected_height = screen_heights[i] - 20; int expected_height = screen_heights[i] - 20;
// 确保期望尺寸不会为负值 // 确保期望尺寸不会为负值
if (expected_width < 100) expected_width = 100; if (expected_width < 100) expected_width = 100;
if (expected_height < 100) expected_height = 100; if (expected_height < 100) expected_height = 100;
// 校验位置(允许±5像素的误差) // 校验位置(允许±5像素的误差)
if (abs(x - expected_x) > 5 || abs(y - expected_y) > 5) { if (abs(x - expected_x) > 5 || abs(y - expected_y) > 5) {
log_message("警告: 屏幕 %d 的窗口位置不匹配。预期: (%d, %d), 实际: (%d, %d)\n", log_message("警告: 屏幕 %d 的窗口位置不匹配。预期: (%d, %d), 实际: (%d, %d)\n",
i, expected_x, expected_y, x, y); i, expected_x, expected_y, x, y);
} }
// 校验大小(允许±5像素的误差) // 校验大小(允许±5像素的误差)
if (abs(width - expected_width) > 5 || abs(height - expected_height) > 5) { if (abs(width - expected_width) > 5 || abs(height - expected_height) > 5) {
log_message("警告: 屏幕 %d 的窗口大小不匹配。预期: %dx%d, 实际: %dx%d\n", log_message("警告: 屏幕 %d 的窗口大小不匹配。预期: %dx%d, 实际: %dx%d\n",
i, expected_width, expected_height, width, height); i, expected_width, expected_height, width, height);
} }
log_message("屏幕 %d 校验完成: 位置=(%d, %d), 大小=%dx%d\n", i, x, y, width, height); log_message("屏幕 %d 校验完成: 位置=(%d, %d), 大小=%dx%d\n", i, x, y, width, height);
} }
if (all_windows_ok) { if (all_windows_ok) {
log_message("所有窗口校验成功\n"); log_message("所有窗口校验成功\n");
} else { } else {
log_message("部分窗口校验失败\n"); log_message("部分窗口校验失败\n");
} }
} }
// 开始下一轮测试 // 开始下一轮测试
void start_next_test_round() { void start_next_test_round() {
current_test_round++; current_test_round++;
if (current_test_round >= total_test_rounds) { if (current_test_round >= total_test_rounds) {
log_message("所有测试轮次已完成,程序结束\n"); log_message("所有测试轮次已完成,程序结束\n");
// 清理资源 // 清理资源
for (int i = 0; i < total_screens; i++) { for (int i = 0; i < total_screens; i++) {
free(screen_names[i]); free(screen_names[i]);
} }
free(screen_widths); free(screen_widths);
free(screen_heights); free(screen_heights);
free(screen_x_offsets); free(screen_x_offsets);
free(screen_y_offsets); free(screen_y_offsets);
free(screen_names); free(screen_names);
free(info_windows); free(info_windows);
// 关闭日志文件 // 关闭日志文件
if (log_file) { if (log_file) {
fclose(log_file); fclose(log_file);
log_file = NULL; log_file = NULL;
} }
gtk_main_quit(); gtk_main_quit();
return; return;
} }
log_message("开始第 %d/%d 轮测试\n", current_test_round + 1, total_test_rounds); log_message("开始第 %d/%d 轮测试\n", current_test_round + 1, total_test_rounds);
// 重置窗口索引 // 重置窗口索引
current_window_index = 0; current_window_index = 0;
space_pressed = 0; space_pressed = 0;
// 开始创建窗口(第一个窗口立即创建,后续窗口间隔3秒) // 开始创建窗口(第一个窗口立即创建,后续窗口间隔3秒)
create_timeout_id = g_timeout_add(100, create_next_window, NULL); create_timeout_id = g_timeout_add(100, create_next_window, NULL);
} }
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
log_file = fopen("screen_demo.log", "a"); log_file = fopen("screen_demo.log", "a");
if (!log_file) { if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n"); printf("无法打开日志文件,将只输出到控制台\n");
} }
log_message("=== 多屏幕信息窗口演示程序 ===\n"); log_message("=== 多屏幕信息窗口演示程序 ===\n");
gtk_init(&argc, &argv); gtk_init(&argc, &argv);
// 获取屏幕信息 // 获取屏幕信息
get_screen_info(); get_screen_info();
if (total_screens == 0) { if (total_screens == 0) {
log_message("未找到任何屏幕,程序退出\n"); log_message("未找到任何屏幕,程序退出\n");
return 1; return 1;
} }
// 设置测试轮次为屏幕数量 // 设置测试轮次为屏幕数量
total_test_rounds = total_screens; total_test_rounds = total_screens;
log_message("将执行 %d 轮测试(每轮在所有屏幕上创建窗口)\n", total_test_rounds); log_message("将执行 %d 轮测试(每轮在所有屏幕上创建窗口)\n", total_test_rounds);
// 开始第一轮测试 // 开始第一轮测试
current_test_round = -1; current_test_round = -1;
start_next_test_round(); start_next_test_round();
gtk_main(); gtk_main();
log_message("程序结束\n"); log_message("程序结束\n");
return 0; return 0;
} }
+20 -1
View File
@@ -374,7 +374,26 @@ int is_touchscreen_device(const char* device_node, char* vid, char* pid, int buf
} }
if (!is_touchscreen) { if (!is_touchscreen) {
// printf(" 未找到 ID_INPUT_TOUCHSCREEN=1 属性\n"); // 通过设备名称关键词匹配(fallback)
const char *dev_name = udev_device_get_property_value(dev, "NAME");
if (dev_name) {
char lower_name[256];
strncpy(lower_name, dev_name, sizeof(lower_name) - 1);
lower_name[sizeof(lower_name) - 1] = '\0';
for (int i = 0; lower_name[i]; i++) {
lower_name[i] = tolower(lower_name[i]);
}
if (strstr(lower_name, "ilitek") != NULL ||
strstr(lower_name, "touchscreen") != NULL ||
strstr(lower_name, "touch") != NULL ||
strstr(lower_name, "tablet") != NULL) {
is_touchscreen = 1;
const char *vendor_id = udev_device_get_property_value(dev, "ID_VENDOR_ID");
const char *model_id = udev_device_get_property_value(dev, "ID_MODEL_ID");
if (vendor_id) strncpy(vid, vendor_id, buffer_size);
if (model_id) strncpy(pid, model_id, buffer_size);
}
}
} }
udev_device_unref(dev); udev_device_unref(dev);
+18 -1
View File
@@ -214,7 +214,24 @@ int get_input_devices(InputDeviceInfo result[]) {
if (is_touchscreen){ // 通过名称关键词匹配(fallback,针对无ID_INPUT_TOUCHSCREEN属性的设备)
int name_matched = 0;
if (!is_touchscreen) {
char lower_name[256];
strncpy(lower_name, dev->name, sizeof(lower_name) - 1);
lower_name[sizeof(lower_name) - 1] = '\0';
for (int i = 0; lower_name[i]; i++) {
lower_name[i] = tolower(lower_name[i]);
}
if (strstr(lower_name, "ilitek") != NULL ||
strstr(lower_name, "touchscreen") != NULL ||
strstr(lower_name, "touch") != NULL ||
strstr(lower_name, "tablet") != NULL) {
name_matched = 1;
}
}
if (is_touchscreen || name_matched){
// log_message("识别到触摸设备,添加到列表中\n name=%s", name); // log_message("识别到触摸设备,添加到列表中\n name=%s", name);
// 初始化当前设备信息结构体 // 初始化当前设备信息结构体
result[count].device_id = dev->deviceid; result[count].device_id = dev->deviceid;
@@ -212,7 +212,24 @@ int get_input_devices(InputDeviceInfo result[]) {
if (is_touchscreen){ // 通过名称关键词匹配(fallback,针对无ID_INPUT_TOUCHSCREEN属性的设备)
int name_matched = 0;
if (!is_touchscreen) {
char lower_name[256];
strncpy(lower_name, dev->name, sizeof(lower_name) - 1);
lower_name[sizeof(lower_name) - 1] = '\0';
for (int i = 0; lower_name[i]; i++) {
lower_name[i] = tolower(lower_name[i]);
}
if (strstr(lower_name, "ilitek") != NULL ||
strstr(lower_name, "touchscreen") != NULL ||
strstr(lower_name, "touch") != NULL ||
strstr(lower_name, "tablet") != NULL) {
name_matched = 1;
}
}
if (is_touchscreen || name_matched){
// log_message("识别到触摸设备,添加到列表中\n name=%s", name); // log_message("识别到触摸设备,添加到列表中\n name=%s", name);
// 初始化当前设备信息结构体 // 初始化当前设备信息结构体
result[count].device_id = dev->deviceid; result[count].device_id = dev->deviceid;
+29 -20
View File
@@ -16,6 +16,7 @@
#define DEFAULT_POLL_INTERVAL 5 // 默认轮询间隔为5秒 #define DEFAULT_POLL_INTERVAL 5 // 默认轮询间隔为5秒
#define MAX_RETRY_ATTEMPTS 3 // 最大重试次数 #define MAX_RETRY_ATTEMPTS 3 // 最大重试次数
#define RETRY_DELAY 1 // 重试延迟(秒) #define RETRY_DELAY 1 // 重试延迟(秒)
#define COOLDOWN_SECONDS 7 // 触摸屏检测冷却时间(秒)
FILE *log_file = NULL; // 日志文件指针 FILE *log_file = NULL; // 日志文件指针
// 函数提前声明 // 函数提前声明
@@ -84,6 +85,7 @@ int is_touchscreen(struct udev_device *dev) {
if (strstr(lower_name, "touchscreen") != NULL || if (strstr(lower_name, "touchscreen") != NULL ||
strstr(lower_name, "touch") != NULL || strstr(lower_name, "touch") != NULL ||
strstr(lower_name, "ilitek") != NULL ||
strstr(lower_name, "tablet") != NULL) { strstr(lower_name, "tablet") != NULL) {
return 1; return 1;
} }
@@ -212,6 +214,7 @@ void monitor_usb_devices(const char *control_file_path, int poll_interval) {
printf("按Ctrl+C退出\n\n"); printf("按Ctrl+C退出\n\n");
// 主循环,监控设备事件 // 主循环,监控设备事件
time_t last_trigger_time = 0; // 上次触发控制文件的时间
while (keep_running) { while (keep_running) {
fd_set fds; fd_set fds;
struct timeval tv; struct timeval tv;
@@ -291,28 +294,34 @@ void monitor_usb_devices(const char *control_file_path, int poll_interval) {
} }
} }
// 一轮检测完成后,如果需要更新控制文件状态 // 一轮检测完成后,如果需要更新控制文件状态(带冷却时间防重复触发)
if (use_control_file && touchscreen_detected) { if (use_control_file && touchscreen_detected) {
if (write_file_content_with_retry(control_file_path, 1) == 0) { time_t now = time(NULL);
log_message("已更新控制文件状态为1\n"); if (difftime(now, last_trigger_time) < COOLDOWN_SECONDS) {
log_message("冷却中,跳过本次触发(距上次 %.0f 秒)\n", difftime(now, last_trigger_time));
// 等待文件状态恢复为0
printf("等待控制文件状态恢复为0...\n");
while (keep_running) {
int current_status = read_file_content_with_retry(control_file_path);
if (current_status == 0) {
printf("控制文件状态已恢复为0,继续监控\n");
break;
} else if (current_status == -1) {
printf("读取控制文件失败,等待%d秒后重试\n", poll_interval);
sleep(poll_interval);
} else {
printf("当前控制文件状态: %d,等待%d秒后检查\n", current_status, poll_interval);
sleep(poll_interval);
}
}
} else { } else {
log_message("更新控制文件状态失败\n"); if (write_file_content_with_retry(control_file_path, 1) == 0) {
last_trigger_time = time(NULL);
log_message("已更新控制文件状态为1\n");
// 等待文件状态恢复为0
printf("等待控制文件状态恢复为0...\n");
while (keep_running) {
int current_status = read_file_content_with_retry(control_file_path);
if (current_status == 0) {
printf("控制文件状态已恢复为0,继续监控\n");
break;
} else if (current_status == -1) {
printf("读取控制文件失败,等待%d秒后重试\n", poll_interval);
sleep(poll_interval);
} else {
printf("当前控制文件状态: %d,等待%d秒后检查\n", current_status, poll_interval);
sleep(poll_interval);
}
}
} else {
log_message("更新控制文件状态失败\n");
}
} }
} }
} }