V2-202509

This commit is contained in:
tang1219
2026-06-12 16:54:32 +08:00
commit a821392ee4
121 changed files with 11161 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# ---> C++
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
.vscode
# 编译好的二进制文件
kdisplay
screen_binder
screen_ds
screen_ds_once
touch_ds
touch_set
usb_ds
+8
View File
@@ -0,0 +1,8 @@
Package: ktouch
Version: 2.0
Architecture: arm64
Maintainer: Kushidou <kushidou@small09.top>
Depends: xinput, python3
Section: x11
Priority: optional
Description: 设置触摸屏仅需简单的点击屏幕.
+136
View File
@@ -0,0 +1,136 @@
#!/bin/sh
BUILDDATE="2025年 09月 25日"
KTVERSION="2.0"
MYDIR=/opt/ktouch
APPDIR=$MYDIR/desktops
# AUTODIR=/etc/xdg/autostart
MENUDIR=/usr/share/applications
BINDIR=/usr/bin
log_path=${MYDIR}
inslog_path=${MYDIR}/install.log
LOGINUSER=$(whoami)
NUSER=${SUDO_USER}
set_autostart()
{
SUSER=$NUSER
if [ "$NUSER" = "" ];then
echo "注意!用户是空,应该使用sudo dpkg 来安装。"
echo "用户名为[$SUSER], 免root操作触摸屏和开机自启动都不能正常工作。" >> $log_path
SUSER="1000"
fi
# 生成服务文件内容
SERVICE_CONTENT="[Unit]
Description=Kushidou Touchscreen Remap Daemon
After=display-manager.service
Requires=display-manager.service
[Service]
Type=simple
User=$SUSER
Group=$SUSER
Environment=DISPLAY=:0
#Environment=XAUTHORITY=/home/$NUSER/.Xauthority
ExecStart=/usr/bin/python3 /opt/ktouch/kscreen-remap-daemon
Restart=always
RestartSec=5
StartLimitInterval=0
[Install]
WantedBy=graphical.target"
# 写入服务文件
SERVICE_FILE="/etc/systemd/system/ktouch_daemon.service"
echo "$SERVICE_CONTENT" > "$SERVICE_FILE" || {
echo "写入服务文件失败: $SERVICE_FILE"
exit 1
}
# 重新加载systemd配置
systemctl daemon-reload || {
echo "重新加载systemd配置失败。"
exit 1
}
# 启用服务自启动
systemctl enable ktouch_daemon.service || {
echo "启用服务自启动失败。"
exit 1
}
# 开启服务
systemctl start ktouch_daemon.service || {
echo "启用服务失败。"
exit 1
}
echo "服务已成功配置并启动。"
}
echo "==========INSTALL===========" >> ${APPDIR}/install.log
echo "=>即将安装触摸屏设置软件ktouch v2">> ${inslog_path}
echo "build date: $BUILDDATE" >> ${inslog_path}
echo "ver: $KTVERSION" >> ${inslog_path}
echo "install: $(date)" >> ${inslog_path}
# start menu
install -p -m 0755 -o root -g root ${APPDIR}/touchscreen-config.desktop ${MENUDIR}
install -p -m 0755 -o root -g root ${APPDIR}/touchscreen-now.desktop ${MENUDIR}
install -p -m 0755 -o root -g root ${APPDIR}/touchscreen-auto.desktop /etc/xdg/autostart
#ICOS
cp ${APPDIR}/*.png /usr/share/pixmaps/
#USR/BIN
if [ -f ${BINDIR}/kscreen-remap ];then
rm -f ${BINDIR}/kscreen-remap
fi
if [ -f ${BINDIR}/kscreen-remap ];then
rm -f ${BINDIR}/kscreen-kscreen-remap-daemon
fi
if [ -f ${BINDIR}/kscreen-remap ];then
rm -f ${BINDIR}/kscreen-setup
fi
if [ -f ${BINDIR}/kscreen-remap ];then
rm -f ${BINDIR}/kscreen-log
fi
ln -s /opt/ktouch/kscreen-remap /usr/bin/kscreen-remap
ln -s /opt/ktouch/kscreen-remap-daemon /usr/bin/kscreen-remap-daemon
ln -s /opt/ktouch/kscreen-setup /usr/bin/kscreen-setup
ln -s /opt/ktouch/kscreen-log /usr/bin/kscreen-log
ln -s /opt/ktouch/kscreen-debug /usr/bin/kscreen-debug
ln -s /opt/ktouch/kdisplay /usr/bin/kdisplay
chmod 0777 /opt/ktouch/* -R
# 将用户加入input组
usermod -a -G input $NUSER
usermod -a -G dialout $NUSER
# 设置自启动
set_autostart
# 禁用UOS自动触摸屏弹窗
if [ -f /usr/lib/deepin-daemon/dde-touchscreen-dialog ];then
mv /usr/lib/deepin-daemon/dde-touchscreen-dialog /usr/lib/deepin-daemon/dde-touchscreen-dialog.disable
fi
# 禁用麒麟V10触摸设置工具
if [ -d /var/screen ];then
mv /var/screen /var/screen.disable
fi
echo "安装已经完成。如果是首次安装,请重启电脑,否则无法打开触摸屏设备"
+70
View File
@@ -0,0 +1,70 @@
#!/bin/sh
APPDIR=/opt/ktouch/desktops
AUTODIR=/etc/xdg/autostart
MENUDIR=/usr/share/applications
inslog_path="/opt/ktouch/install.log"
BUILDDATE="2025年 09月 25日"
KTVERSION="2.0"
echo "==========REMOVE===========" >> ${APPDIR}/install.log
echo "=>即将移除ktouch v2">> ${inslog_path}
echo "build date: $BUILDDATE" >> ${inslog_path}
echo "ver: $KTVERSION" >> ${inslog_path}
echo "remove_date: $(date)" >> ${inslog_path}
# desktop
for USERNAME in $(cat /etc/passwd | awk -F: '$3>=500' | cut -d : -f 1)
do
[ -f /home/$USERNAME/Desktop/touchscreen-config.desktop ] && rm -f /home/$USERNAME/Desktop/touchscreen-config.desktop
[ -f /home/$USERNAME/Desktop/touchscreen-now.desktop ] && rm -f /home/$USERNAME/Desktop/touchscreen-now.desktop
[ -f /home/$USERNAME/桌面/touchscreen-config.desktop ] && rm -f /home/$USERNAME/桌面/touchscreen-config.desktop
[ -f /home/$USERNAME/桌面/touchscreen-now.desktop ] && rm -f /home/$USERNAME/桌面/touchscreen-now.desktop
done
# start menu
rm $MENUDIR/touchscreen-config.desktop >/dev/null 2>&1
rm $MENUDIR/touchscreen-now.desktop >/dev/null 2>&1
# autostart
rm /etc/xdg/autostart/touchscreen-auto.desktop >/dev/null 2>&1
systemctl stop ktouch_daemon
systemctl disable ktouch_daemon
rm /etc/systemd/system/ktouch_daemon.service -f >/dev/null 2>&1
systemctl daemon-reload
#ICOS
rm /usr/share/pixmaps/screen-config.png >/dev/null 2>&1
rm /usr/share/pixmaps/screen-remap.png >/dev/null 2>&1
#USR/BIN
rm -f /usr/bin/kscreen-remap >/dev/null 2>&1
rm -f /usr/bin/kscreen-setup >/dev/null 2>&1
rm -f /usr/bin/kscreen-log > dev/null 2>&1
rm -f /usr/bin/kscreen-debug > dev/null 2>&1
rm -f /usr/bin/kscreen-remap-daemon > dev/null 2>&1
rm -f /usr/bin/kdisplay > dev/null 2>&1
# 解除禁用UOS自动触摸屏弹窗
if [ -f /usr/lib/deepin-daemon/dde-touchscreen-dialog.disable ];then
mv /usr/lib/deepin-daemon/dde-touchscreen-dialog.disable /usr/lib/deepin-daemon/dde-touchscreen-dialog
fi
# 清空UOS的触摸屏设置信息
if [ -f /usr/bin/gsettings ];then
gsettings set com.deepin.dde.display map-output '' > /dev/null 2>&1
fi
# 解除禁用麒麟V10触摸设置工具
if [ -d /var/screen.disable ];then
mv /var/screen.disable /var/screen
fi
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
# 2024.05.28 决定将kushidoutouch全部切换到ktouch,这个脚本用于切换。
# 2025.09.09 ktouch V2基本开发完成,不再需要原来的目录
KTVERSION="2.0"
MYDIR=/opt/ktouch
APPDIR=$MYDIR/desktops
AUTODIR=/etc/xdg/autostart
MENUDIR=/usr/share/applications
BINDIR=/usr/bin
log_path="/var/log/ktouch/touchscreen.log"
inslog_path="/var/log/ktouch/install.log"
# start menu
if [ -f ${MENUDIR}/touchscreen-config.desktop ];then
rm -f ${MENUDIR}/touchscreen-config.desktop >/dev/null 2>&1
fi
if [ -f ${MENUDIR}/touchscreen-now.desktop ];then
rm -f ${MENUDIR}/touchscreen-now.desktop >/dev/null 2>&1
fi
# desktop
for USERNAME in $(cat /etc/passwd | awk -F: '$3>=500' | cut -d : -f 1)
do
if [ -d "/home/${USERNAME}/Desktop" ];then
if [ -f /home/${USERNAME}/Desktop/touchscreen-config.desktop ];then
rm -f /home/${USERNAME}/Desktop/touchscreen-config.desktop
fi
if [ -f /home/${USERNAME}/Desktop/touchscreen-now.desktop ];then
rm -f /home/${USERNAME}/Desktop/touchscreen-now.desktop
fi
fi
if [ -d "/home/${USERNAME}/桌面" ];then
if [ -f /home/${USERNAME}/桌面/touchscreen-config.desktop ];then
rm -f /home/${USERNAME}/桌面/touchscreen-config.desktop
fi
if [ -f /home/${USERNAME}/桌面/touchscreen-now.desktop ];then
rm -f /home/${USERNAME}/桌面/touchscreen-now.desktop
fi
fi
done
# autostart
rm $AUTODIR/touchscreen-autoremap.desktop >/dev/null 2>&1
rm $AUTODIR/touchscreen-powerup.desktop >/dev/null 2>&1
#USR/BIN
rm -f /usr/bin/kscreen-remap >/dev/null 2>&1
rm -f /usr/bin/kscreen-setup >/dev/null 2>&1
rm -f /usr/bin/kscreen-log > dev/null 2>&1
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
inslog_path="/var/log/ktouch/install.log"
systemctl stop ktouch_daemon.service
systemctl disable ktouch_daemon.service
+18
View File
@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 kushidou
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
+3
View File
@@ -0,0 +1,3 @@
# ktouchV2
适配UOS和银河麒麟的触摸屏设置工具。
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Icon=/usr/share/pixmaps/screen-remap.png
GenericName=RemapTouchAuto
GenericName[zh_CN]=映射触摸屏(自启动)
Name=RemapTouchAuto
Name[zh_CN]=映射触摸屏(自启动)
Comment=Remap Touch-Screens Now.
Comment[zh_CN]=立即修复触摸紊乱
Exec=/opt/ktouch/kscreen-remap -s
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Icon=/usr/share/pixmaps/screen-config.png
GenericName=ktouch-setup
GenericName[zh_CN]=配置触摸映射
Name=ConfigTouchMAP
Name[zh_CN]=配置触摸映射
Comment=Config MAP between Touch-devices and Screens
Comment[zh_CN]=配置触摸设备和屏幕的映射关系
Exec=/opt/ktouch/kscreen-setup
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Icon=/usr/share/pixmaps/screen-remap.png
GenericName=RemapTouchNOW
GenericName[zh_CN]=映射触摸屏
Name=RemapTouch
Name[zh_CN]=映射触摸屏
Comment=Remap Touch-Screens Now.
Comment[zh_CN]=立即修复触摸紊乱
Exec=/opt/ktouch/kscreen-remap
+943
View File
@@ -0,0 +1,943 @@
#!/usr/bin/env python3
import os
import time
import logging
import signal
import sys
import re
import argparse
from pathlib import Path
from datetime import datetime, timedelta
# 配置日志
def setup_logging():
apppath="/opt/ktouch"
log_file = os.path.join(apppath, 'sub_modules.log')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s display_monitor [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler(log_file, encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)
return logging.getLogger(__name__)
logger = setup_logging()
class DisplayChecker:
def __init__(self, display_env=":0"):
"""初始化显示器检查器"""
self.display_env = display_env
self.display_name = None
self.config_data = None
self.current_settings = None
def parse_config_file(self, config_path):
"""
解析配置文件
格式: 分辨率@刷新率|坐标|旋转方向|是否是主屏|模式标志
示例: 1920x1080@60.0Hz|0,0|1|P|A 或 1920x1080@60.0Hz|0,0|1|P|F
F: 必须通过cvt生成分辨率并设置
A: 沿用原来的方法(即xrandr存在分辨率就使用系统分辨率,否则使用cvt)
"""
try:
with open(config_path, 'r') as f:
content = f.read().strip()
# 从文件名获取显示器名称(去掉后缀)
self.display_name = Path(config_path).stem
# 解析配置内容
parts = content.split('|')
if len(parts) != 5:
raise ValueError("配置文件格式错误,应该有5个部分")
# 解析分辨率和刷新率
res_refresh_match = re.match(r'(\d+)x(\d+)@([\d.]+)Hz', parts[0])
if not res_refresh_match:
raise ValueError("分辨率刷新率格式错误")
width, height, refresh_rate = res_refresh_match.groups()
# 解析坐标
pos_match = re.match(r'(-?\d+),(-?\d+)', parts[1])
if not pos_match:
raise ValueError("坐标格式错误")
pos_x, pos_y = pos_match.groups()
# 解析旋转方向
rotation_map = {'1': 'normal', '2': 'left', '4': 'inverted', '8': 'right'}
rotation = rotation_map.get(parts[2])
if not rotation:
raise ValueError(f"旋转方向错误: {parts[2]}")
# 解析主屏设置
primary = parts[3] == 'P'
# 解析模式标志
mode_flag = parts[4].strip()
if mode_flag not in ['F', 'A']:
raise ValueError(f"模式标志错误: {mode_flag},应该是 F 或 A")
self.config_data = {
'width': int(width),
'height': int(height),
'refresh_rate': float(refresh_rate),
'pos_x': int(pos_x),
'pos_y': int(pos_y),
'rotation': rotation,
'primary': primary,
'mode_flag': mode_flag # 新增字段
}
return True
except Exception as e:
logger.error(f"解析配置文件错误: {e}")
return False
def is_display_connected(self):
"""检查显示器是否连接"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
import subprocess
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error("获取显示器信息失败")
return False
# 查找显示器连接状态
lines = result.stdout.split('\n')
for line in lines:
if line.startswith(f'{self.display_name} '):
if 'connected' in line and 'disconnected' not in line:
return True
else:
logger.warning(f"显示器 {self.display_name} 未连接或已断开")
return False
logger.warning(f"未找到显示器 {self.display_name}")
return False
except Exception as e:
logger.error(f"检查显示器连接状态错误: {e}")
return False
def run_xrandr_command(self, cmd, ignore_errors=False):
"""运行xrandr命令"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
import subprocess
result = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True)
if result.returncode != 0:
if ignore_errors:
# 检查是否是"已经存在"之类的错误
error_lower = result.stderr.lower()
if any(keyword in error_lower for keyword in ['already exists', 'already set', 'exist']):
logger.info(f"忽略预期中的错误: {result.stderr.strip()}")
return True
else:
logger.warning(f"命令执行失败但忽略错误: {cmd}")
logger.warning(f"错误信息: {result.stderr}")
return True
else:
logger.error(f"命令执行失败: {cmd}")
logger.error(f"错误信息: {result.stderr}")
return False
return True
except Exception as e:
logger.error(f"执行命令错误: {e}")
return False
def get_current_display_settings(self):
"""获取当前显示器的设置"""
# 首先检查显示器是否连接
if not self.is_display_connected():
return None
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
import subprocess
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error("获取显示器信息失败")
return None
# 解析xrandr输出,找到指定显示器的当前设置
lines = result.stdout.split('\n')
current_settings = {}
for line in lines:
# 查找目标显示器的连接状态行
if line.startswith(f'{self.display_name} '):
# 检查是否是主屏
current_settings['primary'] = 'primary' in line
# 提取当前分辨率
res_match = re.search(r'(\d+x\d+)\+(-?\d+)\+(-?\d+)', line)
if res_match:
current_settings['resolution'] = res_match.group(1)
current_settings['pos_x'] = int(res_match.group(2))
current_settings['pos_y'] = int(res_match.group(3))
# 提取旋转信息
if 'inverted' in line and 'x axis y axis' not in line:
current_settings['rotation'] = 'inverted'
elif 'left' in line and 'x axis y axis' not in line:
current_settings['rotation'] = 'left'
elif 'right' in line and 'x axis y axis' not in line:
current_settings['rotation'] = 'right'
else:
current_settings['rotation'] = 'normal'
# 继续查找当前模式行以获取刷新率
for next_line in lines[lines.index(line)+1:]:
if next_line.strip() and not next_line.startswith(' '):
break
# 查找当前模式(带*的)
if '*'+'+' in next_line or '* ' in next_line:
rate_match = re.search(r'(\d+\.\d+)\*', next_line)
if rate_match:
current_settings['refresh_rate'] = float(rate_match.group(1))
break
self.current_settings = current_settings
return current_settings
except Exception as e:
logger.error(f"获取当前显示器设置错误: {e}")
return None
def get_display_modes(self):
"""
获取显示器支持的模式列表
返回: 字典,键为分辨率,值为该分辨率下支持的刷新率列表
"""
# 首先检查显示器是否连接
if not self.is_display_connected():
return {}
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
import subprocess
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error("获取显示器信息失败")
return {}
# 解析xrandr输出,找到指定显示器的模式
lines = result.stdout.split('\n')
modes = {}
in_target_display = False
current_resolution = None
for line in lines:
# 检查是否进入目标显示器的部分
if line.startswith(f'{self.display_name} '):
in_target_display = True
continue
elif in_target_display and line.strip() and not line.startswith(' '):
# 新的显示器部分开始,退出
break
if in_target_display:
# 解析分辨率行
res_match = re.search(r'^\s*(\d+x\d+i?)\s+', line)
if res_match:
current_resolution = res_match.group(1)
modes[current_resolution] = []
# 解析刷新率
if current_resolution:
rate_matches = re.findall(r'(\d+\.\d+)\*?\+?', line)
for rate in rate_matches:
modes[current_resolution].append(float(rate))
return modes
except Exception as e:
logger.error(f"获取显示器模式错误: {e}")
return {}
def get_existing_modelines(self):
"""获取已经存在的自定义模式"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
import subprocess
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
return []
# 查找自定义模式
lines = result.stdout.split('\n')
modelines = []
capturing_modelines = True
for line in lines:
if re.match(r'^\S+ connected', line):
capturing_modelines = False
continue
if capturing_modelines and line.strip():
mode_match = re.match(r'^\s*(\S+)\s+.*?(\d+\.\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)\s+.*?(\d+)', line)
if mode_match:
modelines.append(mode_match.group(1))
return modelines
except Exception as e:
logger.error(f"获取已存在模式错误: {e}")
return []
def find_matching_mode(self, modes):
"""在支持的模式中查找匹配的模式(允许刷新率误差)"""
target_res = f"{self.config_data['width']}x{self.config_data['height']}"
target_refresh = self.config_data['refresh_rate']
# 检查精确匹配的分辨率
if target_res in modes:
for refresh_rate in modes[target_res]:
# 允许±2fps的误差
if abs(refresh_rate - target_refresh) <= 2.0:
return f"{target_res} {refresh_rate}"
# 检查隔行扫描变体
interlaced_res = f"{target_res}i"
if interlaced_res in modes:
for refresh_rate in modes[interlaced_res]:
if abs(refresh_rate - target_refresh) <= 2.0:
return f"{interlaced_res} {refresh_rate}"
return None
def create_and_add_mode(self):
"""使用cvt创建并添加新的显示模式"""
width = self.config_data['width']
height = self.config_data['height']
refresh_rate = self.config_data['refresh_rate']
# 生成模式名称
mode_name = f"{width}x{height}_{refresh_rate}"
# 检查模式是否已经存在
existing_modelines = self.get_existing_modelines()
if mode_name in existing_modelines:
logger.info(f"模式 {mode_name} 已经存在,跳过创建")
else:
# 使用cvt生成模式行
try:
import subprocess
result = subprocess.run(
['cvt', str(width), str(height), str(refresh_rate)],
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error("cvt命令执行失败")
return None
# 从cvt输出中提取模式行
lines = result.stdout.split('\n')
modeline = None
for line in lines:
if line.startswith('Modeline '):
modeline = line.replace('Modeline ', '').strip()
break
if not modeline:
logger.error("无法从cvt输出中提取模式行")
return None
# 添加新模式
add_mode_cmd = f"xrandr --newmode {mode_name} {modeline.split(' ', 1)[1]}"
if not self.run_xrandr_command(add_mode_cmd, ignore_errors=True):
return None
logger.info(f"成功创建新模式: {mode_name}")
except Exception as e:
logger.error(f"创建模式错误: {e}")
return None
# 将新模式添加到显示器
add_to_output_cmd = f"xrandr --addmode {self.display_name} {mode_name}"
if not self.run_xrandr_command(add_to_output_cmd, ignore_errors=True):
return None
return mode_name
def parse_resolution(self, resolution_str):
"""解析分辨率字符串,返回宽高元组"""
try:
if 'x' in resolution_str:
width, height = resolution_str.split('x')
height = height.replace('i', '')
return int(width), int(height)
return 0, 0
except:
return 0, 0
def compare_settings(self):
"""比较当前设置与配置设置"""
if not self.current_settings or not self.config_data:
return False, "无法获取当前设置或配置数据"
# 检查分辨率(允许±10像素误差)
current_res = self.current_settings.get('resolution', '')
current_width, current_height = self.parse_resolution(current_res)
target_width = self.config_data['width']
target_height = self.config_data['height']
width_diff = abs(current_width - target_width)
height_diff = abs(current_height - target_height)
if width_diff > 10 or height_diff > 10:
return False, f"分辨率不匹配: 当前 {current_res}, 配置 {target_width}x{target_height}"
# 检查刷新率(允许±2fps误差)
current_refresh = self.current_settings.get('refresh_rate', 0)
target_refresh = self.config_data['refresh_rate']
refresh_diff = abs(current_refresh - target_refresh)
if refresh_diff > 2.0:
return False, f"刷新率不匹配: 当前 {current_refresh:.1f}Hz, 配置 {target_refresh:.1f}Hz"
# 检查旋转
current_rotation = self.current_settings.get('rotation', 'normal')
if current_rotation != self.config_data['rotation']:
return False, f"旋转不匹配: 当前 {current_rotation}, 配置 {self.config_data['rotation']}"
# 检查主屏设置
current_primary = self.current_settings.get('primary', False)
if current_primary != self.config_data['primary']:
primary_status_current = "" if current_primary else ""
primary_status_target = "" if self.config_data['primary'] else ""
return False, f"主屏设置不匹配: 当前 {primary_status_current}, 配置 {primary_status_target}"
# 位置不进行校验
match_details = [
f"分辨率: {current_res}",
f"刷新率: {current_refresh:.1f}Hz",
f"旋转: {current_rotation}",
f"主屏: {'' if current_primary else ''}",
"位置: 跳过校验"
]
return True, " | ".join(match_details)
def configure_display(self):
"""配置显示器"""
if not self.config_data:
logger.error("没有可用的配置数据")
return False
# 检查显示器是否连接
if not self.is_display_connected():
logger.warning(f"显示器 {self.display_name} 未连接,跳过配置")
return True # 返回True表示跳过而不是失败
# 获取当前支持的模式
modes = self.get_display_modes()
if not modes:
logger.error(f"无法获取显示器 {self.display_name} 的模式信息")
return False
logger.info(f"显示器 {self.display_name} 支持的模式:")
for res, rates in modes.items():
logger.info(f" {res}: {rates}")
# 根据模式标志决定行为
mode_flag = self.config_data.get('mode_flag', 'A') # 默认为A
if mode_flag == 'F':
# F模式:必须使用cvt生成分辨率
logger.info("模式标志为F,强制使用cvt生成分辨率")
mode_name = self.create_and_add_mode()
if not mode_name:
logger.error("创建新模式失败")
return False
else:
# A模式:沿用原来的方法
logger.info("模式标志为A,使用自动模式选择")
mode_name = self.find_matching_mode(modes)
# 如果不支持,创建新模式
if not mode_name:
logger.info("显示器不支持该模式,尝试创建新模式...")
mode_name = self.create_and_add_mode()
if not mode_name:
logger.error("创建新模式失败")
return False
else:
logger.info(f"找到匹配模式: {mode_name}")
# 构建xrandr命令
cmd_parts = ["xrandr", f"--output {self.display_name}"]
# 添加模式
if ' ' in mode_name:
resolution, rate = mode_name.split(' ', 1)
cmd_parts.append(f"--mode {resolution}")
cmd_parts.append(f"--rate {rate}")
else:
cmd_parts.append(f"--mode {mode_name}")
# 添加位置
cmd_parts.append(f"--pos {self.config_data['pos_x']}x{self.config_data['pos_y']}")
# 添加旋转
cmd_parts.append(f"--rotate {self.config_data['rotation']}")
# 如果是主屏
if self.config_data['primary']:
cmd_parts.append("--primary")
# 启用显示器
cmd_parts.append("--auto")
# 执行最终配置命令
final_cmd = " ".join(cmd_parts)
logger.info(f"执行命令: {final_cmd}")
if self.run_xrandr_command(final_cmd):
logger.info("显示器配置成功!")
return True
else:
logger.error("显示器配置失败!")
return False
def check_and_configure(self, config_file_path, is_first=False):
"""检查并配置显示器 - 主要入口方法"""
# 解析配置文件
if not self.parse_config_file(config_file_path):
logger.error(f"解析配置文件失败: {config_file_path}")
return False
# 检查显示器是否连接
if not self.is_display_connected():
logger.warning(f"显示器 {self.display_name} 未连接,跳过配置")
return True # 返回True表示跳过而不是失败
# 如果是首次运行,无视显示器配置
if is_first:
# logger.info("启动程序后首次设置,开始配置显示器")
return self.configure_display()
# 获取当前设置
current_settings = self.get_current_display_settings()
if not current_settings:
logger.warning("无法获取当前显示器设置,尝试直接配置...")
return self.configure_display()
logger.info("当前显示器设置:")
for key, value in current_settings.items():
logger.info(f" {key}: {value}")
# 比较设置
match, message = self.compare_settings()
if match:
logger.info(f"✓ 设置匹配: {message}")
return True
else:
logger.info(f"✗ 设置不匹配: {message}")
logger.info("开始配置显示器...")
return self.configure_display()
class FileMonitor:
def __init__(self):
self.os_version_file = Path("/etc/os-version")
self.screen_file = Path("/tmp/ktouch/screen.txt")
self.settings_dir = Path("/opt/ktouch/display_config")
self.display_update_file = Path("/tmp/ktouch/display_update.txt")
# 存储文件最后修改时间和内容
self.last_mod_time = None
self.last_content = None # 新增:存储文件内容
self.last_display_update_time = 0
# 防止重复触发的机制
self.last_operation_time = None
self.cooldown_period = 15 # 冷却时间15秒
# 设置信号处理,用于优雅退出
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
self.running = True
self.display_checker = DisplayChecker()
def signal_handler(self, signum, frame):
"""处理中断信号"""
logger.info(f"接收到信号 {signum},正在退出...")
self.running = False
def check_os_version(self):
"""检查 /etc/os-version 文件是否存在"""
if not self.os_version_file.exists():
logger.error(f"文件 {self.os_version_file} 不存在,退出脚本")
sys.exit(1)
logger.info(f"检测到 {self.os_version_file} 文件,继续执行")
def wait_for_screen_file(self):
"""等待 screen.txt 文件生成"""
while not self.screen_file.exists() and self.running:
logger.info(f"等待 {self.screen_file} 文件生成...")
time.sleep(5)
if not self.running:
return False
# 获取初始修改时间和内容
self.last_mod_time = self.screen_file.stat().st_mtime
self.last_content = self.read_file_content(self.screen_file)
logger.info(f"检测到 {self.screen_file} 文件,初始修改时间: {time.ctime(self.last_mod_time)}")
return True
def read_file_content(self, file_path):
"""读取文件内容"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return f.read().strip()
except Exception as e:
logger.error(f"读取文件内容失败 {file_path}: {e}")
return None
def is_in_cooldown_period(self):
"""检查是否在冷却期内"""
if self.last_operation_time is None:
return False
elapsed_time = time.time() - self.last_operation_time
return elapsed_time < self.cooldown_period
def update_file_modification_time(self):
"""更新文件修改时间(用于防止重复触发)"""
try:
current_time = time.time()
os.utime(self.screen_file, (current_time, current_time))
self.last_mod_time = current_time
logger.info(f"已更新文件修改时间以防止重复触发")
return True
except Exception as e:
logger.error(f"更新文件修改时间失败: {e}")
return False
def check_file_modified(self):
"""检查文件是否被修改(同时检查修改时间和内容)"""
if not self.screen_file.exists():
logger.warning(f"文件 {self.screen_file} 不存在")
return False
# 检查是否在冷却期内
if self.is_in_cooldown_period():
logger.info("在冷却期内,跳过文件修改检查")
return False
try:
current_mod_time = self.screen_file.stat().st_mtime
current_content = self.read_file_content(self.screen_file)
# 如果无法读取内容,则跳过
if current_content is None:
return False
# 检查修改时间是否变化
if current_mod_time != self.last_mod_time:
logger.info(f"检测到文件修改时间变化 - 原时间: {time.ctime(self.last_mod_time)}, 新时间: {time.ctime(current_mod_time)}")
# 检查内容是否变化
if current_content != self.last_content:
logger.info("检测到文件内容实际发生变化,需要执行分辨率设置")
self.last_mod_time = current_mod_time
self.last_content = current_content
return True
else:
logger.info("文件修改时间变化但内容未变,只更新修改时间")
self.last_mod_time = current_mod_time
# 更新文件修改时间防止重复触发
self.update_file_modification_time()
return False
except OSError as e:
logger.error(f"无法获取文件状态: {e}")
return False
def check_display_update_file(self):
"""检查display_update.txt文件内容是否为1"""
if not self.display_update_file.exists():
# 文件不存在,忽略检测
return False
try:
# 检查文件修改时间,避免重复处理
current_mtime = self.display_update_file.stat().st_mtime
if current_mtime <= self.last_display_update_time:
return False
self.last_display_update_time = current_mtime
content = self.read_file_content(self.display_update_file)
if content == "1":
logger.info(f"检测到 {self.display_update_file} 文件内容为1,立即进行分辨率设置")
# 将文件内容改为0
try:
with open(self.display_update_file, 'w') as f:
f.write("0")
logger.info(f"已将 {self.display_update_file} 文件内容修改为0")
except Exception as e:
logger.error(f"修改display_update.txt文件失败: {e}")
return True
except Exception as e:
logger.error(f"检查display_update.txt文件失败: {e}")
return False
def execute_display_settings(self, is_first=False):
"""执行 display_settings 文件夹中的所有文件 - 直接调用方法"""
# 检查目录是否存在
if not self.settings_dir.exists():
logger.error(f"目录 {self.settings_dir} 不存在")
return False
# 获取目录中的所有文件
setting_files = list(self.settings_dir.glob("*"))
if not setting_files:
logger.warning(f"目录 {self.settings_dir} 中没有文件")
return True
success_count = 0
total_count = len(setting_files)
skipped_count = 0
for file_path in setting_files:
if file_path.is_file():
try:
logger.info(f"处理显示器配置文件: {file_path}")
# 直接调用DisplayChecker的方法,而不是通过子进程
result = self.display_checker.check_and_configure(str(file_path), is_first)
if result:
logger.info(f"成功处理: {file_path}")
success_count += 1
else:
# 检查是否是跳过(显示器未连接)
if not self.display_checker.is_display_connected():
logger.warning(f"跳过未连接的显示器: {file_path}")
skipped_count += 1
else:
logger.error(f"处理失败: {file_path}")
except Exception as e:
logger.error(f"处理异常: {file_path}, 异常: {e}")
logger.info(f"执行完成: 成功 {success_count}, 跳过 {skipped_count}, 总计 {total_count} 个文件")
# 记录操作时间并更新文件修改时间
self.last_operation_time = time.time()
# 等待15秒后更新文件修改时间,防止重复触发
logger.info(f"等待 {self.cooldown_period} 秒后更新文件修改时间...")
time.sleep(self.cooldown_period)
if self.running:
self.update_file_modification_time()
return success_count > 0 # 只要有一个成功就返回True
def initial_resolution_check(self):
"""初始分辨率检查 - 检查十次,每次间隔15秒"""
logger.info("开始初始分辨率检查流程...")
# 检查目录是否存在
if not self.settings_dir.exists():
logger.error(f"目录 {self.settings_dir} 不存在")
return False
# 获取目录中的所有文件
setting_files = list(self.settings_dir.glob("*"))
if not setting_files:
logger.warning(f"目录 {self.settings_dir} 中没有文件")
return True
# 进行十次检查,每次间隔15秒
max_attempts = 10
check_interval = 15 # 秒
for attempt in range(1, max_attempts + 1):
if not self.running:
logger.info("程序正在退出,终止初始分辨率检查")
return False
logger.info(f"初始分辨率检查 - 第 {attempt}/{max_attempts}")
success_count = 0
total_count = len(setting_files)
need_adjustment = False
for file_path in setting_files:
if not self.running:
break
if file_path.is_file():
try:
logger.info(f"检查显示器配置文件: {file_path}")
# 为每个文件创建新的DisplayChecker实例
display_checker = DisplayChecker()
# 解析配置文件
if not display_checker.parse_config_file(str(file_path)):
logger.error(f"解析配置文件失败: {file_path}")
continue
# 检查显示器是否连接
if not display_checker.is_display_connected():
logger.warning(f"显示器 {display_checker.display_name} 未连接,跳过检查")
continue
# 获取当前设置
current_settings = display_checker.get_current_display_settings()
if not current_settings:
logger.warning(f"无法获取显示器 {display_checker.display_name} 的当前设置")
need_adjustment = True
continue
# 比较设置
match, message = display_checker.compare_settings()
if match:
logger.info(f"✓ 显示器 {display_checker.display_name} 设置匹配: {message}")
success_count += 1
else:
logger.info(f"✗ 显示器 {display_checker.display_name} 设置不匹配: {message}")
need_adjustment = True
# 立即进行配置
logger.info(f"立即配置显示器 {display_checker.display_name}...")
if display_checker.configure_display():
logger.info(f"显示器 {display_checker.display_name} 配置成功")
success_count += 1
else:
logger.error(f"显示器 {display_checker.display_name} 配置失败")
except Exception as e:
logger.error(f"处理异常: {file_path}, 异常: {e}")
# 检查是否所有显示器都配置正确
if success_count == total_count:
logger.info(f"✓ 第 {attempt} 次检查完成: 所有显示器配置正确")
break
else:
logger.info(f"{attempt} 次检查完成: {success_count}/{total_count} 个显示器配置正确")
# 如果不是最后一次检查,等待间隔时间
if attempt < max_attempts:
if need_adjustment:
logger.info(f"等待 {check_interval} 秒后进行下一次检查...")
for i in range(check_interval):
if not self.running:
break
time.sleep(1)
logger.info("初始分辨率检查流程完成")
return True
def monitor(self):
"""主监控循环"""
logger.info("启动文件监控脚本...")
# 检查os-version文件
self.check_os_version()
logger.info("进行首次分辨率设置")
self.execute_display_settings(is_first=True)
time.sleep(10)
# 等待screen文件生成
if not self.wait_for_screen_file():
return
# 在screen.txt生成后,进行初始分辨率检查
logger.info("screen.txt文件已生成,开始初始分辨率检查...")
self.initial_resolution_check()
logger.info("开始监控文件变化...")
while self.running:
try:
# 检查display_update.txt文件
if self.check_display_update_file():
logger.info("检测到display_update.txt触发,立即执行分辨率设置...")
self.execute_display_settings()
logger.info("display_update.txt触发操作完成,继续监控...")
# 检查screen.txt文件是否被修改
if self.check_file_modified():
logger.info("检测到screen.txt文件内容变化,执行display_settings中的文件...")
self.execute_display_settings()
logger.info("screen.txt变化操作完成,继续监控...")
# 等待1秒后继续检查
time.sleep(1)
except KeyboardInterrupt:
logger.info("用户中断监控")
break
except Exception as e:
logger.error(f"监控过程中发生异常: {e}")
time.sleep(10)
logger.info("监控脚本已停止")
def main():
"""主函数"""
monitor = FileMonitor()
monitor.monitor()
if __name__ == "__main__":
main()
+53
View File
@@ -0,0 +1,53 @@
# check_save.c - 保存确认对话框
## 概述
这是一个GTK+图形界面程序,用于在多显示器环境下显示保存确认对话框。用户可以在任意显示器上点击"保存"或"取消"按钮,程序会返回相应的退出代码供调用方判断。
## 程序结构
### 数据结构
```c
typedef struct {
GtkWidget *window; // GTK窗口指针
int monitor_num; // 显示器编号
int x, y; // 显示器位置
int width, height; // 显示器分辨率
char *name; // 显示器名称/接口名
} MonitorInfo;
```
### 全局变量
- `monitors`: 显示器信息数组
- `monitor_count`: 显示器数量
- `app`: GTK应用程序实例
- `exit_code`: 退出代码(默认1=取消)
## 工作原理
1. **初始化阶段**: 检查DISPLAY环境变量,创建GTK应用
2. **显示器枚举**: 使用`gdk_screen_get_n_monitors()`获取所有显示器信息
3. **窗口创建**: 为每个显示器创建一个400x200的居中确认窗口
4. **用户交互**:
- 点击"保存" → 打印"0",退出代码=0
- 点击"取消并退出" → 打印"1",退出代码=1
- 关闭窗口 → 打印"1",退出代码=1
## 关键函数
| 函数 | 功能 |
|------|------|
| `activate()` | 应用启动回调,枚举显示器并创建窗口 |
| `create_monitor_window()` | 为指定显示器创建确认窗口 |
| `on_save_clicked()` | 保存按钮回调 |
| `on_cancel_clicked()` | 取消按钮回调 |
| `show_error_dialog()` | 显示错误对话框 |
## 调用方式
```bash
./check_save
# 返回值: 0=保存, 1=取消
```
## 依赖
- GTK+ 3.x
- GDK (含X11后端)
+53
View File
@@ -0,0 +1,53 @@
# demo.c - 多屏幕信息窗口演示程序
## 概述
一个基于GTK+和Xrandr的多屏幕信息演示程序。在所有连接的显示器上依次创建信息窗口,显示屏幕分辨率、位置等信息,支持多轮测试和窗口位置校验。
## 程序结构
### 全局变量
| 变量 | 类型 | 说明 |
|------|------|------|
| `info_windows` | `GtkWidget**` | 各屏幕信息窗口指针数组 |
| `total_screens` | `int` | 连接的显示器总数 |
| `screen_widths/heights` | `int*` | 各屏幕宽高 |
| `screen_x_offsets/y_offsets` | `int*` | 各屏幕坐标偏移 |
| `screen_names` | `char**` | 屏幕接口名(如HDMI-1) |
| `current_test_round` | `int` | 当前测试轮次 |
| `total_test_rounds` | `int` | 总测试轮次 |
| `win_time_delay` | `int` | 窗口创建间隔(ms),默认50 |
## 工作原理
1. **屏幕信息获取**: 使用Xrandr扩展查询所有已连接的输出设备及其CRTC信息
2. **窗口创建**: 每个屏幕创建一个比屏幕尺寸小20像素的窗口,偏移(10,10)
3. **定时创建**: 窗口按`win_time_delay`毫秒间隔依次创建
4. **空格键控制**: 用户按空格键关闭所有窗口,进入下一轮测试
5. **窗口校验**: 每轮结束后校验窗口位置和大小(允许±5像素误差)
6. **日志记录**: 所有操作记录到`screen_demo.log`文件
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_screen_info()` | 通过Xrandr获取所有屏幕信息 |
| `create_info_window()` | 创建单个屏幕的信息窗口 |
| `create_next_window()` | 定时回调,依次创建窗口 |
| `verify_windows()` | 校验窗口位置和大小 |
| `start_next_test_round()` | 开始新一轮测试 |
| `on_key_press()` | 空格键事件处理 |
## 窗口布局
```
窗口位置: (screen_x + 10, screen_y + 10)
窗口大小: (screen_width - 20) x (screen_height - 20)
```
## 调用方式
```bash
./demo
```
## 依赖
- GTK+ 3.x
- X11 + Xrandr扩展
+26
View File
@@ -0,0 +1,26 @@
# gdk_test.c - GDK类型测试
## 概述
一个极简的编译测试程序,用于验证`GdkMonitor`类型在当前GTK/GDK版本中是否可用。
## 代码
```c
#include <gdk/gdk.h>
#include <gtk/gtk.h>
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
GdkMonitor *monitor; // 仅测试该类型是否可识别
return 0;
}
```
## 用途
该文件不包含实际功能逻辑,仅用于:
- 验证编译环境中GDK头文件是否包含`GdkMonitor`类型定义
- 确认GTK版本是否支持该类型(GTK 3.22+引入)
## 编译测试
```bash
gcc gdk_test.c -o gdk_test $(pkg-config --cflags --libs gtk+-3.0)
```
+89
View File
@@ -0,0 +1,89 @@
# screen_binder.c - 触摸屏与显示器绑定程序
## 概述
核心模块之一,用于将触摸屏设备与显示器进行一一绑定。程序在每个显示器上依次弹出校准窗口,用户点击触摸屏后,程序记录触摸设备与当前显示器的对应关系,最终保存到`touch_dis_table.txt`文件。
## 程序结构
### 数据结构
```c
// 屏幕信息
int *screen_widths, *screen_heights; // 屏幕宽高
int *screen_x_offsets, *screen_y_offsets; // 屏幕坐标
char **screen_names; // 屏幕接口名
// 触摸绑定信息
int *touch_bindings; // 每个屏幕绑定的触摸设备ID
char **touch_names; // 触摸设备名称
char **touch_paths; // 触摸设备路径
```
### 线程模型
| 线程 | 功能 |
|------|------|
| 主线程 | GTK事件循环,窗口管理 |
| `touch_thread` | 触摸事件监听线程 |
| `enter_thread` | 全局回车键监听线程(X11) |
### 原子变量(线程安全)
```c
_Atomic int global_enter_pressed; // 全局回车键按下标志
_Atomic int program_active; // 程序活动状态
_Atomic int calibration_active; // 校准进行中标志
```
## 工作原理
1. **屏幕枚举**: 通过Xrandr获取所有已连接显示器
2. **触摸初始化**: 初始化触摸监听器,启动触摸事件监听线程
3. **全局回车监听**: 启动独立线程监听全局回车键(用于跳过非触摸屏)
4. **信息窗口创建**: 依次为每个非当前校准屏幕创建提示窗口
5. **校准流程**:
- 在当前屏幕创建全屏校准窗口
- 等待用户点击触摸屏(60秒超时)
- 记录触摸设备ID、名称、路径
- 用户按回车键可跳过当前屏幕
6. **保存结果**: 将绑定关系保存到`touch_dis_table.txt`
## 校准窗口交互
```
┌─────────────────────────────────────┐
│ 请在触摸屏上点击此屏幕。 │
│ │
│ 屏幕接口: HDMI-1 │
│ │
│ 如果此屏幕不是触摸屏, │
│ 那么请敲击回车或者等待60秒。 │
└─────────────────────────────────────┘
```
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_screen_info()` | Xrandr获取屏幕信息 |
| `init_touch_listener()` | 初始化触摸监听 |
| `touch_callback()` | 触摸事件回调,记录绑定 |
| `global_enter_listener_thread()` | X11全局回车键监听 |
| `show_next_screen()` | 显示下一个校准屏幕 |
| `save_mapping_table()` | 保存绑定关系到文件 |
| `create_calibration_window()` | 创建校准窗口 |
| `create_info_window()` | 创建信息提示窗口 |
## 输出格式
文件`touch_dis_table.txt`格式:
```
屏幕接口名|触摸设备名|触摸设备ID|设备路径
HDMI-1|USB Touchscreen|12345|/dev/input/event5
```
## 调用方式
```bash
sudo ./screen_binder # 需要sudo权限访问触摸设备
```
## 依赖
- GTK+ 3.x
- X11 + Xrandr扩展
- touch_listen模块(触摸事件监听)
- 需要root权限访问`/dev/input/`设备
+70
View File
@@ -0,0 +1,70 @@
# screen_ds.c - 显示器信息持续监控程序
## 概述
一个基于Xrandr的显示器状态持续监控程序。持续监听显示器的连接/断开事件,当检测到变化时(防抖3秒后)输出所有显示器的当前信息。
## 程序结构
### 数据结构
```c
typedef struct {
char* output_name; // 输出接口名(如HDMI-1)
int width, height; // 分辨率
int x, y; // 位置坐标
int rotation; // 旋转方向
} MonitorInfo;
typedef struct {
MonitorInfo* monitors; // 显示器数组
int count; // 显示器数量
} MonitorList;
```
### 旋转方向编码
| 值 | 字母 | 说明 |
|----|------|------|
| RR_Rotate_0 | N | 正常方向 |
| RR_Rotate_90 | L | 左旋转90度 |
| RR_Rotate_180 | I | 翻转180度 |
| RR_Rotate_270 | R | 右旋转90度 |
## 工作原理
1. **初始化**: 打开X显示连接,注册RandR事件监听
2. **首次输出**: 获取并输出当前所有显示器信息
3. **事件循环**:
- 监听`RRScreenChangeNotify``RRNotify`事件
- 检测到事件后设置`need_update`标志
- 防抖3秒后(`DEBOUNCE_TIME`)重新获取并输出显示器信息
4. **无事件时**: 休眠100ms减少CPU占用
## 输出格式
```
HDMI-1|1920x1080|0x0|N
DP-2|1920x1080|1920x0|N
```
每行格式: `接口名|分辨率|位置|旋转方向`
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_monitor_info()` | 获取所有连接的显示器信息 |
| `output_monitor_info()` | 输出显示器信息到文件或stdout |
| `rotation_to_char()` | 旋转值转字母表示 |
| `current_timestamp()` | 获取毫秒级时间戳 |
## 调用方式
```bash
./screen_ds [输出文件路径]
# 例: ./screen_ds /tmp/screen.txt
# 不指定参数则输出到标准输出
```
## 特性
- 3秒防抖:避免频繁热插拔导致的重复输出
- 持续运行,直到Ctrl+C退出
- 支持输出到文件或标准输出
## 依赖
- X11 + Xrandr扩展
+50
View File
@@ -0,0 +1,50 @@
# screen_ds_once.c - 显示器信息单次查询程序
## 概述
screen_ds.c的简化版本,仅执行一次显示器信息查询并输出结果后退出。适用于需要快速获取当前显示器配置的场景。
## 与screen_ds.c的区别
| 特性 | screen_ds | screen_ds_once |
|------|-----------|----------------|
| 执行模式 | 持续监控 | 单次查询 |
| 事件监听 | 有 | 无 |
| 退出方式 | Ctrl+C | 自动退出 |
## 工作原理
1. 打开X显示连接
2. 查询RandR扩展是否可用
3. 获取所有已连接显示器信息
4. 输出到文件或标准输出
5. 释放资源并退出
## 数据结构
与screen_ds.c相同:
```c
typedef struct {
char* output_name;
int width, height;
int x, y;
int rotation;
} MonitorInfo;
```
## 输出格式
```
HDMI-1|1920x1080|0x0|N
```
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_monitor_info()` | 获取显示器信息 |
| `output_monitor_info()` | 输出信息 |
| `rotation_to_char()` | 旋转方向编码转换 |
## 调用方式
```bash
./screen_ds_once [输出文件路径]
```
## 依赖
- X11 + Xrandr扩展
+73
View File
@@ -0,0 +1,73 @@
# touch_ds.c - 触摸屏坐标矩阵监控程序
## 概述
持续监控所有触摸屏设备的坐标转换矩阵(Coordinate Transformation Matrix)。当检测到某个触摸设备的矩阵发生变化时,设置flag文件通知外部程序。
## 程序结构
### 数据结构
```c
typedef struct {
XID deviceid; // XInput设备ID
char name[100]; // 设备名称
char node[100]; // 设备节点路径
char vid_pid[20]; // VID:PID
char matrix[120]; // 坐标转换矩阵字符串
} TouchscreenInfo;
```
### 配置常量
```c
int watch_time_delay = 30; // 监控间隔(秒)
```
## 工作原理
1. **读取配置**: 从触摸屏信息文件读取设备列表(含期望的矩阵值)
2. **X11连接**: 打开X显示连接,检查XInput扩展
3. **主循环**:
- 检查flag文件是否为1(暂停检测)
- 重新读取触摸屏信息文件
- 对每个设备获取当前矩阵值
- 比较矩阵是否与配置值不同
- 若变化,设置flag文件为1
- 等待30秒后继续
### 矩阵获取流程
```
设备 → XInput属性查询 → "Coordinate Transformation Matrix"
→ 或 "libinput Calibration Matrix"
→ 返回3x3浮点矩阵
```
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_device_matrix_property()` | 获取设备的3x3矩阵属性 |
| `get_matrix_string()` | 获取矩阵的字符串表示 |
| `device_exists()` | 检查设备是否仍然存在 |
| `is_touchscreen_device()` | 通过udev判断是否为触摸屏 |
| `read_touchscreens_from_file()` | 读取触摸屏配置文件 |
| `is_flag_set()` / `set_flag()` | flag文件读写 |
## 输入文件格式
触摸屏信息文件每行格式:
```
设备ID|设备名|设备节点|VID:PID|矩阵值
12345|USB Touchscreen|/dev/input/event5|046d:c52b|1.000000,0.000000,...
```
## flag文件机制
- flag文件值为`0`:正常监控
- flag文件值为`1`:暂停监控,等待外部恢复为`0`
## 调用方式
```bash
./touch_ds <触摸屏信息文件> [flag文件路径]
# 例: ./touch_ds /tmp/touch_info.txt /tmp/touch_matrix.flag
```
## 依赖
- X11 + XInput2扩展
- libudev
+99
View File
@@ -0,0 +1,99 @@
# touch_listen.c / touch_listen.h - 触摸事件监听库
## 概述
一个触摸屏事件监听库,通过直接读取Linux输入设备(`/dev/input/event*`)获取触摸事件。支持多点触控,提供回调函数接口供上层模块使用。
## 数据结构
### touch_device (设备信息)
```c
struct touch_device {
int fd; // 文件描述符
char name[256]; // 设备名称
char path[256]; // 设备路径
int min_x, max_x; // X轴范围
int min_y, max_y; // Y轴范围
};
```
### touch_event (触摸事件)
```c
struct touch_event {
int device_id; // 设备标识(路径哈希值)
int x, y; // 坐标
int pressure; // 压力值
int touch_id; // 触摸点ID(多点触控)
int event_type; // 事件类型: 0=按下, 1=移动, 2=释放
char device_name[256]; // 设备名称
char device_path[256]; // 设备路径
struct timespec timestamp; // 时间戳
};
```
### 回调函数类型
```c
typedef void (*touch_event_callback)(struct touch_event event);
```
## 工作原理
### 设备发现
1. 遍历`/dev/input/`目录下所有`event*`设备
2. 通过ioctl检查设备是否支持`EV_ABS``EV_KEY`
3. 进一步检查是否支持`ABS_MT_POSITION_X`(多点)或`ABS_X`(单点)
4. 获取设备名称和坐标范围
### 事件监听
1. 使用`select()`多路复用监听所有设备文件描述符
2. 读取`input_event`结构体
3. 处理事件类型:
- `EV_ABS`: 处理坐标、压力、触摸点ID
- `EV_KEY`: 处理`BTN_TOUCH`按键事件
- `EV_SYN`: 同步事件,触发回调
4. 通过回调函数通知上层
### 事件类型映射
| Linux事件 | 触摸事件类型 | 说明 |
|-----------|-------------|------|
| ABS_MT_TRACKING_ID != -1 | 0 (按下) | 新触摸点 |
| ABS_MT_TRACKING_ID == -1 | 2 (释放) | 触摸释放 |
| BTN_TOUCH = 1 | 0 (按下) | 触摸开始 |
| BTN_TOUCH = 0 | 2 (释放) | 触摸结束 |
| SYN_REPORT | 1 (移动) | 坐标更新 |
## API接口
| 函数 | 功能 |
|------|------|
| `init_touch_listener(callback)` | 初始化监听器,注册回调 |
| `listen_touch_events()` | 开始监听(阻塞) |
| `stop_touch_listener()` | 停止监听 |
| `cleanup_touch_listener()` | 清理资源 |
| `get_active_touch_devices()` | 获取活跃设备列表 |
## 调用示例
```c
#include "touch_listen.h"
void my_callback(struct touch_event event) {
printf("设备 %s: %s at (%d,%d)\n",
event.device_name,
event.event_type == 0 ? "按下" :
event.event_type == 1 ? "移动" : "释放",
event.x, event.y);
}
int main() {
init_touch_listener(my_callback);
listen_touch_events(); // 阻塞
cleanup_touch_listener();
return 0;
}
```
## 使用者
- `screen_binder.c` - 触摸屏绑定程序
## 依赖
- Linux输入子系统(`/dev/input/event*`)
- 需要root权限访问输入设备
+152
View File
@@ -0,0 +1,152 @@
# touch_set.c - 触摸屏坐标转换矩阵设置程序
## 概述
核心模块之一,负责根据配置文件和屏幕信息,自动计算并设置每个触摸屏设备的坐标转换矩阵(Coordinate Transformation Matrix),实现触摸屏与显示器的正确映射。
## 程序结构
### 数据结构
```c
// 设备信息
typedef struct {
int device_id; // XInput设备ID
char name[256]; // 设备名称
char device_node[256]; // 设备节点路径
char vid[10]; // 供应商ID
char pid[10]; // 产品ID
char usb_path[100]; // 物理路径
int is_touchscreen; // 是否为触摸屏
float matrix[9]; // 3x3坐标转换矩阵
int matched; // 是否已匹配
} InputDeviceInfo;
// 配置项
typedef struct {
char display_name[50]; // 显示器名称
char touchscreen_name[50]; // 触摸屏名称
char vid[10]; // 供应商ID
char pid[10]; // 产品ID
char usb_path[100]; // USB路径
int matched; // 是否已匹配
} TouchConfig;
// 屏幕信息
typedef struct {
char name[50]; // 屏幕名称
int width, height; // 分辨率
int x, y; // 位置
char rotation; // 旋转: N/L/R/I
} ScreenInfo;
```
## 工作原理
### 整体流程
```
读取配置文件(/opt/ktouch/config)
读取屏幕信息(/tmp/ktouch/screen.txt)
计算虚拟桌面大小
获取所有触摸设备(XInput + udev)
多轮匹配(严格→宽松)
计算坐标转换矩阵
通过XInput设置矩阵
保存结果到输出文件
```
### 矩阵计算原理
坐标转换矩阵将触摸屏的原始坐标映射到虚拟桌面坐标系:
**正常方向(N)**:
```
| sx 0 dx |
| 0 sy dy |
| 0 0 1 |
```
**左旋转(L)**:
```
| 0 -sx sx+dx |
| sy 0 dy |
| 0 0 1 |
```
**右旋转(R)**:
```
| 0 sx dx |
| -sy 0 sy+dy |
| 0 0 1 |
```
**翻转(I)**:
```
| -sx 0 sx+dx |
| 0 -sy sy+dy |
| 0 0 1 |
```
其中:
- `sx = 屏幕宽度 / 虚拟桌面宽度`
- `sy = 屏幕高度 / 虚拟桌面高度`
- `dx = 屏幕X偏移 / 虚拟桌面宽度`
- `dy = 屏幕Y偏移 / 虚拟桌面高度`
### 设备匹配策略
**第一轮(严格匹配)**:
1. 设备名完全匹配(去除空格后比较)
2. 若多个匹配,按VID:PID筛选
3. 若仍多个,按USB路径筛选
**第二轮及以后(宽松匹配)**:
1. 仅按VID:PID匹配
2. 若多个匹配,返回失败(-1)
## 关键函数
| 函数 | 功能 |
|------|------|
| `get_input_devices()` | 通过XInput+udev获取所有触摸设备 |
| `read_config()` | 读取触摸屏配置文件 |
| `read_screen_info()` | 读取屏幕信息文件 |
| `find_matching_device()` | 多策略设备匹配 |
| `calculate_virtual_desktop()` | 计算虚拟桌面总尺寸 |
| `calculate_ctm()` | 计算坐标转换矩阵 |
| `set_ctm()` | 通过XInput设置矩阵属性 |
| `devnode_to_syspath()` | 设备节点转sysfs路径 |
## 输入文件格式
### 配置文件(/opt/ktouch/config)
```
显示器名|触摸屏名|VID|PID|USB路径
HDMI-1|USB Touchscreen|046d|c52b|usb-0000:00:14.0-1
```
### 屏幕信息文件(/tmp/ktouch/screen.txt)
```
HDMI-1|1920x1080|0x0|N
DP-2|1920x1080|1920x0|N
```
### 输出文件格式
```
设备ID|设备名|设备节点|VID:PID|矩阵值
12345|USB Touchscreen|/dev/input/event5|046d:c52b|1.000000, 0.000000, ...
```
## 调用方式
```bash
./touch_set [输出文件路径]
```
## 依赖
- X11 + XInput2扩展
- libudev
+77
View File
@@ -0,0 +1,77 @@
# usb_ds.c - USB触摸屏设备热插拔监控程序
## 概述
通过udev监控USB设备的热插拔事件,当检测到新的触摸屏设备插入时,通过控制文件通知外部程序。支持轮询间隔配置和控制文件暂停机制。
## 程序结构
### 配置常量
```c
#define DEFAULT_POLL_INTERVAL 5 // 默认轮询间隔(秒)
#define MAX_RETRY_ATTEMPTS 3 // 最大重试次数
#define RETRY_DELAY 1 // 重试延迟(秒)
```
### 控制变量
```c
volatile sig_atomic_t keep_running = 1; // 运行标志
```
## 工作原理
### 监控流程
```
创建udev监控对象
过滤USB和input子系统事件
主循环:
├── 检查控制文件状态
│ └── 若非0,跳过本轮
├── select等待事件(超时=轮询间隔)
├── 有事件:
│ ├── 获取设备信息
│ ├── 检查是否为"add"动作
│ ├── 检查是否为指针设备
│ └── 检查是否为触摸屏
└── 更新控制文件状态
```
### 触摸屏判断
```c
int is_touchscreen(struct udev_device *dev) {
// 1. 检查ID_INPUT_TOUCHSCREEN属性
// 2. 检查设备名称是否包含"touch"/"touchscreen"/"tablet"
}
```
### 控制文件机制
- `0`: 正常监控状态
- `1`: 检测到触摸屏,等待外部处理后恢复为`0`
### 文件操作重试
所有文件读写操作最多重试3次,每次间隔1秒。
## 关键函数
| 函数 | 功能 |
|------|------|
| `monitor_usb_devices()` | 主监控循环 |
| `is_touchscreen()` | 判断设备是否为触摸屏 |
| `read_file_content_with_retry()` | 带重试的文件读取 |
| `write_file_content_with_retry()` | 带重试的文件写入 |
| `sigint_handler()` | SIGINT信号处理 |
## 调用方式
```bash
./usb_ds [控制文件路径] [轮询间隔秒数]
# 例: ./usb_ds /tmp/usb_monitor.ctl 5
# 不带参数: 无控制文件,5秒轮询
```
## 使用场景
当USB触摸屏热插拔时,通过控制文件通知主程序重新配置触摸映射。
## 依赖
- libudev
- POSIX信号处理
+541
View File
@@ -0,0 +1,541 @@
# Copyright (c) 2021 rdbende <rdbende@gmail.com>
# The Forest theme is a beautiful and modern ttk theme inspired by Excel.
package require Tk 8.6
namespace eval ttk::theme::forest-light {
variable version 1.0
package provide ttk::theme::forest-light $version
variable colors
array set colors {
-fg "#313131"
-bg "#ffffff"
-disabledfg "#595959"
-disabledbg "#ffffff"
-selectfg "#ffffff"
-selectbg "#217346"
}
proc LoadImages {imgdir} {
variable I
foreach file [glob -directory $imgdir *.png] {
set img [file tail [file rootname $file]]
set I($img) [image create photo -file $file -format png]
}
}
LoadImages [file join [file dirname [info script]] forest-light]
# Settings
ttk::style theme create forest-light -parent default -settings {
ttk::style configure . \
-background $colors(-bg) \
-foreground $colors(-fg) \
-troughcolor $colors(-bg) \
-focuscolor $colors(-selectbg) \
-selectbackground $colors(-selectbg) \
-selectforeground $colors(-selectfg) \
-insertwidth 1 \
-insertcolor $colors(-fg) \
-fieldbackground $colors(-selectbg) \
-font {TkDefaultFont 10} \
-borderwidth 1 \
-relief flat
ttk::style map . -foreground [list disabled $colors(-disabledfg)]
tk_setPalette background [ttk::style lookup . -background] \
foreground [ttk::style lookup . -foreground] \
highlightColor [ttk::style lookup . -focuscolor] \
selectBackground [ttk::style lookup . -selectbackground] \
selectForeground [ttk::style lookup . -selectforeground] \
activeBackground [ttk::style lookup . -selectbackground] \
activeForeground [ttk::style lookup . -selectforeground]
option add *font [ttk::style lookup . -font]
# Layouts
ttk::style layout TButton {
Button.button -children {
Button.padding -children {
Button.label -side left -expand true
}
}
}
ttk::style layout Toolbutton {
Toolbutton.button -children {
Toolbutton.padding -children {
Toolbutton.label -side left -expand true
}
}
}
ttk::style layout TMenubutton {
Menubutton.button -children {
Menubutton.padding -children {
Menubutton.indicator -side right
Menubutton.label -side right -expand true
}
}
}
ttk::style layout TOptionMenu {
OptionMenu.button -children {
OptionMenu.padding -children {
OptionMenu.indicator -side right
OptionMenu.label -side right -expand true
}
}
}
ttk::style layout Accent.TButton {
AccentButton.button -children {
AccentButton.padding -children {
AccentButton.label -side left -expand true
}
}
}
ttk::style layout TCheckbutton {
Checkbutton.button -children {
Checkbutton.padding -children {
Checkbutton.indicator -side left
Checkbutton.label -side right -expand true
}
}
}
ttk::style layout Switch {
Switch.button -children {
Switch.padding -children {
Switch.indicator -side left
Switch.label -side right -expand true
}
}
}
ttk::style layout ToggleButton {
ToggleButton.button -children {
ToggleButton.padding -children {
ToggleButton.label -side left -expand true
}
}
}
ttk::style layout TRadiobutton {
Radiobutton.button -children {
Radiobutton.padding -children {
Radiobutton.indicator -side left
Radiobutton.label -side right -expand true
}
}
}
ttk::style layout Vertical.TScrollbar {
Vertical.Scrollbar.trough -sticky ns -children {
Vertical.Scrollbar.thumb -expand true
}
}
ttk::style layout Horizontal.TScrollbar {
Horizontal.Scrollbar.trough -sticky ew -children {
Horizontal.Scrollbar.thumb -expand true
}
}
ttk::style layout TCombobox {
Combobox.field -sticky nswe -children {
Combobox.padding -expand true -sticky nswe -children {
Combobox.textarea -sticky nswe
}
}
Combobox.button -side right -sticky ns -children {
Combobox.arrow -sticky nsew
}
}
ttk::style layout TSpinbox {
Spinbox.field -sticky nsew -children {
Spinbox.padding -expand true -sticky nswe -children {
Spinbox.textarea -sticky nsew
}
}
null -side right -sticky nsew -children {
Spinbox.uparrow -side right -sticky nsew -children {
Spinbox.symuparrow
}
Spinbox.downarrow -side left -sticky nsew -children {
Spinbox.symdownarrow
}
}
}
ttk::style layout Horizontal.TSeparator {
Horizontal.separator -sticky nswe
}
ttk::style layout Vertical.TSeparator {
Vertical.separator -sticky nswe
}
ttk::style layout Card {
Card.field {
Card.padding -expand 1
}
}
ttk::style layout TLabelframe {
Labelframe.border {
Labelframe.padding -expand 1 -children {
Labelframe.label -side left
}
}
}
ttk::style layout TNotebook {
Notebook.border -children {
TNotebook.Tab -expand 1 -side top
Notebook.client -sticky nsew
}
}
ttk::style layout TNotebook.Tab {
Notebook.tab -children {
Notebook.padding -side top -children {
Notebook.label
}
}
}
ttk::style layout Treeview.Item {
Treeitem.padding -sticky nswe -children {
Treeitem.indicator -side left -sticky {}
Treeitem.image -side left -sticky {}
Treeitem.text -side left -sticky {}
}
}
# Elements
# Button
ttk::style configure TButton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Button.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-basic) \
disabled $I(rect-basic) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Toolbutton
ttk::style configure Toolbutton -padding {8 4 8 4} -width -10 -anchor center
ttk::style element create Toolbutton.button image \
[list $I(empty) \
{selected disabled} $I(empty) \
disabled $I(empty) \
selected $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-basic) \
] -border 4 -sticky nsew
# Menubutton
ttk::style configure TMenubutton -padding {8 4 4 4}
ttk::style element create Menubutton.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create Menubutton.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# OptionMenu
ttk::style configure TOptionMenu -padding {8 4 4 4}
ttk::style element create OptionMenu.button image \
[list $I(rect-basic) \
disabled $I(rect-basic) \
pressed $I(rect-basic) \
active $I(rect-hover) \
] -border 4 -sticky nsew
ttk::style element create OptionMenu.indicator image \
[list $I(down) \
active $I(down) \
pressed $I(down) \
disabled $I(down) \
] -width 15 -sticky e
# AccentButton
ttk::style configure Accent.TButton -padding {8 4 8 4} -width -10 -anchor center -foreground #eeeeee
ttk::style element create AccentButton.button image \
[list $I(rect-accent) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-accent-hover) \
selected $I(rect-accent) \
pressed $I(rect-accent) \
active $I(rect-accent-hover) \
] -border 4 -sticky nsew
# Checkbutton
ttk::style configure TCheckbutton -padding 4
ttk::style element create Checkbutton.indicator image \
[list $I(check-unsel-accent) \
{alternate disabled} $I(check-tri-basic) \
{selected disabled} $I(check-basic) \
disabled $I(check-unsel-basic) \
{pressed alternate} $I(check-tri-hover) \
{active alternate} $I(check-tri-hover) \
alternate $I(check-tri-accent) \
{pressed selected} $I(check-hover) \
{active selected} $I(check-hover) \
selected $I(check-accent) \
{pressed !selected} $I(check-unsel-pressed) \
active $I(check-unsel-hover) \
] -width 26 -sticky w
# Switch
ttk::style element create Switch.indicator image \
[list $I(off-accent) \
{selected disabled} $I(on-basic) \
disabled $I(off-basic) \
{pressed selected} $I(on-accent) \
{active selected} $I(on-hover) \
selected $I(on-accent) \
{pressed !selected} $I(off-accent) \
active $I(off-hover) \
] -width 46 -sticky w
# ToggleButton
ttk::style configure ToggleButton -padding {8 4 8 4} -width -10 -anchor center -foregound $colors(-fg)
ttk::style map ToggleButton -foreground \
[list {pressed selected} $colors(-fg) \
{pressed !selected} #ffffff \
selected #ffffff]
ttk::style element create ToggleButton.button image \
[list $I(rect-basic) \
{selected disabled} $I(rect-accent-hover) \
disabled $I(rect-basic) \
{pressed selected} $I(rect-basic) \
{active selected} $I(rect-accent-hover) \
selected $I(rect-accent) \
{pressed !selected} $I(rect-accent) \
active $I(rect-hover) \
] -border 4 -sticky nsew
# Radiobutton
ttk::style configure TRadiobutton -padding 4
ttk::style element create Radiobutton.indicator image \
[list $I(radio-unsel-accent) \
{alternate disabled} $I(radio-tri-basic) \
{selected disabled} $I(radio-basic) \
disabled $I(radio-unsel-basic) \
{pressed alternate} $I(radio-tri-hover) \
{active alternate} $I(radio-tri-hover) \
alternate $I(radio-tri-accent) \
{pressed selected} $I(radio-hover) \
{active selected} $I(radio-hover) \
selected $I(radio-accent) \
{pressed !selected} $I(radio-unsel-pressed) \
active $I(radio-unsel-hover) \
] -width 26 -sticky w
# Scrollbar
ttk::style element create Horizontal.Scrollbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Scrollbar.thumb image \
[list $I(hor-accent) \
disabled $I(hor-basic) \
pressed $I(hor-hover) \
active $I(hor-hover) \
] -sticky ew
ttk::style element create Vertical.Scrollbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Scrollbar.thumb image \
[list $I(vert-accent) \
disabled $I(vert-basic) \
pressed $I(vert-hover) \
active $I(vert-hover) \
] -sticky ns
# Scale
ttk::style element create Horizontal.Scale.trough image $I(scale-hor) \
-border 5 -padding 0
ttk::style element create Horizontal.Scale.slider image \
[list $I(thumb-hor-accent) \
disabled $I(thumb-hor-basic) \
pressed $I(thumb-hor-hover) \
active $I(thumb-hor-hover) \
] -sticky {}
ttk::style element create Vertical.Scale.trough image $I(scale-vert) \
-border 5 -padding 0
ttk::style element create Vertical.Scale.slider image \
[list $I(thumb-vert-accent) \
disabled $I(thumb-vert-basic) \
pressed $I(thumb-vert-hover) \
active $I(thumb-vert-hover) \
] -sticky {}
# Progressbar
ttk::style element create Horizontal.Progressbar.trough image $I(hor-basic) \
-sticky ew
ttk::style element create Horizontal.Progressbar.pbar image $I(hor-accent) \
-sticky ew
ttk::style element create Vertical.Progressbar.trough image $I(vert-basic) \
-sticky ns
ttk::style element create Vertical.Progressbar.pbar image $I(vert-accent) \
-sticky ns
# Entry
ttk::style element create Entry.field image \
[list $I(border-basic) \
{focus hover} $I(border-accent) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8} -sticky nsew
# Combobox
ttk::style map TCombobox -selectbackground [list \
{!focus} $colors(-selectbg) \
{readonly hover} $colors(-selectbg) \
{readonly focus} $colors(-selectbg) \
]
ttk::style map TCombobox -selectforeground [list \
{!focus} $colors(-selectfg) \
{readonly hover} $colors(-selectfg) \
{readonly focus} $colors(-selectfg) \
]
ttk::style element create Combobox.field image \
[list $I(border-basic) \
{readonly disabled} $I(rect-basic) \
{readonly pressed} $I(rect-basic) \
{readonly focus hover} $I(rect-hover) \
{readonly focus} $I(rect-hover) \
{readonly hover} $I(rect-hover) \
{focus hover} $I(border-accent) \
readonly $I(rect-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 28 8}
ttk::style element create Combobox.button image \
[list $I(combo-button-basic) \
{!readonly focus} $I(combo-button-focus) \
{readonly focus} $I(combo-button-hover) \
{readonly hover} $I(combo-button-hover)
] -border 5 -padding {2 6 6 6}
ttk::style element create Combobox.arrow image $I(down) -width 15 -sticky e
# Spinbox
ttk::style element create Spinbox.field image \
[list $I(border-basic) \
invalid $I(border-invalid) \
disabled $I(border-basic) \
focus $I(border-accent) \
hover $I(border-hover) \
] -border 5 -padding {8 8 54 8} -sticky nsew
ttk::style element create Spinbox.uparrow image $I(spin-button-up) -border 4 -sticky nsew
ttk::style element create Spinbox.downarrow image \
[list $I(spin-button-down-basic) \
focus $I(spin-button-down-focus) \
] -border 4 -sticky nsew
ttk::style element create Spinbox.symuparrow image $I(up) -width 15 -sticky {}
ttk::style element create Spinbox.symdownarrow image $I(down) -width 17 -sticky {}
# Sizegrip
ttk::style element create Sizegrip.sizegrip image $I(sizegrip) \
-sticky nsew
# Separator
ttk::style element create Horizontal.separator image $I(separator)
ttk::style element create Vertical.separator image $I(separator)
# Card
ttk::style element create Card.field image $I(card) \
-border 10 -padding 4 -sticky nsew
# Labelframe
ttk::style element create Labelframe.border image $I(card) \
-border 5 -padding 4 -sticky nsew
# Notebook
ttk::style configure TNotebook -padding 2
ttk::style element create Notebook.border image $I(card) -border 5
ttk::style element create Notebook.client image $I(notebook) -border 5
ttk::style element create Notebook.tab image \
[list $I(tab-basic) \
selected $I(tab-accent) \
active $I(tab-hover) \
] -border 5 -padding {14 4}
# Treeview
ttk::style element create Treeview.field image $I(card) \
-border 5
ttk::style element create Treeheading.cell image \
[list $I(tree-basic) \
pressed $I(tree-pressed)
] -border 5 -padding 6 -sticky nsew
ttk::style element create Treeitem.indicator image \
[list $I(right) \
user2 $I(empty) \
{user1 focus} $I(down-focus) \
focus $I(right-focus) \
user1 $I(down) \
] -width 17 -sticky {}
ttk::style configure Treeview -background $colors(-bg)
ttk::style configure Treeview.Item -padding {2 0 0 0}
ttk::style map Treeview \
-background [list selected $colors(-selectbg)] \
-foreground [list selected $colors(-selectfg)]
# Sashes
#ttk::style map TPanedwindow -background [list hover $colors(-bg)]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 679 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

+170
View File
@@ -0,0 +1,170 @@
#!/bin/bash
get_touchdevice()
{
# echo -n "" > $touchlist
for touch_id in $(xinput | grep -i -E 'touch|ILITEK|TSD' | cut -d '=' -f 2 | cut -f 1)
do
input_dev=$(xinput list-props $touch_id | grep "Device Node" | awk -F : '{print $2}' | awk -F \" '{print $2}' | awk '{print $1}')
touch_screen=$(udevadm info $input_dev | grep "ID_INPUT_TOUCHSCREEN")
# dev=$(udevadm info $input_dev | grep "ID_SERIAL=" | cut -d "=" -f 2)
dev=$(xinput list-props $touch_id | grep "Device '" | cut -d "'" -f 2)
vid_t=$(udevadm info $input_dev | grep "ID_VENDOR_ID=" | cut -d "=" -f 2)
pid_t=$(udevadm info $input_dev | grep "ID_MODEL_ID=" | cut -d "=" -f 2)
path_t=$(udevadm info $input_dev | grep "ID_PATH=" | cut -d "=" -f 2)
# echo "$vid_t,$pid_t,$path_t,$dev,$touch_id"
echo "$dev"
echo " 输入设备id:$touch_id, 硬件id:$vid_t:$pid_t, 设备路径:$path_t"
done
}
get_screeninfo()
{
xrandr | grep connected | grep -v disconnected | while read -r screen ; do
# 提取显示器名称
name=$(echo $screen | awk '{print $1}')
# 检查是否是主屏幕
if echo $screen | grep -q "primary"; then
primary="true"
# 主屏幕的信息在第4个字段
resolution=$(echo $screen | awk '{print $4}')
else
primary="false"
# 非主屏幕的信息在第3个字段
resolution=$(echo $screen | awk '{print $3}')
fi
# 分离分辨率和坐标
screen_resolution=$(echo $resolution | cut -d'+' -f1)
screen_x=$(echo $resolution | cut -d'+' -f2)
screen_y=$(echo $resolution | cut -d'+' -f3)
# 输出结果
echo "显示器名称: $name"
echo "分辨率: $screen_resolution"
echo "X坐标: $screen_x"
echo "Y坐标: $screen_y"
echo "主屏幕: $primary"
echo "---"
done
}
show_help(){
echo ktouch debug。输入字母可以执行对应的功能
echo " h help,帮助"
echo " q 退出debug程序"
echo " dev [id] 查看触摸设备详细信息,比如 t 10 查看id=10的触摸设备详细信息"
echo " map [屏幕] [id] 手动将对应id的设备校准到指定屏幕上,可能与系统设置冲突,需要重启才能恢复正常。"
echo " log 收集本工具的日志,生成到【主目录/家目录/个人文件夹】中"
echo " setting 开始进行一次触摸屏校准设置,并在终端打印出详细日志,但是不会保存。"
echo " automap 执行一次自动校准,并在终端打印出详细日志"
echo " service stop/start 停止或启动监控服务,包括其自启动也受影响"
echo " fix 修复程序,已经配置的信息会被删除"
echo " "
}
map_to_output(){
xinput map-to-output $2 $1
}
show_dev_info(){
xinput list-props $1
}
service_control(){
if [ "$1" = "stop" ];then
sudo systemctl stop ktouch_daemon.service
sudo systemctl disable ktouch_daemon.service
elif [ "$1" = "start" ];then
sudo systemctl start ktouch_daemon.service
sudo systemctl enable ktouch_daemon.service
else
echo 参数不支持!
fi
}
echo " ktouch 调试信息 "
echo ============================
echo "系统触摸屏、签字笔及其输入节点包括:"
get_touchdevice
echo
echo
echo 系统显示器有:
get_screeninfo
echo
echo
echo 当前的触摸屏映射配置为:
cat /opt/ktouch/config
echo
echo
show_help
while [[ 1 ]]
do
echo -n "请输入命令: "
read CMD
if [ "$CMD" = "h" ];then
show_help
elif [ "$CMD" = "q" ];then
echo -n 0 > /tmp/ktouch/setflag
exit 0
elif [ "$(echo $CMD | cut -d' ' -f1)" = "dev" ];then
show_dev_info $(echo $CMD | cut -d' ' -f2)
elif [ "$(echo $CMD | cut -d' ' -f1)" = "map" ];then
#进行一次映射,则需要停掉触摸监控,在程序结束后再打开
echo -n 1 > /tmp/ktouch/setflag
map_to_output $(echo $CMD | cut -d' ' -f2) $(echo $CMD | cut -d' ' -f3)
elif [ "$CMD" = "log" ];then
kscreen-log
elif [ "$CMD" = "setting" ];then
/opt/ktouch/screen_binder
elif [ "$CMD" = "automap" ];then
kscreen-remap -s
elif [ "$(echo $CMD | cut -d' ' -f1)" = "service" ];then
echo "请注意输入sudo密码"
service_control $(echo $CMD | cut -d' ' -f2)
echo 完成,执行结果为 $?
elif [ "$CMD" = "fix" ];then
# 修复程序,流程为关闭程序监控,删除tmp,删除config,reconfigure一下,开启自启动
echo "请注意输入sudo密码"
service_control stop
rm -rf /tmp/ktouch
rm -f /opt/ktouch/config
sudo dpkg-reconfigure ktouch >/dev/null 2>&1
service_control start
echo 完成,执行结果为 $?
else
echo "命令有误,请输入 h 查看支持的命令。"
fi
echo
done
+308
View File
@@ -0,0 +1,308 @@
#!/bin/bash
# 脚本名称
# SCRIPT_NAME="ktouch-fix-daemon"
SCRIPT_NAME=$0
service_name="ktouch_daemon.service"
SERVICE_FILE="/etc/systemd/system/ktouch_daemon.service"
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 打印函数
print_info() {
echo -e "${GREEN}[INFO ]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN ]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_question() {
echo -e "${BLUE}[ ? ]${NC} $1"
}
# 检查并获取root权限
check_root() {
if [[ $EUID -ne 0 ]]; then
print_info "需要root权限来修改系统服务"
if sudo -n true 2>/dev/null; then
print_info "权限已自动获取,重启脚本"
# 已经有sudo权限,直接重新运行脚本
exec sudo "$0" "$@"
else
# 需要输入密码
print_info "请输入sudo密码"
exec sudo "$0" "$@"
fi
fi
}
# 检查用户是否存在
check_user_exists() {
local username="$1"
if id "$username" &>/dev/null; then
return 0
else
return 1
fi
}
# 获取系统所有用户名列表
get_all_users() {
# 获取所有普通用户和系统用户(UID >= 1000 或 UID < 1000但具有登录shell
# getent passwd | cut -d: -f1 | sort
awk -F: '$3 >= 1000 && $7 ~ /\/(bash|sh|zsh|csh|ksh)$/ {print $1}' /etc/passwd
}
# 停止服务
stop_service() {
local service_name="ktouch_daemon.service"
print_info "停止 $service_name 服务..."
if systemctl is-active --quiet "$service_name"; then
systemctl stop "$service_name"
if [[ $? -eq 0 ]]; then
print_info "服务已停止"
else
print_error "停止服务失败"
return 1
fi
else
print_info "服务未运行"
fi
return 0
}
# 禁用服务自启动
disable_service() {
local service_name="ktouch_daemon.service"
print_info "禁用服务自启动..."
if systemctl is-enabled --quiet "$service_name" 2>/dev/null; then
systemctl disable "$service_name"
if [[ $? -eq 0 ]]; then
print_info "服务自启动已禁用"
else
print_error "禁用服务自启动失败"
return 1
fi
else
print_info "服务自启动未启用"
fi
return 0
}
# 修改服务文件中的用户名
modify_service_user() {
local service_file="$1"
local new_user="$2"
local backup_file="${service_file}.backup.$(date +%Y%m%d_%H%M%S)"
print_info "备份服务文件: $backup_file"
cp "$service_file" "$backup_file"
print_info "修改服务文件中的用户名..."
# 检查服务文件是否包含User=行
if grep -q "^User=" "$service_file"; then
# 替换现有的User=行
sed -i "s/^User=.*/User=$new_user/" "$service_file"
else
# 在[Service]部分添加User=行
if grep -q "\[Service\]" "$service_file"; then
sed -i "/\[Service\]/a User=$new_user" "$service_file"
else
# 如果没有[Service]部分,在文件末尾添加
echo -e "\n[Service]\nUser=$new_user" >> "$service_file"
fi
fi
if [[ $? -eq 0 ]]; then
print_info "服务文件修改成功"
return 0
else
print_error "服务文件修改失败"
# 恢复备份
cp "$backup_file" "$service_file"
return 1
fi
}
# 重新加载systemd配置
reload_systemd() {
print_info "重新加载systemd配置..."
systemctl daemon-reload
if [[ $? -eq 0 ]]; then
print_info "systemd配置已重新加载"
return 0
else
print_error "重新加载systemd配置失败"
return 1
fi
}
# 启用服务自启动
enable_service() {
local service_name="ktouch_daemon.service"
print_info "启用服务自启动..."
systemctl enable "$service_name"
if [[ $? -eq 0 ]]; then
print_info "服务自启动已启用"
return 0
else
print_error "启用服务自启动失败"
return 1
fi
}
# 启动服务
start_service() {
local service_name="ktouch_daemon.service"
print_info "启动服务..."
systemctl start "$service_name"
if [[ $? -eq 0 ]]; then
print_info "服务已启动"
return 0
else
print_error "启动服务失败"
print_info "可以使用 'systemctl status $service_name' 查看详细错误信息"
return 1
fi
}
# 显示服务状态
show_service_status() {
local service_name="ktouch_daemon.service"
print_info "当前服务状态:"
systemctl status "$service_name" --no-pager -l
}
# 主函数
main() {
print_info "=== ktouch_daemon 服务用户修改工具 ==="
if [ ! -f $SERVICE_FILE ];then
print_error "服务文件不存在,请重新安装ktouch软件!"
exit 1
fi
# 检查root权限
check_root "$@"
# 显示当前服务配置
print_info "当前服务配置:"
grep -E "User=" "$SERVICE_FILE" 2>/dev/null || print_warning "无法获取当前配置"
# 获取用户名
while true; do
echo
print_question "正在修改 ktouch_daemon 监控服务的执行用户,请在下面输入需要执行的用户名"
echo -n "用户名: "
read -r username
# 检查输入是否为空
if [[ -z "$username" ]]; then
print_error "用户名不能为空,请重新输入"
continue
fi
# 检查用户是否存在
if check_user_exists "$username"; then
# print_info "用户 '$username' 存在"
break
else
print_error "用户 '$username' 不存在,请重新输入"
print_info "可用的用户列表:"
get_all_users | head -10 | tr '\n' ' '
echo -e "\n(显示前10个用户,完整列表请查看 /etc/passwd)"
fi
done
# 确认操作
echo
print_info "即将执行以下操作:"
print_info "1. 停止 ktouch_daemon.service 服务"
print_info "2. 禁用服务自启动"
print_info "3. 修改服务文件中的用户为: $username"
print_info "4. 重新启用服务自启动"
print_info "5. 启动服务"
echo
echo -n " 是否继续? (y/N): "
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
print_info "操作已取消"
exit 0
fi
# 执行操作
echo
print_info "开始修改服务配置..."
# 停止服务
if ! stop_service; then
exit 1
fi
# 禁用服务自启动
if ! disable_service; then
exit 1
fi
# 修改服务文件
if ! modify_service_user "$SERVICE_FILE" "$username"; then
print_error "修改失败!"
exit 1
fi
# 重新加载systemd配置
if ! reload_systemd; then
exit 1
fi
# 启用服务自启动
if ! enable_service; then
exit 1
fi
# 启动服务
if ! start_service; then
exit 1
fi
# 显示最终状态
echo
print_info "=== 操作完成 ==="
show_service_status
echo
print_info "服务用户已成功修改为: $username"
}
# 错误处理
set -euo pipefail
# 执行主函数
main "$@"
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# 搜集日志和配置文件,用于debug分析
log_path="/opt/ktouch"
now=$(date "+%m%d-%H%M%S")
tdir="/tmp/klog_$now"
mkdir $tdir && cd $tdir
echo "正在收集日志文件,请注意需要输入sudo密码。"
# cp /opt/ktouch/*log* $tdir
# cp /opt/ktouch/config $tdir
# cp /opt/ktouch/*.txt $tdir
# 采集系统信息
export DISPLAY=:0
xinput > $tdir/xinput_info.txt
for id in $(xinput list --id-only); do
xinput --list-props "$id" >> $tdir/xinput_props.txt
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
# 采集系统的日志
sudo cp /var/log/messages* $tdir >/dev/null 2>&1
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 /etc/systemd/system/ktouch_daemon.service $tdir/ktouch_daemon.service
sudo chmod 777 -R $tdir
cp -r /opt/ktouch $tdir/ktouch
ls -la ./ >> $tdir/fileinfo.txt
ls -la ./ktouch >> $tdir/fileinfo.txt
# 打包
echo "正在打包文件,将会生成在主目录/家目录/个人文件夹"
cd /tmp
tar -czf ~/ktouch_log-$now.tar.gz ./klog_$now
rm -rf /tmp/klog
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
export DISPLAY=:0
LOG_FILE=/opt/ktouch/touchscreen.log
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') kscreen-remap : $1" | tee -a "$LOG_FILE"
}
log_message "用户($USER)手动执行映射"
# 清空UOS的触摸屏设置信息
if [ -f /usr/bin/gsettings ];then
gsettings set com.deepin.dde.display map-output '' > /dev/null 2>&1
if [ "$?" == "0" ];then
echo "成功清除UOS系统设置中的触摸屏设置信息"
fi
fi
# 更新一次显示器信息
if [ ! -d /tmp/ktouch ];then
mkdir -p /tmp/ktouch
fi
touch /tmp/ktouch/screen.txt
/opt/ktouch/screen_ds_once /tmp/ktouch/screen.txt
if [ ! -f "/opt/ktouch/config" ];then
# 配置文件不存在
log_message "配置文件不存在,不执行"
notify-send -u critical --icon=dialog-warning -t 9000 "触摸屏映射工具 ktouch" "配置文件不存在,请先进行配置!"
exit 1
fi
if [ "$1" == "" ];then
/opt/ktouch/touch_set
notify-send -u low --icon=view-fullscreen -t 3000 "触摸屏映射工具 ktouch" "映射操作完成,请点击屏幕检查。"
exit 0
fi
if [ "$1" == "-s" ];then
# 单次映射,用于开机自启动脚本保障使用。
log_message "$1 参数 = 单次执行"
/opt/ktouch/touch_set
exit 0
fi
echo "请不要携带任何参数执行。本次未作任何操作。"
log_message "映射命令有参数传入($1), 忽略"
exit 127

Some files were not shown because too many files have changed in this diff Show More