重构项目源代码存储结构

This commit is contained in:
tang1219
2026-06-16 14:59:58 +08:00
parent 2dcac48623
commit 29b4d47355
21 changed files with 413 additions and 406 deletions
-53
View File
@@ -1,53 +0,0 @@
# 定义编译器
CC = gcc
# 定义pkg-config参数
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
# 默认目标
all: $(TARGETS)
# 编译screen_binder
screen_binder: screen_binder.c touch_listen.c
$(CC) -o $@ $^ $(GTK_FLAGS) -lpthread -lX11 -lXrandr
install -m 755 $@ ../
# 编译screen_ds
screen_ds: screen_ds.c
$(CC) -o $@ $^ -lX11 -lXrandr
install -m 755 $@ ../
# 编译screen_ds_once
screen_ds_once: screen_ds_once.c
$(CC) -o $@ $^ -lX11 -lXrandr
install -m 755 $@ ../
# 编译touch_set
touch_set: touch_set.c
$(CC) -o $@ $^ -lX11 -lXi -ludev
install -m 755 $@ ../
# 编译touch_ds
touch_ds: touch_ds.c
$(CC) -o $@ $^ -lX11 -lXi -ludev
install -m 755 $@ ../
# 编译usb_ds
usb_ds: usb_ds.c
$(CC) -o $@ $^ -ludev
install -m 755 $@ ../
# 编译check_save
check_save: check_save.c
$(CC) -o $@ $^ $(GTK_FLAGS)
install -m 755 $@ ../
# 清理生成的文件
clean:
rm -f $(TARGETS)
# 声明伪目标
.PHONY: all clean
-217
View File
@@ -1,217 +0,0 @@
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
typedef struct {
GtkWidget *window;
int monitor_num;
int x;
int y;
int width;
int height;
char *name;
} MonitorInfo;
MonitorInfo *monitors = NULL;
int monitor_count = 0;
GtkApplication *app;
int exit_code = 1; // 默认退出代码为1(取消)
// 显示错误对话框
void show_error_dialog(const char *message) {
GtkWidget *dialog = gtk_message_dialog_new(NULL,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"%s", message);
gtk_window_set_title(GTK_WINDOW(dialog), "错误");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
// 关闭所有窗口
void close_all_windows() {
for (int i = 0; i < monitor_count; i++) {
if (monitors[i].window) {
gtk_widget_destroy(monitors[i].window);
}
}
}
// 保存按钮点击回调函数
void on_save_clicked(GtkWidget *widget, gpointer data) {
printf("0\n"); // 打印0
exit_code = 0; // 设置退出代码为0
close_all_windows();
g_application_quit(G_APPLICATION(app));
}
// 取消按钮点击回调函数
void on_cancel_clicked(GtkWidget *widget, gpointer data) {
printf("1\n"); // 打印1
exit_code = 1; // 设置退出代码为1
close_all_windows();
g_application_quit(G_APPLICATION(app));
}
// 窗口关闭事件回调函数
gboolean on_window_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) {
printf("1\n"); // 打印1
exit_code = 1; // 设置退出代码为1
close_all_windows();
g_application_quit(G_APPLICATION(app));
return TRUE; // 阻止默认关闭行为,因为我们自己处理
}
// 创建显示器确认窗口
void create_monitor_window(MonitorInfo *info) {
// 窗口尺寸
int window_width = 400;
int window_height = 200;
// 计算居中位置
int center_x = info->x + (info->width - window_width) / 2;
int center_y = info->y + (info->height - window_height) / 2;
// 创建窗口标题,包含显示器名称
char window_title[256];
if (info->name && strlen(info->name) > 0) {
snprintf(window_title, sizeof(window_title), "ktouch保存确认-%s", info->name);
} else {
snprintf(window_title, sizeof(window_title), "ktouch保存确认-显示器%d", info->monitor_num + 1);
}
// 创建窗口
info->window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(info->window), window_title);
gtk_window_set_default_size(GTK_WINDOW(info->window), window_width, window_height);
gtk_window_move(GTK_WINDOW(info->window), center_x, center_y);
gtk_window_set_keep_above(GTK_WINDOW(info->window), TRUE);
gtk_window_set_decorated(GTK_WINDOW(info->window), TRUE);
gtk_window_set_position(GTK_WINDOW(info->window), GTK_WIN_POS_CENTER);
// 连接窗口关闭事件
g_signal_connect(info->window, "delete-event", G_CALLBACK(on_window_delete_event), NULL);
// 创建容器
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
gtk_container_set_border_width(GTK_CONTAINER(box), 20);
gtk_container_add(GTK_CONTAINER(info->window), box);
// 提示文字
GtkWidget *label = gtk_label_new("是否保存刚刚的配置的信息?\n任意窗口都可以操作。");
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
// 按钮容器(水平排列)
GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous(GTK_BOX(button_box), TRUE);
gtk_box_pack_start(GTK_BOX(box), button_box, FALSE, FALSE, 0);
// 保存按钮
GtkWidget *save_button = gtk_button_new_with_label("保存");
g_signal_connect(save_button, "clicked", G_CALLBACK(on_save_clicked), NULL);
gtk_box_pack_start(GTK_BOX(button_box), save_button, TRUE, TRUE, 0);
// 取消按钮
GtkWidget *cancel_button = gtk_button_new_with_label("取消并退出");
g_signal_connect(cancel_button, "clicked", G_CALLBACK(on_cancel_clicked), NULL);
gtk_box_pack_start(GTK_BOX(button_box), cancel_button, TRUE, TRUE, 0);
// 显示窗口
gtk_widget_show_all(info->window);
}
// 应用启动回调函数
static void activate(GApplication *application, gpointer user_data) {
GdkScreen *screen = gdk_screen_get_default();
if (!screen) {
fprintf(stderr, "无法获取屏幕信息\n");
show_error_dialog("无法获取屏幕信息");
exit(1);
}
// 获取显示器数量 - 使用兼容旧版本的方法
monitor_count = gdk_screen_get_n_monitors(screen);
if (monitor_count <= 0) {
fprintf(stderr, "未找到显示器\n");
show_error_dialog("未找到显示器");
exit(1);
}
printf("找到 %d 个显示器\n", monitor_count);
// 分配内存存储显示器信息
monitors = malloc(monitor_count * sizeof(MonitorInfo));
memset(monitors, 0, monitor_count * sizeof(MonitorInfo));
// 收集所有显示器信息
for (int i = 0; i < monitor_count; i++) {
GdkRectangle geometry;
gdk_screen_get_monitor_geometry(screen, i, &geometry);
monitors[i].monitor_num = i;
monitors[i].x = geometry.x;
monitors[i].y = geometry.y;
monitors[i].width = geometry.width;
monitors[i].height = geometry.height;
// 尝试获取显示器名称/接口
const char *monitor_name = gdk_screen_get_monitor_plug_name(screen, i);
if (monitor_name && strlen(monitor_name) > 0) {
monitors[i].name = strdup(monitor_name);
} else {
// 如果无法获取显示器名称,使用默认名称
char default_name[50];
snprintf(default_name, sizeof(default_name), "显示器%d", i + 1);
monitors[i].name = strdup(default_name);
}
printf("显示器 %d: 位置(%d, %d), 分辨率 %dx%d, 名称: %s\n",
i+1, geometry.x, geometry.y, geometry.width, geometry.height,
monitors[i].name);
}
// 为每个显示器创建窗口
for (int i = 0; i < monitor_count; i++) {
create_monitor_window(&monitors[i]);
}
}
int main(int argc, char *argv[]) {
// 检查DISPLAY环境变量,确保图形环境可用
if (getenv("DISPLAY") == NULL) {
fprintf(stderr, "DISPLAY环境变量未设置,尝试设置默认值\n");
setenv("DISPLAY", ":0", 1);
}
// 创建GTK应用
app = gtk_application_new("com.example.touchscreen_confirm", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
int status = g_application_run(G_APPLICATION(app), 0, NULL);
g_object_unref(app);
// 释放内存
if (monitors) {
for (int i = 0; i < monitor_count; i++) {
if (monitors[i].name) {
free(monitors[i].name);
}
}
free(monitors);
}
// 返回相应的退出代码
return exit_code;
}
-395
View File
@@ -1,395 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <stdarg.h>
// 添加Xrandr头文件和X11显示类型头文件
#ifdef GDK_WINDOWING_X11
#include <X11/extensions/Xrandr.h>
#include <gdk/x11/gdkx11display.h>
#endif
int win_time_delay = 50;
// 全局变量
GtkWidget **info_windows = NULL;
int total_screens = 0;
int *screen_widths = NULL;
int *screen_heights = NULL;
int *screen_x_offsets = NULL;
int *screen_y_offsets = NULL;
char **screen_names = NULL;
FILE *log_file = NULL;
int current_test_round = 0;
int total_test_rounds = 0;
int space_pressed = 0;
guint create_timeout_id = 0;
int current_window_index = 0;
// 函数声明
void get_screen_info();
void log_message(const char *format, ...);
GtkWidget* create_info_window(int screen_index);
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
gboolean create_next_window(gpointer user_data);
void close_all_windows();
void verify_windows();
void start_next_test_round();
void on_info_window_destroy(GtkWidget *widget, gpointer user_data);
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%s] screen_demo \t", time_str);
vprintf(format, args);
if (log_file) {
fprintf(log_file, "[%s] screen_demo \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file);
}
va_end(args);
}
// 获取屏幕信息 (使用Xrandr方法)
void get_screen_info() {
GdkDisplay *gdk_display = gdk_display_get_default();
#ifdef GDK_WINDOWING_X11
if (!GDK_IS_X11_DISPLAY(gdk_display)) {
log_message("错误: 非X11显示环境\n");
return;
}
Display *xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display);
Window xroot = GDK_WINDOW_XID(gdk_get_default_root_window());
int rr_event_base, rr_error_base;
if (!XRRQueryExtension(xdisplay, &rr_event_base, &rr_error_base)) {
log_message("错误: XRandR扩展不可用\n");
return;
}
XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(xdisplay, xroot);
if (!screen_res) {
log_message("错误: 无法获取屏幕资源\n");
return;
}
// 计算连接的显示器数量
total_screens = 0;
for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
total_screens++;
}
if (output_info) XRRFreeOutputInfo(output_info);
}
log_message("找到 %d 个连接的显示器\n", total_screens);
// 分配内存存储屏幕信息
screen_widths = malloc(total_screens * sizeof(int));
screen_heights = malloc(total_screens * sizeof(int));
screen_x_offsets = malloc(total_screens * sizeof(int));
screen_y_offsets = malloc(total_screens * sizeof(int));
screen_names = malloc(total_screens * sizeof(char*));
info_windows = malloc(total_screens * sizeof(GtkWidget*));
// 获取每个显示器的详细信息
int screen_index = 0;
for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
if (output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(xdisplay, screen_res, output_info->crtc);
if (crtc_info) {
screen_names[screen_index] = strdup(output_info->name);
screen_widths[screen_index] = crtc_info->width;
screen_heights[screen_index] = crtc_info->height;
screen_x_offsets[screen_index] = crtc_info->x;
screen_y_offsets[screen_index] = crtc_info->y;
info_windows[screen_index] = NULL;
log_message("显示器 %d: %s, 分辨率: %dx%d, 位置: %dx%d\n",
screen_index, screen_names[screen_index],
screen_widths[screen_index], screen_heights[screen_index],
screen_x_offsets[screen_index], screen_y_offsets[screen_index]);
XRRFreeCrtcInfo(crtc_info);
screen_index++;
}
}
}
if (output_info) XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(screen_res);
#else
log_message("错误: 非X11环境,无法使用Xrandr\n");
#endif
}
// 创建信息窗口
GtkWidget* create_info_window(int screen_index) {
// 计算窗口位置和大小:偏移(10,10),尺寸比屏幕小20
int window_x = screen_x_offsets[screen_index] + 10;
int window_y = screen_y_offsets[screen_index] + 10;
int window_width = screen_widths[screen_index] - 20;
int window_height = screen_heights[screen_index] - 20;
// 确保窗口尺寸不会为负值
if (window_width < 100) window_width = 100;
if (window_height < 100) window_height = 100;
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "屏幕信息演示");
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_decorated(GTK_WINDOW(window), TRUE);
gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
char message[512];
snprintf(message, sizeof(message),
"<span font='24' weight='bold'>屏幕 %d: %s</span>\n\n"
"<span font='18'>分辨率: %dx%d</span>\n"
"<span font='18'>位置: (%d, %d)</span>\n\n"
"<span font='18'>窗口位置: (%d, %d)</span>\n"
"<span font='18'>窗口大小: %dx%d</span>\n\n"
"<span font='18'>测试轮次: %d/%d</span>\n\n"
"<span font='18' foreground='blue'>按空格键继续...</span>",
screen_index, screen_names[screen_index],
screen_widths[screen_index], screen_heights[screen_index],
screen_x_offsets[screen_index], screen_y_offsets[screen_index],
window_x, window_y, window_width, window_height,
current_test_round + 1, total_test_rounds);
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), message);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
// 添加容器使标签居中
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box);
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));
return window;
}
// 键盘事件处理
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
if (event->keyval == GDK_KEY_space) {
log_message("用户按下空格键\n");
space_pressed = 1;
// 关闭所有窗口并开始下一轮测试
close_all_windows();
return TRUE;
}
return FALSE;
}
// 信息窗口销毁事件处理
void on_info_window_destroy(GtkWidget *widget, gpointer user_data) {
int screen_index = GPOINTER_TO_INT(user_data);
log_message("屏幕 %d (%s) 的信息窗口已销毁\n", screen_index, screen_names[screen_index]);
info_windows[screen_index] = NULL;
}
// 创建下一个窗口(间隔3秒)
gboolean create_next_window(gpointer user_data) {
if (current_window_index >= total_screens) {
log_message("所有窗口已创建完成\n");
verify_windows();
create_timeout_id = 0;
return G_SOURCE_REMOVE;
}
log_message("正在为屏幕 %d (%s) 创建信息窗口...\n",
current_window_index, screen_names[current_window_index]);
info_windows[current_window_index] = create_info_window(current_window_index);
gtk_widget_show_all(info_windows[current_window_index]);
log_message("屏幕 %d 的信息窗口已显示\n", current_window_index);
current_window_index++;
if (current_window_index < total_screens) {
// 设置3秒后创建下一个窗口
// 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);
} else {
log_message("所有窗口已创建完成\n");
verify_windows();
}
return G_SOURCE_REMOVE;
}
// 关闭所有窗口
void close_all_windows() {
log_message("正在关闭所有窗口...\n");
for (int i = 0; i < total_screens; i++) {
if (info_windows[i] != NULL) {
gtk_widget_destroy(info_windows[i]);
info_windows[i] = NULL;
}
}
// 取消超时计时器
if (create_timeout_id > 0) {
g_source_remove(create_timeout_id);
create_timeout_id = 0;
}
log_message("所有窗口已关闭\n");
// 开始下一轮测试
start_next_test_round();
}
// 校验窗口的显示状态、位置和大小
void verify_windows() {
log_message("开始校验窗口状态...\n");
int all_windows_ok = 1;
for (int i = 0; i < total_screens; i++) {
if (info_windows[i] == NULL) {
log_message("错误: 屏幕 %d 的窗口未创建\n", i);
all_windows_ok = 0;
continue;
}
if (!gtk_widget_get_visible(info_windows[i])) {
log_message("错误: 屏幕 %d 的窗口不可见\n", i);
all_windows_ok = 0;
}
// 获取窗口实际位置和大小
int x, y, width, height;
gtk_window_get_position(GTK_WINDOW(info_windows[i]), &x, &y);
gtk_window_get_size(GTK_WINDOW(info_windows[i]), &width, &height);
// 计算期望的位置和大小
int expected_x = screen_x_offsets[i] + 10;
int expected_y = screen_y_offsets[i] + 10;
int expected_width = screen_widths[i] - 20;
int expected_height = screen_heights[i] - 20;
// 确保期望尺寸不会为负值
if (expected_width < 100) expected_width = 100;
if (expected_height < 100) expected_height = 100;
// 校验位置(允许±5像素的误差)
if (abs(x - expected_x) > 5 || abs(y - expected_y) > 5) {
log_message("警告: 屏幕 %d 的窗口位置不匹配。预期: (%d, %d), 实际: (%d, %d)\n",
i, expected_x, expected_y, x, y);
}
// 校验大小(允许±5像素的误差)
if (abs(width - expected_width) > 5 || abs(height - expected_height) > 5) {
log_message("警告: 屏幕 %d 的窗口大小不匹配。预期: %dx%d, 实际: %dx%d\n",
i, expected_width, expected_height, width, height);
}
log_message("屏幕 %d 校验完成: 位置=(%d, %d), 大小=%dx%d\n", i, x, y, width, height);
}
if (all_windows_ok) {
log_message("所有窗口校验成功\n");
} else {
log_message("部分窗口校验失败\n");
}
}
// 开始下一轮测试
void start_next_test_round() {
current_test_round++;
if (current_test_round >= total_test_rounds) {
log_message("所有测试轮次已完成,程序结束\n");
// 清理资源
for (int i = 0; i < total_screens; i++) {
free(screen_names[i]);
}
free(screen_widths);
free(screen_heights);
free(screen_x_offsets);
free(screen_y_offsets);
free(screen_names);
free(info_windows);
// 关闭日志文件
if (log_file) {
fclose(log_file);
log_file = NULL;
}
gtk_main_quit();
return;
}
log_message("开始第 %d/%d 轮测试\n", current_test_round + 1, total_test_rounds);
// 重置窗口索引
current_window_index = 0;
space_pressed = 0;
// 开始创建窗口(第一个窗口立即创建,后续窗口间隔3秒)
create_timeout_id = g_timeout_add(100, create_next_window, NULL);
}
int main(int argc, char *argv[]) {
log_file = fopen("screen_demo.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("=== 多屏幕信息窗口演示程序 ===\n");
gtk_init(&argc, &argv);
// 获取屏幕信息
get_screen_info();
if (total_screens == 0) {
log_message("未找到任何屏幕,程序退出\n");
return 1;
}
// 设置测试轮次为屏幕数量
total_test_rounds = total_screens;
log_message("将执行 %d 轮测试(每轮在所有屏幕上创建窗口)\n", total_test_rounds);
// 开始第一轮测试
current_test_round = -1;
start_next_test_round();
gtk_main();
log_message("程序结束\n");
return 0;
}
-677
View File
@@ -1,677 +0,0 @@
#!/usr/bin/env python3
import dbus
import logging
import time
import os
from typing import List, Dict, Any
import sys
setting_file_dir="/opt/ktouch/display_config"
class DisplayControl:
def __init__(self):
self.logger = logging.getLogger('DisplayControl')
self.logger.setLevel(logging.INFO)
# 创建控制台处理器
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
self.logger.addHandler(ch)
self.bus = None
self.display_obj = None
self.display_props = None
self.display_interface = None
self.connect_to_dbus()
def connect_to_dbus(self):
"""连接到DBus服务"""
try:
self.bus = dbus.SessionBus()
self.display_obj = self.bus.get_object('com.deepin.daemon.Display', '/com/deepin/daemon/Display')
self.display_props = dbus.Interface(self.display_obj, 'org.freedesktop.DBus.Properties')
self.display_interface = dbus.Interface(self.display_obj, 'com.deepin.daemon.Display')
self.logger.info("成功连接到DBus显示服务")
except Exception as e:
self.logger.error(f"连接DBus显示服务失败: {str(e)}")
raise
# def get_monitors(self) -> List[Dict[str, Any]]:
# """获取所有显示器信息"""
# self.logger.info("开始获取显示器信息")
# try:
# monitors_paths = self.display_props.Get('com.deepin.daemon.Display', 'Monitors')
# self.logger.info(f"找到 {len(monitors_paths)} 个显示器")
# monitors = []
# for monitor_path in monitors_paths:
# monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
# monitor_props = dbus.Interface(monitor_obj, 'org.freedesktop.DBus.Properties')
# # 获取显示器属性
# name = str(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Name'))
# enabled = str(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Enabled'))
# modes = monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Modes')
# current_mode = monitor_props.Get('com.deepin.daemon.Display.Monitor', 'CurrentMode')
# x = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'X'))
# y = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Y'))
# width = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Width'))
# height = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Height'))
# rotation = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Rotation'))
# # 获取主显示器
# primary = self.display_props.Get('com.deepin.daemon.Display', 'Primary')
# is_primary = (primary == name)
# # 检查是否存在配置文件
# config_exists = self.check_config_exists(name)
# # 解析可用模式,过滤出60fps的模式
# available_modes = []
# for mode in modes:
# mode_id = int(mode[0])
# mode_width = int(mode[1])
# mode_height = int(mode[2])
# refresh_rate = int(mode[3])
# # 过滤55-65Hz的刷新率
# if 55 <= refresh_rate <= 65:
# available_modes.append({
# 'id': mode_id,
# 'width': mode_width,
# 'height': mode_height,
# 'refresh_rate': refresh_rate,
# 'label': f"{mode_width}x{mode_height} @ {refresh_rate:.2f}Hz"
# })
# # 添加自定义分辨率选项
# available_modes.append({
# 'id': 'custom',
# 'width': width, # 默认使用当前宽度
# 'height': height, # 默认使用当前高度
# 'refresh_rate': 60.0, # 默认刷新率
# 'label': "自定义分辨率"
# })
# # 如果有配置文件,设置current_mode为id=0
# if config_exists:
# current_mode = [0, width, height, 60] # id=0表示自定义模式
# self.logger.info(f"显示器 {name} 使用自定义分辨率模式")
# monitors.append({
# 'path': monitor_path,
# 'name': name,
# 'enabled': enabled,
# 'x': x,
# 'y': y,
# 'width': width,
# 'height': height,
# 'rotation': rotation,
# 'is_primary': is_primary,
# 'current_mode': [int(current_mode[0]), int(current_mode[1]), int(current_mode[2]), int(current_mode[3])],
# 'modes': available_modes,
# 'custom_width': width, # 自定义宽度
# 'custom_height': height, # 自定义高度
# 'custom_refresh_rate': 60.0, # 自定义刷新率
# 'has_custom_config': config_exists # 是否有自定义配置
# })
# self.logger.info(f"显示器: {name}, 启用: {enabled}, 位置: ({x}, {y}), 分辨率: {width}x{height}, 旋转: {rotation}, 主屏: {is_primary}")
# # 估算显示器的位置顺序(基于X坐标)
# self.estimate_monitor_positions(monitors)
# self.logger.info("显示器信息获取完成")
# return monitors
# except Exception as e:
# self.logger.error(f"获取显示器信息时出错: {str(e)}")
# raise
def get_monitors(self) -> List[Dict[str, Any]]:
"""获取所有显示器信息"""
self.logger.info("开始获取显示器信息")
try:
monitors_paths = self.display_props.Get('com.deepin.daemon.Display', 'Monitors')
self.logger.info(f"找到 {len(monitors_paths)} 个显示器")
monitors = []
for monitor_path in monitors_paths:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_props = dbus.Interface(monitor_obj, 'org.freedesktop.DBus.Properties')
# 获取显示器基础属性
name = str(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Name'))
enabled = str(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Enabled'))
modes = monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Modes')
x = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'X'))
y = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Y'))
width = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Width'))
height = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Height'))
rotation = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Rotation'))
# 检查是否存在配置文件并加载
config_exists = self.check_config_exists(name)
custom_config = None
if config_exists:
custom_config = self.load_custom_resolution(name)
if custom_config:
# 从配置文件更新分辨率信息
width = custom_config['width']
height = custom_config['height']
self.logger.info(f"显示器 {name} 加载配置文件中的分辨率: {width}x{height}")
else:
self.logger.warning(f"显示器 {name} 配置文件存在但解析失败,将使用系统分辨率")
# 确定当前模式(优先使用配置文件信息)
if config_exists and custom_config:
# 配置文件存在且有效时,使用配置的分辨率,模式ID为0,刷新率60
current_mode = [0, width, height, 60]
self.logger.info(f"显示器 {name} 使用配置文件中的当前模式: {current_mode}")
else:
# 否则使用系统当前模式
current_mode = monitor_props.Get('com.deepin.daemon.Display.Monitor', 'CurrentMode')
current_mode = [int(current_mode[0]), int(current_mode[1]), int(current_mode[2]), int(current_mode[3])]
# 获取主显示器信息
primary = self.display_props.Get('com.deepin.daemon.Display', 'Primary')
is_primary = (primary == name)
# 解析可用模式,过滤出55-65Hz的刷新率
available_modes = []
for mode in modes:
mode_id = int(mode[0])
mode_width = int(mode[1])
mode_height = int(mode[2])
refresh_rate = int(mode[3])
if 55 <= refresh_rate <= 65:
available_modes.append({
'id': mode_id,
'width': mode_width,
'height': mode_height,
'refresh_rate': refresh_rate,
'label': f"{mode_width}x{mode_height} @ {refresh_rate:.2f}Hz"
})
# 添加自定义分辨率选项(使用当前有效分辨率作为默认值)
available_modes.append({
'id': 'custom',
'width': width,
'height': height,
'refresh_rate': 60.0,
'label': "自定义分辨率"
})
monitors.append({
'path': monitor_path,
'name': name,
'enabled': enabled,
'x': x,
'y': y,
'width': width,
'height': height,
'rotation': rotation,
'is_primary': is_primary,
'current_mode': current_mode,
'modes': available_modes,
'custom_width': width,
'custom_height': height,
'custom_refresh_rate': 60.0,
'has_custom_config': config_exists
})
self.logger.info(f"显示器: {name}, 启用: {enabled}, 位置: ({x}, {y}), 分辨率: {width}x{height}, 旋转: {rotation}, 主屏: {is_primary}")
# 估算显示器的位置顺序(基于X坐标)
self.estimate_monitor_positions(monitors)
self.logger.info("显示器信息获取完成")
return monitors
except Exception as e:
self.logger.error(f"获取显示器信息时出错: {str(e)}")
raise
def check_config_exists(self, monitor_name: str) -> bool:
"""检查显示器配置文件是否存在"""
config_dir = os.path.expanduser(setting_file_dir)
config_file = os.path.join(config_dir, f"{monitor_name}.conf")
return os.path.exists(config_file)
def estimate_monitor_positions(self, monitors: List[Dict[str, Any]]):
"""根据X坐标估算显示器的位置顺序"""
# 按X坐标排序
sorted_monitors = sorted(monitors, key=lambda m: m['x'])
seq = 0
last_x = -1
# 分配位置编号
for i, monitor in enumerate(sorted_monitors):
if monitor['x'] == last_x:
monitor['position'] = seq
last_x = int(monitor['x'])
else:
seq += 1
monitor['position'] = seq
last_x = int(monitor['x'])
self.logger.info(f"显示器 {monitor['name']} 估算位置: {monitor['position']} (X坐标: {monitor['x']})")
def enable_monitor(self, monitor_path: str, enabled: bool):
"""启用或禁用显示器"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
if enabled:
self.logger.info(f"启用显示器: {monitor_path}")
monitor_interface.Enable()
else:
self.logger.info(f"禁用显示器: {monitor_path}")
monitor_interface.Disable()
except Exception as e:
self.logger.error(f"设置显示器启用状态时出错: {str(e)}")
raise
def set_mode(self, monitor_path: str, mode_id: int, monitor_name: str = None):
"""通过Mode ID设置显示器模式"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
self.logger.info(f"设置显示器 {monitor_path} 的Mode ID为 {mode_id}")
monitor_interface.SetMode(mode_id)
# 如果是系统模式,删除配置文件
if mode_id != 'custom' and monitor_name:
self.delete_config(monitor_name)
except Exception as e:
self.logger.error(f"设置显示器Mode时出错: {str(e)}")
raise
# 设置显示模式为填充
try:
monitor_props = dbus.Interface(monitor_obj, 'org.freedesktop.DBus.Properties')
monitor_props.Set('com.deepin.daemon.Display.Monitor', 'CurrentFillMode', dbus.String('Full'))
self.logger.info(f"已设置显示器 {monitor_path} 的CurrentFillMode为Full")
except Exception as fill_mode_error:
self.logger.warning(f"设置CurrentFillMode失败: {str(fill_mode_error)}")
def set_mode_by_size(self, monitor_path: str, width: int, height: int, monitor_name: str = None, is_custom: bool = False):
"""通过分辨率设置显示器模式"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
self.logger.info(f"设置显示器 {monitor_path} 的分辨率为 {width}x{height}")
monitor_interface.SetModeBySize(width, height)
# 如果是自定义分辨率,保存配置并创建need_update文件
if is_custom and monitor_name:
self.save_custom_resolution(monitor_name, width, height, 60.0)
self.create_need_update_file()
# 如果是系统模式,删除配置文件
elif monitor_name:
self.delete_config(monitor_name)
except Exception as e:
self.logger.error(f"设置显示器分辨率时出错: {str(e)}")
raise
try:
monitor_props = dbus.Interface(monitor_obj, 'org.freedesktop.DBus.Properties')
monitor_props.Set('com.deepin.daemon.Display.Monitor', 'CurrentFillMode', dbus.String('Full'))
self.logger.info(f"已设置显示器 {monitor_path} 的CurrentFillMode为Full")
except Exception as fill_mode_error:
self.logger.warning(f"设置CurrentFillMode失败: {str(fill_mode_error)}")
def set_position(self, monitor_path: str, x: int, y: int):
"""设置显示器位置"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
self.logger.info(f"设置显示器 {monitor_path} 的位置为 ({x}, {y})")
monitor_interface.SetPosition(x, y)
except Exception as e:
self.logger.error(f"设置显示器位置时出错: {str(e)}")
raise
def set_refresh_rate(self, monitor_path: str, refresh_rate: float, monitor_name: str = None, is_custom: bool = False):
"""设置显示器刷新率"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
self.logger.info(f"设置显示器 {monitor_path} 的刷新率为 {refresh_rate}Hz")
monitor_interface.SetRefreshRate(refresh_rate)
# 如果是自定义刷新率,保存配置并创建need_update文件
if is_custom and monitor_name:
# 需要先获取当前分辨率
monitor_props = dbus.Interface(monitor_obj, 'org.freedesktop.DBus.Properties')
width = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Width'))
height = int(monitor_props.Get('com.deepin.daemon.Display.Monitor', 'Height'))
self.save_custom_resolution(monitor_name, width, height, refresh_rate)
self.create_need_update_file()
# 如果是系统模式,删除配置文件
elif monitor_name:
self.delete_config(monitor_name)
except Exception as e:
self.logger.error(f"设置显示器刷新率时出错: {str(e)}")
raise
def set_rotation(self, monitor_path: str, rotation: int):
"""设置显示器旋转方向"""
try:
monitor_obj = self.bus.get_object('com.deepin.daemon.Display', monitor_path)
monitor_interface = dbus.Interface(monitor_obj, 'com.deepin.daemon.Display.Monitor')
rotation_names = {
1: "正常",
2: "向左90度",
4: "翻转",
8: "向右90度"
}
self.logger.info(f"设置显示器 {monitor_path} 的旋转方向为 {rotation} ({rotation_names.get(rotation, '未知')})")
monitor_interface.SetRotation(rotation)
except Exception as e:
self.logger.error(f"设置显示器旋转方向时出错: {str(e)}")
raise
def switch_mode(self):
"""切换显示模式,为设置主显示器做准备"""
try:
self.logger.info("调用SwitchMode方法准备设置主显示器")
# 调用SwitchMode方法,第一个参数是byte类型的2,第二个参数是空字符串
self.display_interface.SwitchMode(dbus.Byte(2), dbus.String(""))
self.logger.info("SwitchMode方法调用成功")
except Exception as e:
self.logger.error(f"调用SwitchMode方法时出错: {str(e)}")
raise
def set_primary(self, monitor_name: str):
"""设置主显示器"""
try:
self.logger.info(f"设置主显示器: {monitor_name}")
self.display_interface.SetPrimary(monitor_name)
except Exception as e:
self.logger.error(f"设置主显示器时出错: {str(e)}")
raise
def apply_changes(self):
"""应用更改"""
try:
self.logger.info("应用显示设置更改")
self.display_interface.ApplyChanges()
except Exception as e:
self.logger.error(f"应用显示设置时出错: {str(e)}")
raise
def save_settings(self):
"""保存设置"""
try:
self.logger.info("保存显示设置")
self.display_interface.Save()
except Exception as e:
self.logger.error(f"保存显示设置时出错: {str(e)}")
raise
def save_custom_resolution(self, monitor_name: str, config: str):
"""保存自定义配置到文件,配置格式为“{width}x{height}@{rate}|x,y|rotation|isPrimary”"""
try:
config_dir = os.path.expanduser(setting_file_dir)
os.makedirs(config_dir, exist_ok=True)
config_file = os.path.join(config_dir, f"{monitor_name}.conf")
with open(config_file, 'w') as f:
f.write(config)
self.logger.info(f"已保存自定义配置到 {config_file}: {config}")
except Exception as e:
self.logger.error(f"保存自定义配置时出错: {str(e)}")
def remove_custom_resolution(self, monitor_name):
config_dir = os.path.expanduser(setting_file_dir)
os.makedirs(config_dir, exist_ok=True)
config_file = os.path.join(config_dir, f"{monitor_name}.conf")
if os.path.exists(config_file):
try:
# 删除配置文件
os.remove(config_file)
# logging.info(f"已删除显示器 {monitor_name} 的自定义分辨率配置文件")
return True
except Exception as e:
# logging.error(f"删除显示器 {monitor_name} 的自定义分辨率配置文件时出错: {str(e)}")
return False
return True
def create_need_update_file(self):
"""创建need_update文件,如果打开失败则再尝试一次"""
try:
config_dir = os.path.expanduser(setting_file_dir)
os.makedirs(config_dir, exist_ok=True)
need_update_file = os.path.join(config_dir, "need_update")
try:
with open(need_update_file, 'w') as f:
f.write('1')
except:
# 如果失败,再尝试一次
try:
with open(need_update_file, 'w') as f:
f.write('1')
except Exception as e:
self.logger.error(f"写入need_update文件失败: {str(e)}")
except Exception as e:
self.logger.error(f"创建need_update文件时出错: {str(e)}")
def delete_config(self, monitor_name: str):
"""删除配置文件"""
try:
config_dir = os.path.expanduser(setting_file_dir)
config_file = os.path.join(config_dir, f"{monitor_name}.conf")
if os.path.exists(config_file):
os.remove(config_file)
self.logger.info(f"已删除配置文件: {config_file}")
except Exception as e:
self.logger.error(f"删除配置文件时出错: {str(e)}")
# def load_custom_resolution(self, monitor_name: str):
# """
# 加载自定义分辨率配置文件
# 配置文件新格式: {width}x{height}@{rate}|x,y|rotation|isPrimary
# 例如: 1920x1080@60|1920,0|0|True
# 配置文件路径: 同目录下的display_settings目录,以显示器名称.conf命名
# Args:
# monitor_name: 显示器名称
# Returns:
# 包含解析后的配置信息的字典,解析失败返回None
# """
# try:
# # 构建配置文件路径:同目录下的display_settings目录,文件名格式为"显示器名称.conf"
# import os
# # 获取当前文件所在目录
# current_dir = os.path.dirname(os.path.abspath(__file__))
# # 构建display_settings目录路径
# settings_dir = setting_file_dir
# # 构建完整配置文件路径
# config_path = os.path.join(settings_dir, f"{monitor_name}.conf")
# # 检查配置文件目录是否存在
# if not os.path.exists(settings_dir):
# self.logger.warning(f"配置文件目录不存在: {settings_dir}")
# return None
# # 检查配置文件是否存在
# if not os.path.exists(config_path):
# self.logger.warning(f"显示器 {monitor_name} 配置文件不存在: {config_path}")
# return None
# with open(config_path, 'r', encoding='utf-8') as f:
# content = f.read().strip()
# # 按分隔符分割配置项
# parts = content.split('|')
# if len(parts) != 4:
# self.logger.error(f"显示器 {monitor_name} 配置文件格式错误,需要4个部分,实际有{len(parts)}个")
# return None
# # 解析分辨率和刷新率部分 (格式: {width}x{height}@{rate})
# resolution_part = parts[0].strip()
# if '@' not in resolution_part or 'x' not in resolution_part:
# self.logger.error(f"显示器 {monitor_name} 分辨率格式错误: {resolution_part}")
# return None
# # 分离分辨率和刷新率
# resolution, rate_str = resolution_part.split('@', 1)
# width_str, height_str = resolution.split('x', 1)
# rate_str = rate_str[0:-2]
# print(rate_str)
# # 解析位置信息 (格式: x,y)
# position_part = parts[1].strip()
# x_str, y_str = position_part.split(',', 1)
# # 解析旋转角度和主显示器标识
# rotation_str = parts[2].strip()
# is_primary_str = parts[3].strip().lower()
# # 转换为相应的数据类型
# return {
# 'width': int(width_str),
# 'height': int(height_str),
# 'refresh_rate': float(rate_str),
# 'x': int(x_str),
# 'y': int(y_str),
# 'rotation': int(rotation_str),
# 'is_primary': is_primary_str in ('true', '1', 'yes')
# }
# except ValueError as e:
# self.logger.error(f"显示器 {monitor_name} 配置文件数值解析失败: {str(e)}")
# except FileNotFoundError:
# self.logger.warning(f"显示器 {monitor_name} 配置文件未找到: {config_path}")
# except Exception as e:
# self.logger.error(f"加载显示器 {monitor_name} 配置文件时出错: {str(e)}")
# return None
def load_custom_resolution(self, monitor_name: str):
"""
加载自定义分辨率配置文件
配置文件新格式: {width}x{height}@{rate}|x,y|rotation|isPrimary|forceFlag
例如: 1920x1080@60|1920,0|0|True|F 或 1920x1080@60|1920,0|0|True|A
配置文件路径: 同目录下的display_settings目录,以显示器名称.conf命名
Args:
monitor_name: 显示器名称
Returns:
包含解析后的配置信息的字典,解析失败返回None
"""
try:
# 构建配置文件路径:同目录下的display_settings目录,文件名格式为"显示器名称.conf"
import os
# 获取当前文件所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 构建display_settings目录路径
settings_dir = setting_file_dir
# 构建完整配置文件路径
config_path = os.path.join(settings_dir, f"{monitor_name}.conf")
# 检查配置文件目录是否存在
if not os.path.exists(settings_dir):
self.logger.warning(f"配置文件目录不存在: {settings_dir}")
return None
# 检查配置文件是否存在
if not os.path.exists(config_path):
self.logger.warning(f"显示器 {monitor_name} 配置文件不存在: {config_path}")
return None
with open(config_path, 'r', encoding='utf-8') as f:
content = f.read().strip()
# 按分隔符分割配置项,现在可能有4或5个部分(兼容旧格式)
parts = content.split('|')
if len(parts) < 4:
self.logger.error(f"显示器 {monitor_name} 配置文件格式错误,需要至少4个部分,实际有{len(parts)}")
return None
# 解析分辨率和刷新率部分 (格式: {width}x{height}@{rate})
resolution_part = parts[0].strip()
if '@' not in resolution_part or 'x' not in resolution_part:
self.logger.error(f"显示器 {monitor_name} 分辨率格式错误: {resolution_part}")
return None
# 分离分辨率和刷新率
resolution, rate_str = resolution_part.split('@', 1)
width_str, height_str = resolution.split('x', 1)
rate_str = rate_str[0:-2] # 移除"Hz"
# 解析位置信息 (格式: x,y)
position_part = parts[1].strip()
x_str, y_str = position_part.split(',', 1)
# 解析旋转角度和主显示器标识
rotation_str = parts[2].strip()
is_primary_str = parts[3].strip().lower()
# 转换为相应的数据类型
result = {
'width': int(width_str),
'height': int(height_str),
'refresh_rate': float(rate_str),
'x': int(x_str),
'y': int(y_str),
'rotation': int(rotation_str),
'is_primary': is_primary_str in ('true', '1', 'yes', 'p')
}
# 如果有第5个部分(强制标志),则解析它
if len(parts) >= 5:
force_flag = parts[4].strip().upper()
result['force_custom'] = (force_flag == 'F')
else:
result['force_custom'] = False # 默认值
return result
except ValueError as e:
self.logger.error(f"显示器 {monitor_name} 配置文件数值解析失败: {str(e)}")
except FileNotFoundError:
self.logger.warning(f"显示器 {monitor_name} 配置文件未找到: {config_path}")
except Exception as e:
self.logger.error(f"加载显示器 {monitor_name} 配置文件时出错: {str(e)}")
return None
-535
View File
@@ -1,535 +0,0 @@
#!/usr/bin/env python3
"""
显示器配置检查与设置脚本
环境变量: DISPLAY=:0
参数: 配置文件路径
配置文件格式: 分辨率@刷新率|坐标|旋转方向|是否是主屏
示例: 1920x1080@60.0Hz|0,0|1|P
功能: 检查当前分辨率是否与配置文件一致,否则进行设置
"""
import os
import sys
import re
import subprocess
import argparse
from pathlib import Path
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
"""
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) != 4:
raise ValueError("配置文件格式错误,应该有4个部分")
# 解析分辨率和刷新率
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'
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
}
return True
except Exception as e:
print(f"解析配置文件错误: {e}")
return False
def run_xrandr_command(self, cmd, ignore_errors=False):
"""运行xrandr命令"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
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']):
print(f"忽略预期中的错误: {result.stderr.strip()}")
return True
else:
print(f"命令执行失败但忽略错误: {cmd}")
print(f"错误信息: {result.stderr}")
return True
else:
print(f"命令执行失败: {cmd}")
print(f"错误信息: {result.stderr}")
return False
return True
except Exception as e:
print(f"执行命令错误: {e}")
return False
def get_current_display_settings(self):
"""获取当前显示器的设置"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
print("获取显示器信息失败")
return None
# 解析xrandr输出,找到指定显示器的当前设置
lines = result.stdout.split('\n')
current_settings = {}
for line in lines:
# 查找目标显示器的连接状态行
if line.startswith(f'{self.display_name} '):
# 示例: "HDMI-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 527mm x 296mm"
# 或者: "HDMI-1 connected 1920x1080+0+0"
# 检查是否是主屏
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:
print(f"获取当前显示器设置错误: {e}")
return None
def get_display_modes(self):
"""
获取显示器支持的模式列表
返回: 字典,键为分辨率,值为该分辨率下支持的刷新率列表
"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
result = subprocess.run(
['xrandr'],
env=env,
capture_output=True,
text=True
)
if result.returncode != 0:
print("获取显示器信息失败")
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:
# 解析分辨率行,例如: "1920x1080" 或 "1280x720i" (隔行扫描)
res_match = re.search(r'^\s*(\d+x\d+i?)\s+', line)
if res_match:
current_resolution = res_match.group(1)
modes[current_resolution] = []
# 解析刷新率,例如: "60.00*+", "59.94", "50.00"
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:
print(f"获取显示器模式错误: {e}")
return {}
def get_existing_modelines(self):
"""获取已经存在的自定义模式"""
env = os.environ.copy()
env['DISPLAY'] = self.display_env
try:
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:
print(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]:
# 允许±2fps的误差
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:
print(f"模式 {mode_name} 已经存在,跳过创建")
else:
# 使用cvt生成模式行
try:
# 运行cvt命令
result = subprocess.run(
['cvt', str(width), str(height), str(refresh_rate)],
capture_output=True,
text=True
)
if result.returncode != 0:
print("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:
print("无法从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
print(f"成功创建新模式: {mode_name}")
except Exception as e:
print(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')
# 处理隔行扫描(如1080i)
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} ({width_diff}, {height_diff} 像素差异), 配置 {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 ({refresh_diff:.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 = []
if width_diff > 0 or height_diff > 0:
match_details.append(f"分辨率: {current_res} (差异: {width_diff}x{height_diff} 像素)")
else:
match_details.append(f"分辨率: {current_res} (精确匹配)")
if refresh_diff > 0:
match_details.append(f"刷新率: {current_refresh:.1f}Hz (差异: {refresh_diff:.1f}Hz)")
else:
match_details.append(f"刷新率: {current_refresh:.1f}Hz (精确匹配)")
match_details.append(f"旋转: {current_rotation}")
match_details.append(f"主屏: {'是' if current_primary else '否'}")
match_details.append("位置: 跳过校验")
return True, " | ".join(match_details)
def configure_display(self):
"""配置显示器"""
if not self.config_data:
print("没有可用的配置数据")
return False
# 获取当前支持的模式
modes = self.get_display_modes()
if not modes:
print(f"无法获取显示器 {self.display_name} 的模式信息")
return False
print(f"显示器 {self.display_name} 支持的模式:")
for res, rates in modes.items():
print(f" {res}: {rates}")
# 查找匹配的模式
mode_name = self.find_matching_mode(modes)
# 如果不支持,创建新模式
if not mode_name:
print("显示器不支持该模式,尝试创建新模式...")
mode_name = self.create_and_add_mode()
if not mode_name:
print("创建新模式失败")
return False
else:
print(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)
print(f"执行命令: {final_cmd}")
if self.run_xrandr_command(final_cmd):
print("显示器配置成功!")
return True
else:
print("显示器配置失败!")
return False
def check_and_configure(self):
"""检查并配置显示器"""
# 获取当前设置
current_settings = self.get_current_display_settings()
if not current_settings:
print("无法获取当前显示器设置,尝试直接配置...")
return self.configure_display()
print("当前显示器设置:")
for key, value in current_settings.items():
print(f" {key}: {value}")
# 比较设置
match, message = self.compare_settings()
if match:
print(f"✓ 设置匹配: {message}")
return True
else:
print(f"✗ 设置不匹配: {message}")
print("开始配置显示器...")
return self.configure_display()
def main():
"""主函数"""
# 检查DISPLAY环境变量
if 'DISPLAY' not in os.environ:
os.environ['DISPLAY'] = ':0'
print("设置DISPLAY环境变量为:0")
# 解析命令行参数
parser = argparse.ArgumentParser(description='显示器配置检查与设置脚本')
parser.add_argument('config_file', help='配置文件路径')
args = parser.parse_args()
# 检查配置文件是否存在
if not os.path.exists(args.config_file):
print(f"配置文件不存在: {args.config_file}")
sys.exit(1)
# 创建检查器并执行检查与配置
checker = DisplayChecker()
if checker.parse_config_file(args.config_file):
if checker.check_and_configure():
print("显示器检查与配置完成")
sys.exit(0)
else:
print("显示器检查与配置失败")
sys.exit(1)
else:
sys.exit(1)
if __name__ == "__main__":
main()
-8
View File
@@ -1,8 +0,0 @@
#include <gdk/gdk.h>
#include <gtk/gtk.h>
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
GdkMonitor *monitor; // 仅测试该类型是否可识别
return 0;
}
-818
View File
@@ -1,818 +0,0 @@
#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import logging
import time
import os
from display_control import DisplayControl
import sys
import sv_ttk
def get_real_path(relative_path):
"""获取资源的正确绝对路径"""
try:
# 打包后的情况
base_path = sys._MEIPASS
except AttributeError:
# 开发环境的情况
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def get_display_info():
# 默认临时文件路径
temp_file = "/tmp/display_selection.txt"
# 检查环境变量中是否有自定义路径
if 'DISPLAY_SELECTION_FILE' in os.environ:
temp_file = os.environ['DISPLAY_SELECTION_FILE']
display_info = {}
if os.path.exists(temp_file):
with open(temp_file, 'r') as f:
for line in f:
if '=' in line:
key, value = line.strip().split('=', 1)
display_info[key] = value
# 转换为整数
for key in ['MONITOR', 'X', 'Y', 'WIDTH', 'HEIGHT']:
if key in display_info:
try:
display_info[key] = int(display_info[key])
except ValueError:
# 如果转换失败,使用默认值
if key == 'MONITOR':
display_info[key] = 0
elif key in ['X', 'Y']:
display_info[key] = 0
elif key == 'WIDTH':
display_info[key] = 1920
elif key == 'HEIGHT':
display_info[key] = 1080
return display_info
class DisplaySettingsGUI:
def __init__(self, root):
self.root = root
self.root.title("显示器设置工具(for UOS)")
# """根据/tmp/display_selection.txt设置窗口位置"""
display_info = get_display_info()
target_x = 100
target_x = 100
window_width = 950 # 增加窗口宽度
window_height = 600 # 增加窗口高度
try:
# 读取配置
x = display_info.get('X', 0)
y = display_info.get('Y', 0)
width = display_info.get('WIDTH', 1280)
height = display_info.get('HEIGHT', 720)
target_x = int( x + (width - window_width) // 2)
target_y = int(y + (height - window_height) // 2)
except:
pass
self.root.geometry(f"{window_width}x{window_height}+{target_x}+{target_y}")
self.root.resizable(True, True)
# 显示器控制对象
self.display_control = DisplayControl()
# 显示器信息
self.monitors = []
self.monitor_vars = []
self.resolution_vars = []
self.rotation_vars = []
self.primary_vars = []
# 旋转方向映射
self.rotation_names = {
1: "正常",
2: "向左90度",
4: "翻转",
8: "向右90度"
}
# 创建界面
self.create_widgets()
# 获取显示器信息
self.refresh_monitor_info()
def create_widgets(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10") # 增加内边距
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(1, weight=1)
# 标题
# title_label = ttk.Label(main_frame, text="显示器设置工具(for UOS)", font=("Arial", 12, "bold"))
# title_label.grid(row=0, column=0, columnspan=2, pady=(0, 15)) # 增加下边距
# 左侧设置面板
settings_frame = ttk.LabelFrame(main_frame, text="显示器设置", padding="10") # 增加内边距
settings_frame.grid(row=1, column=0,columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 15)) # 增加右边距
# 右侧示意图面板
diagram_frame = ttk.LabelFrame(main_frame, text="显示器排列示意图", padding="10") # 增加内边距
diagram_frame.grid(row=1, column=2, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
main_frame.rowconfigure(1, weight=1)
main_frame.columnconfigure(1, weight=1)
settings_frame.columnconfigure(0, weight=2)
settings_frame.rowconfigure(0, weight=1)
diagram_frame.columnconfigure(0, weight=1)
diagram_frame.rowconfigure(0, weight=1)
# 画布用于显示示意图
self.canvas = tk.Canvas(diagram_frame, bg="#f0f0f0", relief=tk.SUNKEN, bd=1)
self.canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建设置区域的滚动框架
self.scrollable_frame = ttk.Frame(settings_frame)
self.scrollable_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 创建滚动条
scrollbar = ttk.Scrollbar(settings_frame, orient="vertical")
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
# 创建画布用于滚动
self.settings_canvas = tk.Canvas(self.scrollable_frame, yscrollcommand=scrollbar.set)
self.settings_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 配置滚动条
scrollbar.config(command=self.settings_canvas.yview)
# 创建设置区域的内部框架
self.settings_inner_frame = ttk.Frame(self.settings_canvas, padding="1") # 增加内边距
self.settings_canvas.create_window((0, 0), window=self.settings_inner_frame, anchor="nw")
# 绑定配置事件以更新滚动区域
self.settings_inner_frame.bind("<Configure>", self.on_settings_frame_configure)
# 按钮框架
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=2, column=0, columnspan=2, pady=(15, 0)) # 增加上边距
# 刷新按钮
refresh_btn = ttk.Button(button_frame, text="刷新显示器信息", command=self.refresh_monitor_info)
refresh_btn.pack(side=tk.LEFT, padx=(0, 10))
# 应用并保存按钮
apply_save_btn = ttk.Button(button_frame, text="应用并保存设置", command=self.apply_and_save_settings)
apply_save_btn.pack(side=tk.LEFT)
def on_settings_frame_configure(self, event):
"""更新滚动区域"""
self.settings_canvas.configure(scrollregion=self.settings_canvas.bbox("all"))
def refresh_monitor_info(self):
"""获取显示器信息并更新界面"""
try:
self.monitors = self.display_control.get_monitors()
self.update_settings_ui()
self.update_diagram()
except Exception as e:
messagebox.showerror("错误", f"获取显示器信息时出错: {str(e)}")
def update_settings_ui(self):
"""更新设置界面"""
# 清除现有控件
for widget in self.settings_inner_frame.winfo_children():
widget.destroy()
self.monitor_vars = []
self.resolution_vars = []
self.rotation_vars = []
self.primary_vars = []
# 获取所有可用的位置选项
position_options = [str(i+1) for i in range(len(self.monitors))]
# 为每个显示器创建设置控件
for i, monitor in enumerate(self.monitors):
monitor_frame = ttk.LabelFrame(self.settings_inner_frame, text=monitor['name'])
monitor_frame.grid(row=i, column=0, sticky=(tk.W, tk.E), padx=(0,10),pady=(0, 15), ipadx=5, ipady=3) # 增加下边距
monitor_frame.columnconfigure(1, weight=1)
# 分辨率设置(放在第一行)
ttk.Label(monitor_frame, text="分辨率:").grid(row=0, column=0, sticky=tk.W, padx=(0, 20)) # 增加右边距
res_var = tk.StringVar()
resolutions = [mode['label'] for mode in monitor['modes']]
# print(resolutions)
# 设置分辨率
current_res_index = 0
if monitor["current_mode"][0] == 0:
# 自定义分辨率
resolutions = [f"自定义:{monitor['current_mode'][1]}x{monitor['current_mode'][2]}@{monitor['current_mode'][3]}Hz"] + resolutions
else:
for idx, mode in enumerate(monitor['modes']):
if mode['id'] == monitor['current_mode'][0]:
current_res_index = idx
break
res_combo = ttk.Combobox(monitor_frame, textvariable=res_var, values=resolutions, state="readonly", width=25,height=20) # 增加宽度
res_combo.current(current_res_index)
res_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
# 绑定分辨率变化事件
res_combo.bind('<<ComboboxSelected>>',
lambda e, m=monitor, v=res_var: self.on_resolution_change(m, v))
self.resolution_vars.append(res_var)
# 第二行:位置、旋转和主显示器设置
options_frame = ttk.Frame(monitor_frame)
options_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
# 位置设置
ttk.Label(options_frame, text="位置:").pack(side=tk.LEFT, padx=(0, 5))
pos_var = tk.StringVar(value=str(monitor['position']))
pos_combo = ttk.Combobox(options_frame, textvariable=pos_var, values=position_options,
state="readonly", width=5)
pos_combo.pack(side=tk.LEFT, padx=(0, 10))
pos_combo.bind('<<ComboboxSelected>>',
lambda e, m=monitor, v=pos_var: self.update_monitor_position(m, v))
self.monitor_vars.append(pos_var)
# 分隔线
ttk.Separator(options_frame, orient='vertical').pack(side=tk.LEFT, padx=10, fill=tk.Y)
# 旋转设置
ttk.Label(options_frame, text="旋转:").pack(side=tk.LEFT, padx=(0, 5))
rotation_var = tk.StringVar(value=str(monitor['rotation']))
rotation_combo = ttk.Combobox(options_frame, textvariable=rotation_var,
values=["正常", "向左90度", "翻转", "向右90度"], state="readonly", width=10)
rotation_combo.pack(side=tk.LEFT, padx=(0, 10))
# 设置显示文本为旋转方向名称
rotation_name = self.rotation_names.get(monitor['rotation'], "正常")
rotation_combo.set(rotation_name)
# 绑定旋转变化事件
rotation_combo.bind('<<ComboboxSelected>>',
lambda e, m=monitor, v=rotation_var: self.on_rotation_change(m, v))
self.rotation_vars.append(rotation_var)
# 分隔线
ttk.Separator(options_frame, orient='vertical').pack(side=tk.LEFT, padx=10, fill=tk.Y)
# 主屏设置
primary_var = tk.BooleanVar(value=monitor['is_primary'])
primary_check = ttk.Checkbutton(options_frame, text="主显示器",
variable=primary_var,
command=lambda v=primary_var, m=monitor: self.update_primary_monitor(v, m))
primary_check.pack(side=tk.LEFT)
self.primary_vars.append(primary_var)
def on_resolution_change(self, monitor, var):
"""当分辨率改变时更新显示器信息"""
selected_res = var.get()
if selected_res == "自定义分辨率":
self.prompt_custom_resolution(monitor, var)
else:
# 解析分辨率并更新显示器信息
if "自定义" in selected_res:
selected_res = selected_res[4:]
parts = selected_res.split(' @ ')
resolution = parts[0].split('x')
width = int(resolution[0])
height = int(resolution[1])
monitor['width'] = width
monitor['height'] = height
# 更新所有显示器的坐标
self.update_all_monitor_positions()
# 更新示意图
self.update_diagram()
def update_all_monitor_positions(self):
"""更新所有显示器的坐标"""
# 按位置分组显示器
position_groups = {}
for monitor in self.monitors:
pos = monitor['position']
if pos not in position_groups:
position_groups[pos] = []
position_groups[pos].append(monitor)
# 计算每个位置组的X坐标
x_positions = {}
x_offset = 0
for pos in sorted(position_groups.keys()):
# 使用组中第一个显示器的宽度
monitor = position_groups[pos][0]
x_positions[pos] = x_offset
x_offset += monitor['width']
# 更新所有显示器的坐标
for monitor in self.monitors:
position = monitor['position']
monitor['x'] = x_positions.get(position, 0)
monitor['y'] = 0 # 假设所有显示器在垂直方向对齐
def prompt_custom_resolution(self, monitor, res_var):
"""提示用户输入自定义分辨率"""
dialog = CustomResolutionDialog(self.root, monitor)
result = dialog.result
if result:
width, height, refresh_rate, force_custom = result
monitor['custom_width'] = width
monitor['custom_height'] = height
monitor['custom_refresh_rate'] = refresh_rate
monitor['force_custom'] = force_custom
# 更新显示器信息
monitor['width'] = width
monitor['height'] = height
# 更新所有显示器的坐标
self.update_all_monitor_positions()
# 更新下拉框显示
res_var.set(f"自定义:{width}x{height}@{refresh_rate}Hz")
# 更新示意图
self.update_diagram()
def on_rotation_change(self, monitor, var):
"""当旋转方向改变时更新显示器信息"""
rotation_str = var.get()
rotation_map = {
"正常": 1,
"向左90度": 2,
"翻转": 4,
"向右90度": 8
}
rotation = rotation_map.get(rotation_str, 1)
old_rotation = monitor["rotation"]
if old_rotation in [1, 4]:
if rotation in [2,8]:
buff = monitor['width']
monitor['width'] = monitor['height']
monitor['height'] = buff
else:
if rotation in [1,4]:
buff = monitor['width']
monitor['width'] = monitor['height']
monitor['height'] = buff
monitor['rotation'] = rotation
self.update_all_monitor_positions()
# 更新示意图
self.update_diagram()
def update_monitor_position(self, monitor, var):
"""更新显示器位置"""
try:
position = int(var.get())
if 1 <= position <= len(self.monitors):
old_position = monitor.get('position', 1)
monitor['position'] = position
# 更新所有显示器的坐标
self.update_all_monitor_positions()
self.update_diagram()
except ValueError:
pass
def update_primary_monitor(self, var, monitor):
"""更新主显示器设置"""
if var.get():
# 取消其他显示器的选中状态
for i, primary_var in enumerate(self.primary_vars):
if primary_var != var:
primary_var.set(False)
for m in self.monitors:
m["is_primary"] = False
# 设置主显示器
monitor['is_primary'] = True
# 更新示意图
self.update_diagram()
else:
# 不允许取消主显示器,至少需要有一个主显示器
var.set(True)
# messagebox.showwarning("警告", "至少需要有一个主显示器")
def update_diagram(self):
"""更新显示器排列示意图,根据实际坐标进行渲染"""
if not self.monitors:
return
self.canvas.delete("all")
# 计算所有显示器的边界
min_x = min(monitor.get('x', 0) for monitor in self.monitors)
max_x = max(monitor.get('x', 0) + monitor['width'] for monitor in self.monitors)
min_y = min(monitor.get('y', 0) for monitor in self.monitors)
max_y = max(monitor.get('y', 0) + monitor['height'] for monitor in self.monitors)
# 计算画布大小和缩放比例
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
if canvas_width <= 1 or canvas_height <= 1:
# 画布尚未渲染,使用默认大小
canvas_width = 400
canvas_height = 300
# 计算总宽度和高度
total_width = max_x - min_x
total_height = max_y - min_y
# 计算缩放比例,保留边距
scale_x = 0.8 * canvas_width / total_width if total_width > 0 else 1
scale_y = 0.8 * canvas_height / total_height if total_height > 0 else 1
scale = min(scale_x, scale_y)
# 计算偏移量,使所有显示器居中显示
offset_x = (canvas_width - total_width * scale) / 2 - min_x * scale
offset_y = (canvas_height - total_height * scale) / 2 - min_y * scale
# 按坐标和分辨率分组显示器
coord_groups = {}
for monitor in self.monitors:
x = monitor.get('x', 0)
y = monitor.get('y', 0)
width = monitor['width']
height = monitor['height']
# 创建分组键
group_key = (x, y, width, height)
if group_key not in coord_groups:
coord_groups[group_key] = []
coord_groups[group_key].append(monitor)
# 绘制每个组的显示器
for group_key, monitors in coord_groups.items():
x, y, width, height = group_key
# 计算在画布上的位置
canvas_x = offset_x + x * scale
canvas_y = offset_y + y * scale
canvas_width_scaled = width * scale
canvas_height_scaled = height * scale
# 绘制组的背景矩形(仅在多个显示器共享相同坐标和分辨率时)
if len(monitors) > 1:
self.canvas.create_rectangle(
canvas_x, canvas_y,
canvas_x + canvas_width_scaled, canvas_y + canvas_height_scaled,
fill="#e0e0e0", outline="#a0a0a0", width=1, dash=(5, 5)
)
# 收集所有显示器的名称和旋转信息
all_names = []
for monitor in monitors:
rotation_text = self.rotation_names.get(monitor['rotation'], "未知")
name_text = f"{monitor['name']} "
if monitor['is_primary']:
name_text += " [主]"
all_names.append(name_text)
# 在组上方显示所有显示器名称
if all_names:
name_text = "\n".join(all_names)
self.canvas.create_text(
canvas_x + canvas_width_scaled / 2, canvas_y - 20,
text=name_text,
font=("Arial", 9, "bold"),
justify=tk.CENTER,
fill="blue"
)
# 绘制每个显示器(按面积从大到小排序,小的覆盖大的)
sorted_monitors = sorted(monitors, key=lambda m: m['width'] * m['height'], reverse=True)
# 计算重叠显示器的偏移量
offset_step = min(10 * scale, 10) # 最大偏移10像素
max_offset = offset_step * (len(sorted_monitors) - 1)
for i, monitor in enumerate(sorted_monitors):
# 计算显示器的偏移位置
monitor_x = canvas_x + min(i * offset_step, max_offset)
monitor_y = canvas_y + min(i * offset_step, max_offset)
# 绘制矩形
fill_color = "lightblue" if monitor['is_primary'] else "white"
rect_id = self.canvas.create_rectangle(
monitor_x, monitor_y,
monitor_x + canvas_width_scaled, monitor_y + canvas_height_scaled,
fill=fill_color, outline="black", width=2
)
# 添加显示器分辨率和旋转信息
rotation_text = self.rotation_names.get(monitor['rotation'], "未知")
text_lines = [
f"{monitor['width']}x{monitor['height']}",
f"旋转: {rotation_text}"
]
text_content = "\n".join(text_lines)
text_id = self.canvas.create_text(
monitor_x + canvas_width_scaled/2, monitor_y + canvas_height_scaled/2,
text=text_content,
font=("Arial", 8),
justify=tk.CENTER
)
# 添加坐标标签
coord_text = f"({x}, {y})"
self.canvas.create_text(
canvas_x + canvas_width_scaled / 2, canvas_y + canvas_height_scaled + 15,
text=coord_text,
font=("Arial", 8),
fill="green"
)
def apply_settings(self):
"""应用显示器设置"""
try:
# 检查位置冲突
position_groups = {}
for monitor in self.monitors:
pos = monitor['position']
if pos not in position_groups:
position_groups[pos] = []
position_groups[pos].append(monitor)
# 检查是否有重叠的显示器
has_overlap = any(len(monitors) > 1 for monitors in position_groups.values())
if has_overlap:
overlap_info = []
for pos, monitors in position_groups.items():
if len(monitors) > 1:
monitor_names = ", ".join([m['name'] for m in monitors])
overlap_info.append(f"位置 {pos}: {monitor_names}")
warning_msg = "以下位置的显示器将重叠显示:\n" + "\n".join(overlap_info)
if not messagebox.askyesno("位置重叠警告", f"{warning_msg}\n\n是否继续?"):
return
# 计算每个位置组的X坐标
x_positions = {}
x_offset = 0
for pos in sorted(position_groups.keys()):
# 使用组中第一个显示器的宽度
monitor = position_groups[pos][0]
x_positions[pos] = x_offset
x_offset += monitor['width']
# 在设置主显示器之前调用SwitchMode
self.display_control.switch_mode()
# 为每个显示器应用设置
for i, monitor in enumerate(self.monitors):
# 设置分辨率
selected_res = self.resolution_vars[i].get()
# print(selected_res)
if selected_res:
if "自定义" in selected_res:
config=""
# 使用自定义分辨率
# 设置旋转
rotation_str = self.rotation_vars[i].get()
rotation_map = {
"正常": 1,
"向左90度": 2,
"翻转": 4,
"向右90度": 8
}
rotation = rotation_map.get(rotation_str, 1)
# 设置位置
position = monitor['position']
x = x_positions.get(position, 0)
y = 0
# 设置主显示器
isPrimary="N"
if self.primary_vars[i].get():
isPrimary="P"
# 添加强制标志
force_flag = "F" if monitor.get('force_custom', False) else "A"
config=f'{selected_res[4:]}|{x},{y}|{rotation}|{isPrimary}|{force_flag}'
# print(config)
self.display_control.save_custom_resolution(monitor['name'], config)
continue
else:
self.display_control.remove_custom_resolution(monitor['name'])
# 解析分辨率和刷新率
parts = selected_res.split(' @ ')
resolution = parts[0].split('x')
width = int(resolution[0])
height = int(resolution[1])
refresh_rate = float(parts[1].replace('Hz', ''))
# 优先使用Mode接口
mode_id = None
for mode in monitor['modes']:
if mode['label'] == selected_res:
mode_id = mode['id']
break
if mode_id and mode_id != 'custom':
self.display_control.set_mode(monitor['path'], mode_id)
else:
# 备用方案:使用SetModeBySize和SetRefreshRate接口
self.display_control.set_mode_by_size(monitor['path'], width, height)
self.display_control.set_refresh_rate(monitor['path'], refresh_rate)
# 设置旋转
rotation_str = self.rotation_vars[i].get()
rotation_map = {
"正常": 1,
"向左90度": 2,
"翻转": 4,
"向右90度": 8
}
rotation = rotation_map.get(rotation_str, 1)
self.display_control.set_rotation(monitor['path'], rotation)
# 设置位置
position = monitor['position']
x = x_positions.get(position, 0)
y = 0 # 假设所有显示器在垂直方向对齐
self.display_control.set_position(monitor['path'], x, y)
# 设置主显示器
if self.primary_vars[i].get():
self.display_control.set_primary(monitor['name'])
# 应用更改
self.display_control.apply_changes()
# 等待1秒后刷新显示器信息
self.root.after(1000, self.refresh_monitor_info)
# messagebox.showinfo("成功", "显示器设置已应用")
except Exception as e:
messagebox.showerror("错误", f"应用设置时出错: {str(e)}")
def apply_and_save_settings(self):
"""应用并保存设置"""
try:
# 先应用设置
self.apply_settings()
# 3秒后保存设置
self.root.after(3000, self.save_settings_with_delay)
except Exception as e:
messagebox.showerror("错误", f"应用设置时出错: {str(e)}")
def save_settings_with_delay(self):
"""延迟保存设置"""
try:
file_path = "/tmp/ktouch/display_update.txt"
dir_path = "/tmp/ktouch"
# 检查目录是否存在
if os.path.exists(dir_path):
# 写入文件(不存在则创建)
with open(file_path, 'w') as f:
f.write("1")
self.display_control.save_settings()
# messagebox.showinfo("成功", "显示器设置已保存")
except Exception as e:
messagebox.showerror("错误", f"保存设置时出错: {str(e)}")
def on_resize(self, event):
"""处理窗口大小变化事件"""
if event.widget == self.root:
self.update_diagram()
class CustomResolutionDialog:
"""自定义分辨率对话框"""
def __init__(self, parent, monitor):
self.parent = parent
self.monitor = monitor
self.result = None
self.dialog = tk.Toplevel(parent)
self.dialog.title("自定义分辨率")
self.dialog.geometry("350x250") # 增加高度以容纳新控件
self.dialog.transient(parent)
self.dialog.grab_set()
# 加载之前保存的自定义分辨率
custom_width = monitor.get('custom_width', 1920)
custom_height = monitor.get('custom_height', 1080)
custom_refresh = monitor.get('custom_refresh_rate', 60.0)
force_custom = monitor.get('force_custom', False)
# 宽度设置
ttk.Label(self.dialog, text="宽度:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.width_var = tk.StringVar(value=str(custom_width))
ttk.Entry(self.dialog, textvariable=self.width_var).grid(row=0, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
# 高度设置
ttk.Label(self.dialog, text="高度:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.height_var = tk.StringVar(value=str(custom_height))
ttk.Entry(self.dialog, textvariable=self.height_var).grid(row=1, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
# 刷新率设置
ttk.Label(self.dialog, text="刷新率 (Hz):").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
self.refresh_var = tk.StringVar(value=str(custom_refresh))
ttk.Entry(self.dialog, textvariable=self.refresh_var).grid(row=2, column=1, padx=5, pady=5, sticky=(tk.W, tk.E))
# 强制使用自定义分辨率复选框
self.force_var = tk.BooleanVar(value=force_custom)
force_check = ttk.Checkbutton(
self.dialog,
text="强制使用自定义分辨率而不是选取驱动分辨率",
variable=self.force_var
)
force_check.grid(row=3, column=0, columnspan=2, padx=5, pady=10, sticky=tk.W)
# 按钮框架
button_frame = ttk.Frame(self.dialog)
button_frame.grid(row=4, column=0, columnspan=2, pady=10)
ttk.Button(button_frame, text="确定", command=self.on_ok).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="取消", command=self.on_cancel).pack(side=tk.LEFT, padx=5)
# 配置网格权重
self.dialog.columnconfigure(1, weight=1)
self.dialog.wait_window()
def on_ok(self):
"""确定按钮处理"""
try:
width = int(self.width_var.get())
height = int(self.height_var.get())
refresh_rate = float(self.refresh_var.get())
force_custom = self.force_var.get()
if width <= 0 or height <= 0 or refresh_rate <= 0:
raise ValueError("值必须为正数")
self.result = (width, height, refresh_rate, force_custom)
self.dialog.destroy()
except ValueError as e:
messagebox.showerror("错误", f"请输入有效的数值: {str(e)}")
def on_cancel(self):
"""取消按钮处理"""
self.dialog.destroy()
def main():
root = tk.Tk()
# Import the tcl file
root.tk.call('source', '/opt/ktouch/forest-light.tcl')
# Set the theme with the theme_use method
ttk.Style().theme_use('forest-light')
app = DisplaySettingsGUI(root)
root.bind('<Configure>', app.on_resize)
# sv_ttk.use_light_theme()
root.mainloop()
if __name__ == "__main__":
main()
-350
View File
@@ -1,350 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import time
import logging
import signal
import hashlib
import subprocess
from pathlib import Path
from threading import Thread
from time import sleep
# 配置X11环境变量
os.environ['DISPLAY'] = ':0'
# os.environ['XAUTHORITY'] = '/home/pi/.Xauthority' # 根据实际情况调整路径
# 全局变量
processes = {} # 存储进程对象
monitor_thread = None # 监控线程
running = True # 控制循环运行
# 配置日志
def setup_logging():
log_file = 'touchscreen.log'
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file, encoding='utf-8'),
logging.StreamHandler(sys.stdout)
]
)
return logging.getLogger(__name__)
logger = setup_logging()
# 信号处理函数
def signal_handler(signum, frame):
global running
logger.info(f"Received signal {signum}, shutting down...")
running = False
cleanup()
def cleanup():
"""清理函数,停止所有子进程"""
logger.info("清理子进程")
for proc_name, proc_info in processes.items():
try:
proc = proc_info['process']
if proc and proc.poll() is None: # 进程还在运行
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
logger.info(f"关闭了进程: {proc_name}")
except Exception as e:
logger.error(f"关闭进程失败 {proc_name}: {e}")
processes.clear()
# 1. 日志文件轮转函数
def rotate_logs():
log_files = ['screen_calibration.log', 'touchscreen.log']
max_size = 10 * 1024 * 1024 # 10MB
max_backups = 7
for log_file in log_files:
if os.path.exists(log_file) and os.path.getsize(log_file) > max_size:
# 删除最旧的备份文件
oldest_backup = f"{log_file}.{max_backups}"
if os.path.exists(oldest_backup):
os.remove(oldest_backup)
logger.info(f"清理过早的日志: {oldest_backup}")
# 重命名现有备份文件
for i in range(max_backups-1, 0, -1):
old_name = f"{log_file}.{i}"
new_name = f"{log_file}.{i+1}"
if os.path.exists(old_name):
os.rename(old_name, new_name)
# logger.info(f"Renamed {old_name} to {new_name}")
# 重命名当前日志文件
os.rename(log_file, f"{log_file}.1")
# logger.info(f"Rotated log file: {log_file} -> {log_file}.1")
# 2. 检查并创建所需文件
def initialize_files():
ktouch_dir = Path("/tmp/ktouch")
files_to_create = [
"screen.txt",
"touch.txt",
"touch_need_update.txt",
"usbadd.txt"
]
# 创建目录
ktouch_dir.mkdir(exist_ok=True)
logger.info("创建状态缓存目录 /tmp/ktouch ")
# 创建或初始化文件
for file_name in files_to_create:
file_path = ktouch_dir / file_name
with open(file_path, 'w') as f:
f.write('1')
logger.info(f"初始化缓存文件 {file_path}: to 1")
# 启动单个后台程序
def start_process(proc_name, proc_args):
"""启动单个进程并添加到监控列表"""
# 设置X11环境
env = os.environ.copy()
env['DISPLAY'] = ':0'
try:
proc = subprocess.Popen(proc_args, env=env)
processes[proc_name] = {
'process': proc,
'args': proc_args,
'start_time': time.time(),
'restart_count': 0
}
logger.info(f"拉起子进程 {proc_name} (PID: {proc.pid})")
return True
except Exception as e:
logger.error(f"进程启动失败 {proc_name}: {e}")
return False
# 3. 启动后台程序(添加X11环境支持)
def start_background_processes():
process_configs = {
'screen_ds': ["/opt/ktouch/screen_ds", "/tmp/ktouch/screen.txt"],
'touch_ds': ["/opt/ktouch/touch_ds", "/tmp/ktouch/touchmap.txt", "/tmp/ktouch/touch_need_update.txt"],
'usb_ds': ["/opt/ktouch/usb_ds", "/tmp/ktouch/usbadd.txt"]
}
for proc_name, proc_args in process_configs.items():
start_process(proc_name, proc_args)
# # 设置X11环境
# env = os.environ.copy()
# env['DISPLAY'] = ':0'
# for proc_args in processes:
# try:
# subprocess.Popen(proc_args, env=env)
# logger.info(f"Started process: {' '.join(proc_args)}")
# except Exception as e:
# logger.error(f"Failed to start process {proc_args[0]}: {e}")
# 监控和重启进程
def monitor_processes():
"""监控进程状态,如果进程退出则重启"""
global running
while running:
try:
for proc_name, proc_info in list(processes.items()):
proc = proc_info['process']
returncode = proc.poll()
if returncode is not None: # 进程已退出
logger.warning(f"进程 {proc_name} (PID: {proc.pid}) 已退出,代码 {returncode}")
# 限制重启次数,避免无限重启
proc_info['restart_count'] += 1
if proc_info['restart_count'] > 128: # 最多重启128次
logger.error(f"启动 {proc_name} 进程被结束超过128次,决定不再重启。请检查日志文件。")
del processes[proc_name]
continue
# 等待一段时间再重启
time.sleep(2)
# 重新启动进程
if start_process(proc_name, proc_info['args']):
logger.info(f"重启进程: {proc_name}")
else:
logger.error(f"重启进程失败: {proc_name}")
# 检查是否有进程需要启动(初始启动失败的情况)
expected_processes = ['screen_ds', 'touch_ds', 'usb_ds']
for proc_name in expected_processes:
if proc_name not in processes:
logger.warning(f"发现 {proc_name} 消失, 准备重新启动")
# 这里需要根据进程名重新构建参数
if proc_name == 'screen_ds':
start_process(proc_name, ["/opt/ktouch/screen_ds", "/tmp/ktouch/screen.txt"])
elif proc_name == 'touch_ds':
start_process(proc_name, ["/opt/ktouch/touch_ds", "/tmp/ktouch/touchmap.txt", "/tmp/ktouch/touch_need_update.txt"])
elif proc_name == 'usb_ds':
start_process(proc_name, ["/opt/ktouch/usb_ds", "/tmp/ktouch/usbadd.txt"])
time.sleep(5) # 每2秒检查一次进程状态
except Exception as e:
logger.error(f"进程监控出现问题: {e}")
time.sleep(5)
# 4. 执行touch_remap并重置文件(添加X11环境支持)
def run_touch_remap():
# 设置X11环境
env = os.environ.copy()
env['DISPLAY'] = ':0'
try:
# 执行touch_remap程序
result = subprocess.run(["/opt/ktouch/touch_set", "/tmp/ktouch/touch.txt"], capture_output=True, text=True, env=env)
if result.returncode == 0:
logger.info("执行映射(touch_set)成功")
else:
logger.error(f"执行映射(touch_set)失败,错误信息为 {result.returncode}: {result.stderr}")
except Exception as e:
logger.error(f"无法执行touch_set映射程序!!!!!: {e}")
# 重置文件为0
ktouch_dir = Path("/tmp/ktouch")
files_to_reset = [
"touch_need_update.txt",
"usbadd.txt"
]
for file_name in files_to_reset:
file_path = ktouch_dir / file_name
try:
with open(file_path, 'w') as f:
f.write('0')
# logger.info(f"Reset {file_path} to 0")
except Exception as e:
# logger.error(f"Failed to reset {file_path}: {e}")
# 5. 监控文件变化
def monitor_files():
# 记录screen.txt的初始状态
screen_file = "/tmp/ktouch/screen.txt"
last_screen_content = ""
if os.path.exists(screen_file):
with open(screen_file, 'r') as f:
last_screen_content = f.read().strip()
logger.info("开始主程序监控")
while running:
# 检查是否需要执行touch_remap
need_update = False
# 检查usbadd.txt和touch_need_update.txt
for file_name in ["/tmp/ktouch/usbadd.txt", "/tmp/ktouch/touch_need_update.txt"]:
try:
with open(file_name, 'r') as f:
content = f.read().strip()
if content == '1':
logger.info(f"文件 {file_name} 内容变为1,触发重映射")
need_update = True
except Exception as e:
logger.error(f"读取文件失败 {file_name}: {e}")
# 检查screen.txt是否发生变化
try:
with open(screen_file, 'r') as f:
current_content = f.read().strip()
if current_content != last_screen_content:
logger.info("文件 screen.txt 内容变化,触发重映射")
need_update = True
last_screen_content = current_content
except Exception as e:
logger.error(f"读取文件失败 {screen_file}: {e}")
# 如果需要更新,执行touch_remap
if need_update:
run_touch_remap()
# 等待一段时间再次检查
time.sleep(1)
# 检查X11环境是否可用
def check_x11_environment():
try:
# 尝试运行一个简单的X11命令来检查环境
result = subprocess.run(['xset', '-q'], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
logger.info("X11 environment is available")
return True
else:
logger.warning("X11 environment check failed")
return False
except Exception as e:
logger.error(f"Error checking X11 environment: {e}")
return False
# 主函数
def main():
global monitor_thread, running
try:
# 注册信号处理
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
logger.info("kscreen_remap_daemon 程序启动")
# 检查X11环境
if not check_x11_environment():
logger.warning("主程序:X11 environment may not be available, 程序将继续运行但无效果")
# 1. 日志轮转
rotate_logs()
logger.info("主程序:处理日志 OK")
# 2. 初始化文件
initialize_files()
logger.info("主程序:初始化和检查文件 OK")
# 3. 启动后台进程
start_background_processes()
logger.info("主程序:启动子进程 OK")
# 启动进程监控线程
monitor_thread = Thread(target=monitor_processes, daemon=True)
monitor_thread.start()
logger.info("主程序:启动进程监控 OK")
sleep(3)
# 4. 首次执行touch_remap
run_touch_remap()
logger.info("主程序:执行一次映射 OK")
sleep(7)
# 5. 开始监控文件变化
logger.info("主程序:开始监控loop")
monitor_files()
except Exception as e:
logger.error(f"Unexpected error in main: {e}")
running = False
cleanup()
sys.exit(1)
finally:
cleanup()
logger.info("Daemon script stopped")
if __name__ == "__main__":
main()
-99
View File
@@ -1,99 +0,0 @@
# 多个子功能源代码
## 源代码对应程序的功能
### screen_binder + touch_listen.c
创建全屏窗口给与提示,并检测触摸点击事件,生成显示器和触摸屏对应列表到当前目录下。
生成的路径是'touch_dis_table.txt',文件格式为 显示器名|触摸屏名|触摸屏ID|触摸屏设备路径
### screen_ds
检测显示器分辨率、数量、坐标是否发生了变化,需要通过参数指定记录文件路径,一般在/tmp下。
文件格式 显示器名|分辨率XxY|坐标x,y
-----
定义路径: /tmp/ktouch/screen.txt
### touch_ds
检测触摸屏的矩阵是否变化,需要通过参数指定记录文件
记格式为 12|ELAN Touchscreen|/dev/input/event5|04f3:2c2c|1.000000,0.000000,0.000000,0.000000,1.000000,0.000000,0.000000,0.000000,1.000000
touch_ds传入两个参数,第一个是对比文件的路径,第二个是通知上位程序更新的文件。
---
定义路径: /tmp/ktouch/touchmap.txt/tmp/ktouch/touch_need_update.txt
### usb_ds
检测usb是否有新的接入行为,判断接入是是否是触摸屏,需要通过参数指定记录文件。
接入触摸屏后,将会将指定文件内容改成1,提醒主程序,主程序处理完成后应该将其改回0。
----
定义路径: /tmp/ktouch/usbadd.txt
## 打包后目录中文件说明
check_save 设置后的二次确认窗口,以确定是否要替换触摸屏配置文件
desktops/ 快捷方式存放的目录
display_monitor.py 监控屏幕分辨率是否被修改的脚本,后台保持运行
forest-light/ 显示器设置GUI的主题文件
forest-light.tcl 显示器设置GUI的主题文件
kdisplay 显示器设置程序
kscreen-debug 触摸屏debug程序,用于现场快速排错
kscreen-fix-daemon 修复service的专用脚本
kscreen-log 采集程序日志和系统日志并打包
kscreen-remap 单次映射/校准触摸屏
kscreen-remap-daemon 主要功能实现的程序,在后台监控触摸屏状态及其他状态,适时操作校准
kscreen-setup 设置触摸屏脚本
screen_binder 设置触摸屏的GUI主体程序
screen_ds 显示屏变化监控
screen_ds_once 更新一次显示器信息,显示屏监控的简化版
src 所有二进制程序的源文件,保留在程序内防止丢失
touch_ds 对每个触摸屏的矩阵监控
touch_set 计算校准矩阵并写入libinput的主体程序
usb_ds usb设备插拔监控
## 功能设计
### 执行设置的功能
首先关闭所有的监控程序可功能。
执行设置时,先通过脚本准备环境,包括桌面环境的检查、文件路径的创建。
然后执行 screen_binder 程序进行识别。
完成后解析上个步骤生成的临时文件,将其补充信息后转换成我们可用的文件。
最后拉起监控程序。
### 检测功能
由服务拉起脚本,脚本配置好环境后在用户环境运行主程序,主程序使用python编写。
1. 准备环境、文件夹
2. 首先启动屏幕监控,会实时更新screen.txt。
3. 启动usb监控,会实时更新usbadd.txt。
4. 发起一次校准操作。
5. 程序死循环运行,除非收到关闭信号。
5.1 高频轮询screen.txt和usbadd.txt的更新
5.2 低频启动touch_ds,检测touchmap.txt和计算值的差异。
5.3 以上任一一个条件触发重新校准操作。
### 校准功能
1. 读取配置文件并解析
2. 读取screen.txt,并对齐
3. 计算校准矩阵
4. 将校准写入系统
编译环境
sudo apt install libinput-tools python3-evdev python3-pyudev -y
apt download libevdev-dev libinput-dev libmtdev-dev libudev-dev libwacom-dev libevdev2 libinput-bin libinput10 libudev1 libwacom-common libwacom2 udev
fix3更新
程序完全兼容麒麟和uos,解决了大量麒麟上的显示bug。垃圾麒麟!!
fix4更新
新增支持旋转方向适配,计算矩阵时将会考虑到屏幕的旋转。
新增支持签字笔的配置,采用传统方式。
fix5更新
新增UOS显示器设置工具
新增旋转适配
新增debug脚本用于现场快速检查
-618
View File
@@ -1,618 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <stdarg.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <stdatomic.h>
#include "touch_listen.h"
// 添加Xrandr头文件和X11显示类型头文件
#ifdef GDK_WINDOWING_X11
#include <X11/extensions/Xrandr.h>
#include <gdk/x11/gdkx11display.h>
#endif
// 全局变量
GtkWidget **calibration_windows = NULL;
GtkWidget **info_windows = NULL;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int current_screen = 0;
int total_screens = 0;
int *screen_widths = NULL;
int *screen_heights = NULL;
int *screen_x_offsets = NULL;
int *screen_y_offsets = NULL;
char **screen_names = NULL;
int *touch_bindings = NULL;
char **touch_names = NULL;
char **touch_paths = NULL;
int touch_received = 0;
int enter_pressed = 0;
time_t screen_start_time = 0;
guint timeout_id = 0;
FILE *log_file = NULL;
int info_window_index = 0; // 用于跟踪信息窗口创建进度
Display *xdisplay = NULL; // X11显示连接
// 使用原子操作确保线程安全的状态标志
_Atomic int global_enter_pressed = 0; // 全局回车键按下标志
_Atomic int program_active = 0; // 程序是否处于活动状态
_Atomic int calibration_active = 0; // 校准是否正在进行
pthread_mutex_t enter_mutex = PTHREAD_MUTEX_INITIALIZER; // 回车键互斥锁
pthread_t enter_thread; // 全局回车键监听线程
pthread_t touch_thread;
// 函数声明
void show_next_screen();
void touch_callback(struct touch_event event);
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data);
gboolean on_timeout(gpointer user_data);
GtkWidget* create_info_window(int screen_index);
GtkWidget* create_calibration_window(int screen_index);
void on_calibration_window_destroy(GtkWidget *widget, gpointer user_data);
void get_screen_info();
void* touch_listener_thread(void* arg);
void log_message(const char *format, ...);
void save_mapping_table();
gboolean create_info_windows_timeout(gpointer user_data);
void* global_enter_listener_thread(void* arg); // 全局回车监听线程
gboolean check_enter_pressed(gpointer user_data); // 检查回车键按下的超时函数
void cleanup_resources(); // 清理资源函数
// 保存映射关系表到文件
void save_mapping_table() {
FILE *map_file = fopen("touch_dis_table.txt", "w");
if (!map_file) {
log_message("无法打开映射关系表文件 touch_dis_table.txt\n");
return;
}
log_message("保存映射关系到文件: touch_dis_table.txt\n");
for (int i = 0; i < total_screens; i++) {
if (touch_bindings[i] != -1) {
fprintf(map_file, "%s|%s|%d|%s\n",
screen_names[i],
touch_names[i],
touch_bindings[i],
touch_paths[i]);
} else {
fprintf(map_file, "%s| | | \n", screen_names[i]);
}
}
fclose(map_file);
log_message("映射关系表保存完成\n");
}
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
printf("[%s] screen_binder \t", time_str);
vprintf(format, args);
if (log_file) {
fprintf(log_file, "[%s] screen_binder \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file);
}
va_end(args);
}
// 显示错误对话框
void show_error_dialog(const char *message) {
GtkWidget *dialog = gtk_message_dialog_new(NULL,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"%s", message);
gtk_window_set_title(GTK_WINDOW(dialog), "错误");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
// 触摸事件回调函数
void touch_callback(struct touch_event event) {
pthread_mutex_lock(&mutex);
if(event.event_type == 1){
pthread_mutex_unlock(&mutex);
return;
}
const char *event_types[] = {"按下", "移动", "释放"};
log_message("触摸事件: 设备ID=%d, 设备名=%s, 设备路径=%s, 类型=%s, 坐标=(%d, %d)\n",
event.device_id, event.device_name, event.device_path, event_types[event.event_type], event.x, event.y);
static int touch_down_received = 0;
if (event.event_type == 0) {
touch_down_received = 1;
log_message("按下事件记录\n");
}
else if (event.event_type == 2 && touch_down_received) {
log_message("有效触摸序列完成 (按下+释放)\n");
touch_bindings[current_screen] = event.device_id;
strncpy(touch_names[current_screen], event.device_name, 255);
touch_names[current_screen][255] = '\0';
strncpy(touch_paths[current_screen], event.device_path, 255);
touch_paths[current_screen][255] = '\0';
touch_received = 1;
touch_down_received = 0;
g_idle_add((GSourceFunc)gtk_widget_destroy, calibration_windows[current_screen]);
}
pthread_mutex_unlock(&mutex);
}
// 键盘事件处理
gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
log_message("用户按下回车键,跳过屏幕 %d (%s)\n", current_screen, screen_names[current_screen]);
gtk_widget_destroy(widget);
return TRUE;
}
return FALSE;
}
// 超时处理函数
gboolean on_timeout(gpointer user_data) {
log_message("屏幕 %d (%s) 超时60秒,继续下一个屏幕\n", current_screen, screen_names[current_screen]);
gtk_widget_destroy(calibration_windows[current_screen]);
timeout_id = 0;
return G_SOURCE_REMOVE;
}
// 创建信息提示窗口
GtkWidget* create_info_window(int screen_index) {
GdkRectangle rect = {
.x = screen_x_offsets[screen_index],
.y = screen_y_offsets[screen_index],
.width = screen_widths[screen_index],
.height = screen_heights[screen_index]
};
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "屏幕校准");
gtk_window_move(GTK_WINDOW(window), rect.x, rect.y);
gtk_window_set_default_size(GTK_WINDOW(window), rect.width, rect.height);
gtk_window_set_decorated(GTK_WINDOW(window), TRUE);
gtk_window_set_keep_above(GTK_WINDOW(window), TRUE); // 确保窗口在最前面
char message[512];
snprintf(message, sizeof(message),
"<span font='28' weight='bold'>正在校准其他屏幕,请根据其他屏幕上的信息指导操作。</span>\n\n当前屏幕接口: %s",
screen_names[screen_index]);
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), message);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
// 添加容器使标签居中
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box);
gtk_window_set_deletable(GTK_WINDOW(window), FALSE);
return window;
}
// 创建校准窗口
GtkWidget* create_calibration_window(int screen_index) {
GdkRectangle rect = {
.x = screen_x_offsets[screen_index],
.y = screen_y_offsets[screen_index],
.width = screen_widths[screen_index],
.height = screen_heights[screen_index]
};
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "触摸屏校准");
gtk_window_move(GTK_WINDOW(window), rect.x, rect.y);
gtk_window_set_default_size(GTK_WINDOW(window), rect.width, rect.height);
gtk_window_fullscreen(GTK_WINDOW(window));
gtk_window_set_decorated(GTK_WINDOW(window), TRUE);
gtk_window_set_keep_above(GTK_WINDOW(window), TRUE); // 确保窗口在最前面
gtk_window_set_modal(GTK_WINDOW(window), TRUE); // 设置为模态窗口,防止失去焦点
// 设置窗口类型提示为对话框,增加获得焦点的机会
gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
// 强制窗口获得焦点
gtk_window_present(GTK_WINDOW(window));
char message[512];
snprintf(message, sizeof(message),
"<span font='32' weight='bold' foreground='red'>请在触摸屏上点击此屏幕。</span>\n\n屏幕接口: %s\n\n如果此屏幕不是触摸屏,那么请敲击回车或者等待60秒。\n\n点击后请查看下一个屏幕,请勿重复点击。",
screen_names[screen_index]);
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label), message);
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
// 添加容器使标签居中
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), label, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box);
g_signal_connect(window, "key-press-event", G_CALLBACK(on_key_press), NULL);
return window;
}
// 窗口关闭事件处理
void on_calibration_window_destroy(GtkWidget *widget, gpointer user_data) {
log_message("关闭屏幕 %d (%s) 的校准窗口\n", current_screen, screen_names[current_screen]);
// 取消超时计时器
if (timeout_id > 0) {
g_source_remove(timeout_id);
timeout_id = 0;
}
// 如果所有屏幕都已处理完毕
if (++current_screen >= total_screens) {
// 设置程序状态为非活动
atomic_store(&program_active, 0);
atomic_store(&calibration_active, 0);
// 首先,销毁所有信息窗口
for (int i = 0; i < total_screens; i++) {
if (info_windows[i] != NULL) {
gtk_widget_destroy(info_windows[i]);
info_windows[i] = NULL;
}
}
// 打印绑定结果
log_message("\n=== 绑定结果 ===\n");
for (int i = 0; i < total_screens; i++) {
log_message("屏幕 %d (%s): ", i, screen_names[i]);
if (touch_bindings[i] != -1) {
log_message("触摸设备ID: %d, 设备名: %s, 设备路径: %s\n", touch_bindings[i], touch_names[i], touch_paths[i]);
} else {
log_message("未绑定触摸设备\n");
}
}
// 保存映射关系表
save_mapping_table();
// 清理资源
cleanup_resources();
gtk_main_quit();
} else {
// 显示下一个屏幕的窗口
show_next_screen();
}
}
// 超时创建信息窗口的回调函数
gboolean create_info_windows_timeout(gpointer user_data) {
if (info_window_index < total_screens) {
info_windows[info_window_index] = create_info_window(info_window_index);
gtk_widget_show_all(info_windows[info_window_index]);
log_message("为屏幕 %d (%s) 创建信息窗口\n", info_window_index, screen_names[info_window_index]);
info_window_index++;
return TRUE; // 继续调用
} else {
// 所有信息窗口创建完毕,开始校准流程
show_next_screen();
return FALSE; // 停止调用
}
}
// 显示下一个屏幕的窗口
void show_next_screen() {
log_message("\n=== 处理屏幕 %d (%s) ===\n", current_screen, screen_names[current_screen]);
// 设置校准状态为活动
atomic_store(&calibration_active, 1);
// 创建当前屏幕的校准窗口
calibration_windows[current_screen] = create_calibration_window(current_screen);
g_signal_connect(calibration_windows[current_screen], "destroy",
G_CALLBACK(on_calibration_window_destroy), NULL);
gtk_widget_show_all(calibration_windows[current_screen]);
// 强制窗口获得焦点
gtk_window_present(GTK_WINDOW(calibration_windows[current_screen]));
log_message("创建校准窗口完成\n");
// 重置状态
pthread_mutex_lock(&mutex);
touch_received = 0;
enter_pressed = 0;
screen_start_time = time(NULL);
pthread_mutex_unlock(&mutex);
// 设置60秒超时
timeout_id = g_timeout_add_seconds(60, on_timeout, NULL);
log_message("显示提示文本完成,开始60秒计时\n");
}
// 获取屏幕信息 (使用Xrandr方法)
void get_screen_info() {
GdkDisplay *gdk_display = gdk_display_get_default();
#ifdef GDK_WINDOWING_X11
if (!GDK_IS_X11_DISPLAY(gdk_display)) {
log_message("错误: 非X11显示环境\n");
return;
}
xdisplay = GDK_DISPLAY_XDISPLAY(gdk_display);
Window xroot = GDK_WINDOW_XID(gdk_get_default_root_window());
int rr_event_base, rr_error_base;
if (!XRRQueryExtension(xdisplay, &rr_event_base, &rr_error_base)) {
log_message("错误: XRandR扩展不可用\n");
return;
}
XRRScreenResources *screen_res = XRRGetScreenResourcesCurrent(xdisplay, xroot);
if (!screen_res) {
log_message("错误: 无法获取屏幕资源\n");
return;
}
// 计算连接的显示器数量
total_screens = 0;
for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
total_screens++;
}
if (output_info) XRRFreeOutputInfo(output_info);
}
log_message("找到 %d 个连接的显示器\n", total_screens);
// 分配内存存储屏幕信息
screen_widths = malloc(total_screens * sizeof(int));
screen_heights = malloc(total_screens * sizeof(int));
screen_x_offsets = malloc(total_screens * sizeof(int));
screen_y_offsets = malloc(total_screens * sizeof(int));
screen_names = malloc(total_screens * sizeof(char*));
touch_bindings = malloc(total_screens * sizeof(int));
touch_names = malloc(total_screens * sizeof(char*));
touch_paths = malloc(total_screens * sizeof(char*));
calibration_windows = malloc(total_screens * sizeof(GtkWidget*));
info_windows = malloc(total_screens * sizeof(GtkWidget*));
// 获取每个显示器的详细信息
int screen_index = 0;
for (int i = 0; i < screen_res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(xdisplay, screen_res, screen_res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
if (output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(xdisplay, screen_res, output_info->crtc);
if (crtc_info) {
screen_names[screen_index] = strdup(output_info->name);
screen_widths[screen_index] = crtc_info->width;
screen_heights[screen_index] = crtc_info->height;
screen_x_offsets[screen_index] = crtc_info->x;
screen_y_offsets[screen_index] = crtc_info->y;
touch_bindings[screen_index] = -1;
touch_names[screen_index] = malloc(256 * sizeof(char));
strcpy(touch_names[screen_index], "未绑定");
touch_paths[screen_index] = malloc(256 * sizeof(char));
strcpy(touch_paths[screen_index], "/dev/null");
calibration_windows[screen_index] = NULL;
info_windows[screen_index] = NULL;
log_message("显示器 %d: %s, 分辨率: %dx%d, 位置: %dx%d\n",
screen_index, screen_names[screen_index],
screen_widths[screen_index], screen_heights[screen_index],
screen_x_offsets[screen_index], screen_y_offsets[screen_index]);
XRRFreeCrtcInfo(crtc_info);
screen_index++;
}
}
}
if (output_info) XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(screen_res);
#else
log_message("错误: 非X11环境,无法使用Xrandr\n");
#endif
}
// 触摸监听线程
void* touch_listener_thread(void* arg) {
log_message("开始监听触摸事件...\n");
listen_touch_events();
return NULL;
}
// 全局回车键监听线程
void* global_enter_listener_thread(void* arg) {
log_message("开始全局监听回车键...\n");
// 打开X11显示连接
Display *display = XOpenDisplay(NULL);
if (!display) {
log_message("无法打开X11显示连接以监听全局回车键\n");
return NULL;
}
// 获取根窗口
Window root = DefaultRootWindow(display);
// 抓取回车键
XGrabKey(display, XKeysymToKeycode(display, XK_Return), AnyModifier,
root, False, GrabModeAsync, GrabModeAsync);
XGrabKey(display, XKeysymToKeycode(display, XK_KP_Enter), AnyModifier,
root, False, GrabModeAsync, GrabModeAsync);
XEvent event;
while (atomic_load(&program_active)) {
// 使用非阻塞方式检查事件
if (XPending(display) > 0) {
XNextEvent(display, &event);
if (event.type == KeyPress) {
KeySym keysym = XLookupKeysym(&event.xkey, 0);
if (keysym == XK_Return || keysym == XK_KP_Enter) {
// 只有在校准活动时才响应回车键
if (atomic_load(&calibration_active)) {
log_message("全局回车键被按下(校准过程中)\n");
atomic_store(&global_enter_pressed, 1);
} else {
log_message("全局回车键被按下(非校准过程中,忽略)\n");
}
}
}
} else {
// 短暂睡眠以减少CPU使用
usleep(100000); // 100毫秒
}
}
// 释放抓取的键
XUngrabKey(display, XKeysymToKeycode(display, XK_Return), AnyModifier, root);
XUngrabKey(display, XKeysymToKeycode(display, XK_KP_Enter), AnyModifier, root);
XCloseDisplay(display);
log_message("全局回车键监听线程退出\n");
return NULL;
}
// 检查回车键按下的超时函数
gboolean check_enter_pressed(gpointer user_data) {
if (atomic_load(&global_enter_pressed)) {
atomic_store(&global_enter_pressed, 0);
// 只有在校准活动时才处理回车键
if (atomic_load(&calibration_active) && calibration_windows[current_screen]) {
log_message("检测到全局回车键按下,跳过屏幕 %d (%s)\n", current_screen, screen_names[current_screen]);
g_idle_add((GSourceFunc)gtk_widget_destroy, calibration_windows[current_screen]);
}
}
return G_SOURCE_CONTINUE; // 继续调用
}
// 清理资源函数
void cleanup_resources() {
// 停止触摸监听
stop_touch_listener();
// // 等待触摸线程结束
// if (touch_thread) {
// pthread_join(touch_thread, NULL);
// touch_thread = 0;
// }
// 清理屏幕信息相关资源
for (int i = 0; i < total_screens; i++) {
free(screen_names[i]);
free(touch_names[i]);
free(touch_paths[i]);
}
free(screen_widths);
free(screen_heights);
free(screen_x_offsets);
free(screen_y_offsets);
free(screen_names);
free(touch_bindings);
free(touch_names);
free(touch_paths);
free(calibration_windows);
free(info_windows);
// 关闭X11显示连接
// if (xdisplay) {
// XCloseDisplay(xdisplay);
// xdisplay = NULL;
// }
// 关闭日志文件
if (log_file) {
fclose(log_file);
log_file = NULL;
}
}
int main(int argc, char *argv[]) {
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("=== 触摸屏与显示器绑定程序 (使用Xrandr获取屏幕信息) ===\n");
// 设置程序状态为活动
atomic_store(&program_active, 1);
atomic_store(&calibration_active, 0);
gtk_init(&argc, &argv);
get_screen_info();
log_message("初始化触摸监听器...\n");
int rst = init_touch_listener(touch_callback);
if(rst){
log_message("未找到触摸设备或无法打开触摸设备,请使用sudo运行。\n");
show_error_dialog("未找到触摸设备或无法打开触摸设备,请使用sudo运行。\n");
log_message("程序结束\n");
cleanup_resources();
return 0;
}
// pthread_t touch_thread;
touch_thread = 0;
if (pthread_create(&touch_thread, NULL, touch_listener_thread, NULL) != 0) {
log_message("无法创建触摸监听线程\n");
cleanup_resources();
return 1;
}
// pthread_detach(touch_thread);
// 创建全局回车键监听线程
if (pthread_create(&enter_thread, NULL, global_enter_listener_thread, NULL) != 0) {
log_message("无法创建全局回车键监听线程\n");
}
// 添加定时器检查回车键按下
g_timeout_add(100, check_enter_pressed, NULL); // 每100毫秒检查一次
// 使用超时函数依次创建信息窗口,间隔100毫秒
info_window_index = 0;
g_timeout_add(10, create_info_windows_timeout, NULL);
gtk_main();
// 设置程序状态为非活动,等待线程退出
atomic_store(&program_active, 0);
// 等待全局回车键监听线程退出
pthread_join(enter_thread, NULL);
log_message("程序结束\n");
return 0;
}
-247
View File
@@ -1,247 +0,0 @@
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#define DEBOUNCE_TIME 3 // 防抖时间(秒)
typedef struct {
char* output_name;
int width;
int height;
int x;
int y;
int rotation; // 新增:旋转方向
} MonitorInfo;
typedef struct {
MonitorInfo* monitors;
int count;
} MonitorList;
FILE *log_file = NULL; // 日志文件指针
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] screen_ds \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] screen_ds \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 获取当前时间戳(毫秒)
long long current_timestamp() {
struct timeval te;
gettimeofday(&te, NULL);
long long milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000;
return milliseconds;
}
// 将旋转值转换为单个字母表示
char rotation_to_char(int rotation) {
switch (rotation) {
case RR_Rotate_0: return 'N'; // 正常
case RR_Rotate_90: return 'L'; // 左旋转
case RR_Rotate_180: return 'I'; // 倒转
case RR_Rotate_270: return 'R'; // 右旋转
default: return 'N'; // 未知则也是正常方向
}
}
// 获取显示器信息
MonitorList get_monitor_info(Display* dpy, Window root) {
MonitorList list = {NULL, 0};
XRRScreenResources *res = XRRGetScreenResourcesCurrent(dpy, root);
if (!res) {
log_message( "无法获取屏幕资源\n");
return list;
}
// 第一次遍历计算连接中的显示器数量
int connected_count = 0;
for (int i = 0; i < res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
connected_count++;
}
XRRFreeOutputInfo(output_info);
}
// 分配内存
list.monitors = malloc(connected_count * sizeof(MonitorInfo));
list.count = 0;
// 第二次遍历获取详细信息
for (int i = 0; i < res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
if (output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(dpy, res, output_info->crtc);
if (crtc_info) {
list.monitors[list.count].output_name = strdup(output_info->name);
list.monitors[list.count].width = crtc_info->width;
list.monitors[list.count].height = crtc_info->height;
list.monitors[list.count].x = crtc_info->x;
list.monitors[list.count].y = crtc_info->y;
list.monitors[list.count].rotation = crtc_info->rotation; // 新增:记录旋转方向
list.count++;
XRRFreeCrtcInfo(crtc_info);
}
}
}
XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(res);
return list;
}
// 释放显示器列表内存
void free_monitor_list(MonitorList* list) {
for (int i = 0; i < list->count; i++) {
free(list->monitors[i].output_name);
}
free(list->monitors);
list->monitors = NULL;
list->count = 0;
}
// 输出显示器信息到文件或标准输出
void output_monitor_info(MonitorList* list, const char* filename) {
FILE* output = stdout;
if (filename) {
output = fopen(filename, "w");
if (!output) {
log_message("无法打开目标文件: %s\n", filename);
output = stdout;
}
}
log_message("检测到显示器:\n");
for (int i = 0; i < list->count; i++) {
fprintf(output, "%s|%dx%d|%dx%d|%c\n",
list->monitors[i].output_name,
list->monitors[i].width,
list->monitors[i].height,
list->monitors[i].x,
list->monitors[i].y,
rotation_to_char(list->monitors[i].rotation));
log_message(" %s:分辨率%dx%d, 位置%dx%d, 旋转:%c\n",
list->monitors[i].output_name,
list->monitors[i].width,
list->monitors[i].height,
list->monitors[i].x,
list->monitors[i].y,
rotation_to_char(list->monitors[i].rotation));
}
if (output != stdout) {
fclose(output);
log_message("显示器信息已保存到: %s\n", filename);
}
}
int main(int argc, char *argv[]) {
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("=== 显示器监控程序 ===\n");
Display *dpy = XOpenDisplay(NULL);
if (!dpy) {
log_message("无法打开X显示\n");
return 1;
}
int rr_event_base, rr_error_base;
if (!XRRQueryExtension(dpy, &rr_event_base, &rr_error_base)) {
log_message("RandR扩展未找到\n");
XCloseDisplay(dpy);
return 1;
}
Window root = DefaultRootWindow(dpy);
// 监听屏幕变化和输出变化事件
XRRSelectInput(dpy, root, RRScreenChangeNotifyMask | RROutputChangeNotifyMask);
// 获取命令行参数
const char* output_file = NULL;
if (argc > 1) {
output_file = argv[1];
}
int need_update = 0;
long long last_event_time = 0;
printf("开始监听显示器变化...\n");
printf("使用Ctrl+C退出程序\n");
if (output_file) {
printf("结果将保存到: %s\n", output_file);
} else {
printf("结果将输出到标准输出\n");
}
// 保存首次信息
MonitorList monitors = get_monitor_info(dpy, root);
output_monitor_info(&monitors, output_file);
free_monitor_list(&monitors);
XEvent ev;
while (1) {
// 检查是否有事件,但设置超时以避免阻塞
if (XPending(dpy)) {
XNextEvent(dpy, &ev);
// 处理RandR事件
if (ev.type == rr_event_base + RRScreenChangeNotify ||
ev.type == rr_event_base + RRNotify) {
need_update = 1;
last_event_time = current_timestamp();
}
} else {
// 没有事件时检查是否需要更新
long long current_time = current_timestamp();
if (need_update && (current_time - last_event_time) >= DEBOUNCE_TIME * 1000) {
MonitorList monitors = get_monitor_info(dpy, root);
output_monitor_info(&monitors, output_file);
free_monitor_list(&monitors);
need_update = 0;
}
// 短暂休眠以减少CPU使用
usleep(100000); // 100ms
}
}
XCloseDisplay(dpy);
return 0;
}
-223
View File
@@ -1,223 +0,0 @@
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#define DEBOUNCE_TIME 3 // 防抖时间(秒)
typedef struct {
char* output_name;
int width;
int height;
int x;
int y;
int rotation; // 新增:旋转方向
} MonitorInfo;
typedef struct {
MonitorInfo* monitors;
int count;
} MonitorList;
FILE *log_file = NULL; // 日志文件指针
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] screen_ds_once \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] screen_ds \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 获取当前时间戳(毫秒)
long long current_timestamp() {
struct timeval te;
gettimeofday(&te, NULL);
long long milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000;
return milliseconds;
}
// 将旋转值转换为单个字母表示
char rotation_to_char(int rotation) {
switch (rotation) {
case RR_Rotate_0: return 'N'; // 正常
case RR_Rotate_90: return 'L'; // 左旋转
case RR_Rotate_180: return 'I'; // 倒转
case RR_Rotate_270: return 'R'; // 右旋转
default: return 'N'; // 未知则也是正常方向
}
}
// 获取显示器信息
MonitorList get_monitor_info(Display* dpy, Window root) {
MonitorList list = {NULL, 0};
XRRScreenResources *res = XRRGetScreenResourcesCurrent(dpy, root);
if (!res) {
log_message( "无法获取屏幕资源\n");
return list;
}
// 第一次遍历计算连接中的显示器数量
int connected_count = 0;
for (int i = 0; i < res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
connected_count++;
}
XRRFreeOutputInfo(output_info);
}
// 分配内存
list.monitors = malloc(connected_count * sizeof(MonitorInfo));
list.count = 0;
// 第二次遍历获取详细信息
for (int i = 0; i < res->noutput; i++) {
XRROutputInfo *output_info = XRRGetOutputInfo(dpy, res, res->outputs[i]);
if (output_info && output_info->connection == RR_Connected) {
if (output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(dpy, res, output_info->crtc);
if (crtc_info) {
list.monitors[list.count].output_name = strdup(output_info->name);
list.monitors[list.count].width = crtc_info->width;
list.monitors[list.count].height = crtc_info->height;
list.monitors[list.count].x = crtc_info->x;
list.monitors[list.count].y = crtc_info->y;
list.monitors[list.count].rotation = crtc_info->rotation; // 新增:记录旋转方向
list.count++;
XRRFreeCrtcInfo(crtc_info);
}
}
}
XRRFreeOutputInfo(output_info);
}
XRRFreeScreenResources(res);
return list;
}
// 释放显示器列表内存
void free_monitor_list(MonitorList* list) {
for (int i = 0; i < list->count; i++) {
free(list->monitors[i].output_name);
}
free(list->monitors);
list->monitors = NULL;
list->count = 0;
}
// 输出显示器信息到文件或标准输出
void output_monitor_info(MonitorList* list, const char* filename) {
FILE* output = stdout;
if (filename) {
output = fopen(filename, "w");
if (!output) {
log_message("无法打开目标文件: %s\n", filename);
output = stdout;
}
}
log_message("检测到显示器:\n");
for (int i = 0; i < list->count; i++) {
fprintf(output, "%s|%dx%d|%dx%d|%c\n",
list->monitors[i].output_name,
list->monitors[i].width,
list->monitors[i].height,
list->monitors[i].x,
list->monitors[i].y,
rotation_to_char(list->monitors[i].rotation));
log_message(" %s:分辨率%dx%d, 位置%dx%d, 旋转:%c\n",
list->monitors[i].output_name,
list->monitors[i].width,
list->monitors[i].height,
list->monitors[i].x,
list->monitors[i].y,
rotation_to_char(list->monitors[i].rotation));
}
if (output != stdout) {
fclose(output);
log_message("显示器信息已保存到: %s\n", filename);
}
}
int main(int argc, char *argv[]) {
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("=== 显示器监控程序(单次执行) ===\n");
Display *dpy = XOpenDisplay(NULL);
if (!dpy) {
log_message("无法打开X显示\n");
return 1;
}
int rr_event_base, rr_error_base;
if (!XRRQueryExtension(dpy, &rr_event_base, &rr_error_base)) {
log_message("RandR扩展未找到\n");
XCloseDisplay(dpy);
return 1;
}
Window root = DefaultRootWindow(dpy);
// 监听屏幕变化和输出变化事件
//XRRSelectInput(dpy, root, RRScreenChangeNotifyMask | RROutputChangeNotifyMask);
// 获取命令行参数
const char* output_file = NULL;
if (argc > 1) {
output_file = argv[1];
}
int need_update = 0;
long long last_event_time = 0;
//printf("开始监听显示器变化...\n");
//printf("使用Ctrl+C退出程序\n");
if (output_file) {
printf("结果将保存到: %s\n", output_file);
} else {
printf("结果将输出到标准输出\n");
}
// 保存首次信息
MonitorList monitors = get_monitor_info(dpy, root);
output_monitor_info(&monitors, output_file);
free_monitor_list(&monitors);
//XEvent ev;
XCloseDisplay(dpy);
return 0;
}
-2
View File
@@ -1,2 +0,0 @@
HDMI-0|ILITEK Multi-Touch-V5000|1435117611|/dev/input/event10
VGA-0|ILITEK ILITEK-TP|1435117617|/dev/input/event16
-623
View File
@@ -1,623 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <libudev.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/XInput.h>
#include <sys/stat.h>
#include <ctype.h>
// 监控等待时间
int watch_time_delay = 30;
FILE *log_file = NULL; // 日志文件指针
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] touch_ds \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] touch_ds \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 清理字符串中可能存在的空格
void clean_string(const char* input, char* output, size_t output_size) {
if (!input || !output || output_size == 0) {
if (output && output_size > 0) {
output[0] = '\0';
}
return;
}
// 第一步:去除所有空格
char temp[256] = {0};
size_t temp_idx = 0;
for (size_t i = 0; input[i] != '\0' && temp_idx < sizeof(temp) - 1; i++) {
if (!isspace((unsigned char)input[i])) {
temp[temp_idx++] = input[i];
}
}
temp[temp_idx] = '\0';
// 第二步:去除首尾引号
const char* start = temp;
const char* end = temp + strlen(temp) - 1;
if (*start == '"' || *start == '\'') {
start++;
}
if (end >= start && (*end == '"' || *end == '\'')) {
end--;
}
// 第三步:复制到输出缓冲区
size_t len = end - start + 1;
if (len >= output_size) {
len = output_size - 1;
}
strncpy(output, start, len);
output[len] = '\0';
}
// 获取FLOAT原子类型
Atom get_float_atom(Display* display) {
return XInternAtom(display, "FLOAT", False);
}
// 获取STRING原子类型
Atom get_string_atom(Display* display) {
return XInternAtom(display, "STRING", False);
}
// 获取INTEGER原子类型
Atom get_integer_atom(Display* display) {
return XInternAtom(display, "INTEGER", False);
}
// 获取设备属性值
char* get_device_property(Display* display, XID deviceid, const char* prop_name, Atom expected_type) {
Atom prop = XInternAtom(display, prop_name, False);
Atom type;
int format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char* data = NULL;
// 获取设备属性
int result = XIGetProperty(display, deviceid, prop, 0, 1024, False,
AnyPropertyType, &type, &format,
&nitems, &bytes_after, &data);
if (result == Success && type == expected_type && data != NULL) {
if (format == 8) { // 字符串类型
return strdup((char*)data);
}
}
if (data) XFree(data);
return NULL;
}
// 获取设备矩阵属性
float* get_device_matrix_property(Display* display, XID deviceid, const char* prop_name, int* success) {
Atom prop = XInternAtom(display, prop_name, False);
Atom float_atom = get_float_atom(display);
Atom type;
int format;
unsigned long nitems;
unsigned long bytes_after;
unsigned char* data = NULL;
// 获取设备属性
int result = XIGetProperty(display, deviceid, prop, 0, 9, False,
AnyPropertyType, &type, &format,
&nitems, &bytes_after, &data);
if (result == Success && type == float_atom && format == 32 && nitems == 9) {
*success = 1;
return (float*)data;
}
if (data) XFree(data);
*success = 0;
return NULL;
}
// 判断设备是否存在
// 检查设备是否存在
int device_exists(Display* display, XID deviceid) {
int ndevices;
XIDeviceInfo* devices = XIQueryDevice(display, XIAllDevices, &ndevices);
if (!devices) {
return 0; // 无法获取设备列表
}
int exists = 0;
for (int i = 0; i < ndevices; i++) {
if (devices[i].deviceid == deviceid) {
exists = 1;
break;
}
}
XIFreeDeviceInfo(devices);
return exists;
}
// 获取设备矩阵属性字符串表示
int get_matrix_string(Display* display, XID deviceid, char* result) {
// 第一步检查设备
// 首先检查设备是否存在
if (!device_exists(display, deviceid)) {
return 0;
}
int success;
float* matrix = NULL;
// char* result = malloc(256); // 分配足够空间存储矩阵字符串
// log_message("尝试获取设备%d的校准矩阵\n", deviceid);
// 先尝试获取 Coordinate Transformation Matrix
matrix = get_device_matrix_property(display, deviceid,
"Coordinate Transformation Matrix", &success);
// 如果没有,尝试获取 libinput Calibration Matrix
if (!success) {
matrix = get_device_matrix_property(display, deviceid,
"libinput Calibration Matrix", &success);
}
if (success && matrix) {
// 格式化矩阵字符串,保留6位小数,去掉括号
sprintf(result, "%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f,%.6f",
matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5],
matrix[6], matrix[7], matrix[8]);
XFree(matrix);
} else {
strcpy(result, "None");
}
return 1;
}
// 获取设备节点信息
char* get_device_node(Display* display, XID deviceid, const char* device_name) {
// 尝试不同的属性名称来获取设备节点
char* device_node = NULL;
Atom string_atom = get_string_atom(display);
// 尝试不同的属性名称
const char* prop_names[] = {
"Device Node",
"device-node",
"DEVICE_NODE",
NULL
};
for (int i = 0; prop_names[i] != NULL; i++) {
device_node = get_device_property(display, deviceid, prop_names[i], string_atom);
if (device_node != NULL) {
// printf(" 找到设备节点: %s (属性: %s)\n", device_node, prop_names[i]);
break;
}
}
if (device_node == NULL) {
device_node = strdup("Unknown");
// printf(" 未能找到设备节点,使用 'Unknown'\n");
}
return device_node;
}
// 检查文件是否存在
int file_exists(const char *path) {
struct stat buf;
return (stat(path, &buf) == 0);
}
// 使用udev检查设备是否为触摸屏并获取VID:PID
int is_touchscreen_device(const char* device_node, char* vid, char* pid, int buffer_size) {
if (strcmp(device_node, "Unknown") == 0) {
// printf(" 设备节点未知,跳过触摸屏检查\n");
return 0;
}
// 首先检查设备节点是否存在
if (!file_exists(device_node)) {
// printf(" 设备节点不存在: %s\n", device_node);
return 0;
}
struct udev *udev;
struct udev_device *dev;
int is_touchscreen = 0;
// 初始化VID和PID
strncpy(vid, "Unknown", buffer_size);
strncpy(pid, "Unknown", buffer_size);
// 创建udev上下文
udev = udev_new();
if (!udev) {
log_message("无法创建udev上下文\n");
return 0;
}
// printf(" 正在检查设备: %s\n", device_node);
// 获取设备信息
dev = udev_device_new_from_syspath(udev, device_node);
if (!dev) {
log_message(" 无法从syspath创建udev设备: %s\n", device_node);
// 尝试通过设备节点创建udev设备
struct stat st;
if (stat(device_node, &st) == 0) {
dev = udev_device_new_from_devnum(udev, 'c', st.st_rdev);
// if (dev) {
// printf(" 通过设备节点创建udev设备成功\n");
// } else {
// printf(" 通过设备节点创建udev设备失败\n");
// }
} else {
log_message(" 无法获取设备状态: %s\n", device_node);
}
}
if (dev) {
// 打印设备属性
// printf(" 设备属性:\n");
struct udev_list_entry *properties = udev_device_get_properties_list_entry(dev);
struct udev_list_entry *entry;
udev_list_entry_foreach(entry, properties) {
const char *name = udev_list_entry_get_name(entry);
const char *value = udev_list_entry_get_value(entry);
if (strstr(name, "INPUT") || strstr(name, "ID_")) {
// printf(" %s=%s\n", name, value);
// 获取VID和PID
if (strcmp(name, "ID_VENDOR_ID") == 0) {
strncpy(vid, value, buffer_size);
// printf(" 找到VID: %s\n", vid);
} else if (strcmp(name, "ID_MODEL_ID") == 0) {
strncpy(pid, value, buffer_size);
// printf(" 找到PID: %s\n", pid);
}
}
}
// 向上遍历设备树,查找输入设备属性
struct udev_device *parent = dev;
int level = 0;
while (parent && level < 10) { // 限制遍历深度
// 检查ID_INPUT_TOUCHSCREEN属性
const char *touchscreen = udev_device_get_property_value(parent, "ID_INPUT_TOUCHSCREEN");
if (touchscreen) {
// printf(" 在层级 %d 找到 ID_INPUT_TOUCHSCREEN=%s\n", level, touchscreen);
if (strcmp(touchscreen, "1") == 0) {
is_touchscreen = 1;
// 获取VID和PID
const char *vendor_id = udev_device_get_property_value(parent, "ID_VENDOR_ID");
const char *model_id = udev_device_get_property_value(parent, "ID_MODEL_ID");
if (vendor_id) {
strncpy(vid, vendor_id, buffer_size);
// printf(" 找到VID: %s\n", vid);
}
if (model_id) {
strncpy(pid, model_id, buffer_size);
// printf(" 找到PID: %s\n", pid);
}
break;
}
}
// 检查其他相关属性
const char *capabilities = udev_device_get_property_value(parent, "ID_INPUT");
if (capabilities) {
// printf(" 在层级 %d 找到 ID_INPUT=%s\n", level, capabilities);
if (strstr(capabilities, "touchscreen")) {
is_touchscreen = 1;
// 获取VID和PID
const char *vendor_id = udev_device_get_property_value(parent, "ID_VENDOR_ID");
const char *model_id = udev_device_get_property_value(parent, "ID_MODEL_ID");
if (vendor_id) {
strncpy(vid, vendor_id, buffer_size);
// printf(" 找到VID: %s\n", vid);
}
if (model_id) {
strncpy(pid, model_id, buffer_size);
// printf(" 找到PID: %s\n", pid);
}
break;
}
}
// 检查父设备
parent = udev_device_get_parent(parent);
level++;
}
if (!is_touchscreen) {
// 通过设备名称关键词匹配(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);
} else {
// printf(" 无法创建udev设备对象\n");
}
udev_unref(udev);
return is_touchscreen;
}
// 从文件中读取触摸屏信息
typedef struct {
XID deviceid;
char name[100];
char node[100];
char vid_pid[20];
char matrix[120];
} TouchscreenInfo;
int read_touchscreens_from_file(const char* filename, TouchscreenInfo *touchscreens) {
int count = 0;
int max_count = 25;
FILE* file = fopen(filename, "r");
if (!file) {
log_message("无法打开文件: %s\n", filename);
return 0;
}
char line[1024];
while (fgets(line, sizeof(line), file) && count < max_count) {
// 移除行尾的换行符
line[strcspn(line, "\n")] = '\0';
// 跳过空行
if (strlen(line) == 0) {
continue;
}
// 使用strtok分割字符串
char* token = strtok(line, "|");
if (!token) continue;
// 解析设备ID
touchscreens[count].deviceid = strtoul(token, NULL, 10);
// 解析名称
token = strtok(NULL, "|");
if (!token) continue;
strncpy(touchscreens[count].name, token, sizeof(touchscreens[count].name) - 1);
touchscreens[count].name[sizeof(touchscreens[count].name) - 1] = '\0'; // 确保字符串终止
// 解析设备节点
token = strtok(NULL, "|");
if (!token) continue;
strncpy(touchscreens[count].node, token, sizeof(touchscreens[count].node) - 1);
touchscreens[count].node[sizeof(touchscreens[count].node) - 1] = '\0';
// 解析VID:PID
token = strtok(NULL, "|");
if (!token) continue;
strncpy(touchscreens[count].vid_pid, token, sizeof(touchscreens[count].vid_pid) - 1);
touchscreens[count].vid_pid[sizeof(touchscreens[count].vid_pid) - 1] = '\0';
// 解析矩阵
token = strtok(NULL, "|");
if (!token) continue;
char matrix_with_space[120];
strncpy(matrix_with_space, token, sizeof(touchscreens[count].matrix) - 1);
clean_string(matrix_with_space, touchscreens[count].matrix, sizeof(matrix_with_space));
// touchscreens[count].matrix[sizeof(touchscreens[count].matrix) - 1] = '\0';
count++;
}
fclose(file);
return count;
}
// 检查flag文件是否为1
int is_flag_set(const char* flagfile) {
if (!flagfile || !file_exists(flagfile)) {
return 0;
}
FILE* file = fopen(flagfile, "r");
if (!file) {
return 0;
}
char value[2];
fgets(value, sizeof(value), file);
fclose(file);
return (value[0] == '1');
}
// 设置flag文件值
void set_flag(const char* flagfile, int value) {
if (!flagfile) return;
FILE* file = fopen(flagfile, "w");
if (!file) {
log_message("无法打开flag文件: %s\n", flagfile);
return;
}
fprintf(file, "%d", value);
fclose(file);
log_message("设置flag文件 %s 为 %d\n", flagfile, value);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
fprintf(stderr, "用法: %s <触摸屏信息文件> [flag文件]\n", argv[0]);
return 1;
}
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("=== 触摸屏矩阵监控 ===\n");
const char* touchscreen_file = argv[1];
const char* flag_file = argc > 2 ? argv[2] : NULL;
// 读取触摸屏信息文件
int touchscreen_count = 0;
TouchscreenInfo touchscreens[20];
while(1){
touchscreen_count = read_touchscreens_from_file(touchscreen_file, touchscreens);
if (!touchscreens || touchscreen_count == 0) {
fprintf(stderr, "没有找到触摸屏信息或无法读取文件,等待10秒后重新开始\n");
sleep(10);
continue;
}else break;
}
Display* display = XOpenDisplay(NULL);
if (!display) {
log_message("无法打开X显示\n");
// free_touchscreens(touchscreens, touchscreen_count);
return 1;
}
// 检查XInput扩展
int opcode, event, error;
if (!XQueryExtension(display, "XInputExtension", &opcode, &event, &error)) {
log_message("X Input扩展不可用\n");
XCloseDisplay(display);
// free_touchscreens(touchscreens, touchscreen_count);
return 1;
}
// 主循环
while (1) {
// 检查flag文件
if (flag_file && is_flag_set(flag_file)) {
printf("检测暂停,flag文件值为1\n");
sleep(10);
continue;
}
int changed = 0;
touchscreen_count = read_touchscreens_from_file(touchscreen_file, touchscreens);
if (!touchscreens || touchscreen_count == 0) {
fprintf(stderr, "没有找到触摸屏信息或无法读取文件,等待10秒后重新开始\n");
sleep(10);
continue;
}
// 检查每个触摸屏设备的矩阵
for (int i = 0; i < touchscreen_count; i++) {
TouchscreenInfo* info = &touchscreens[i];
printf("检查设备 ID: %lu, 名称: %s\n", info->deviceid, info->name);
// 获取当前矩阵,产生的数据已经不带空格了
char current_matrix[100] = {0};
int isDeviceExist = 0;
// char* current_matrix = get_matrix_string(display, info->deviceid);
isDeviceExist = get_matrix_string(display, info->deviceid, current_matrix);
if(!isDeviceExist) {
printf("设备%lu不存在\n", info->deviceid);
continue;
}
// 比较矩阵
if (strcmp(current_matrix, info->matrix) != 0) {
log_message("设备 %lu 的矩阵发生变化:原矩阵: %s, 新矩阵: %s\n", info->deviceid,info->matrix,current_matrix);
changed = 1;
}
// free(current_matrix);
// 如果检测到变化,设置flag并跳出循环
if (changed) {
if (flag_file) {
set_flag(flag_file, 1);
}
break;
}
}
if (!changed) {
// printf("所有触摸屏设备矩阵未发生变化\n");
}
// 等待10秒
sleep(watch_time_delay);
}
// 清理资源(实际上不会执行到这里,因为上面是无限循环)
// free_touchscreens(touchscreens, touchscreen_count);
XCloseDisplay(display);
return 0;
}
-424
View File
@@ -1,424 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
#include <dirent.h>
#include <errno.h>
#include <stdbool.h>
#include <time.h>
#include "touch_listen.h"
// 全局变量
struct touch_device *devices = NULL;
int device_count = 0;
touch_event_callback event_callback = NULL;
static volatile int listener_running = 0;
// 计算字符串的简单哈希值,用于设备标识
int hash_string(const char *str) {
int hash = 0;
while (*str) {
hash = hash * 31 + *str++;
}
return hash & 0x7FFFFFFF; // 确保是正数
}
// 检查设备是否是触摸屏
bool is_touch_device(const char *device_path) {
int fd = open(device_path, O_RDONLY);
if (fd < 0) {
return false;
}
unsigned long evbit = 0;
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
// 检查设备是否支持绝对坐标和触摸事件
if (!(evbit & (1 << EV_ABS)) ||
!(evbit & (1 << EV_KEY))) {
close(fd);
return false;
}
// 检查是否支持ABS_MT_POSITION_X事件(多点触控)
unsigned long absbit = 0;
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), &absbit);
if (absbit & (1 << ABS_MT_POSITION_X)) {
close(fd);
return true;
}
// 检查是否支持ABS_X事件(单点触控)
if (absbit & (1 << ABS_X)) {
close(fd);
return true;
}
close(fd);
return false;
}
// 获取设备信息
void get_device_info(struct touch_device *device) {
int fd = open(device->path, O_RDONLY);
if (fd < 0) {
return;
}
// 获取设备名称
ioctl(fd, EVIOCGNAME(sizeof(device->name)), device->name);
// 获取X轴范围
struct input_absinfo absinfo;
if (ioctl(fd, EVIOCGABS(ABS_X), &absinfo) >= 0) {
device->min_x = absinfo.minimum;
device->max_x = absinfo.maximum;
}
// 获取Y轴范围
if (ioctl(fd, EVIOCGABS(ABS_Y), &absinfo) >= 0) {
device->min_y = absinfo.minimum;
device->max_y = absinfo.maximum;
}
close(fd);
}
// 发现所有触摸屏设备
int discover_touch_devices() {
DIR *dir;
struct dirent *entry;
char path[256];
// 释放之前分配的内存
if (devices != NULL) {
for (int i = 0; i < device_count; i++) {
if (devices[i].fd >= 0) {
close(devices[i].fd);
}
}
free(devices);
devices = NULL;
device_count = 0;
}
// 打开输入设备目录
dir = opendir("/dev/input");
if (!dir) {
perror("无法打开 /dev/input");
return -1;
}
// 第一次遍历,计算设备数量
while ((entry = readdir(dir)) != NULL) {
if (strncmp(entry->d_name, "event", 5) == 0) {
snprintf(path, sizeof(path), "/dev/input/%s", entry->d_name);
if (is_touch_device(path)) {
device_count++;
}
}
}
rewinddir(dir);
// 分配设备数组内存
devices = malloc(device_count * sizeof(struct touch_device));
if (!devices) {
closedir(dir);
return -1;
}
// 第二次遍历,填充设备信息
int index = 0;
while ((entry = readdir(dir)) != NULL && index < device_count) {
if (strncmp(entry->d_name, "event", 5) == 0) {
snprintf(path, sizeof(path), "/dev/input/%s", entry->d_name);
if (is_touch_device(path)) {
strncpy(devices[index].path, path, sizeof(devices[index].path));
get_device_info(&devices[index]);
devices[index].fd = -1; // 初始化为未打开状态
index++;
}
}
}
closedir(dir);
return device_count;
}
// 打开所有触摸屏设备
int open_touch_devices() {
for (int i = 0; i < device_count; i++) {
devices[i].fd = open(devices[i].path, O_RDONLY | O_NONBLOCK);
if (devices[i].fd < 0) {
perror("无法打开设备");
return -1;
}
}
return 0;
}
// 关闭所有触摸屏设备
void close_touch_devices() {
for (int i = 0; i < device_count; i++) {
if (devices[i].fd >= 0) {
close(devices[i].fd);
devices[i].fd = -1;
}
}
}
// 处理输入事件
void process_input_event(struct input_event *ev, struct touch_device *device,
struct touch_event *touch_ev) {
static int x = 0, y = 0, pressure = 0, touch_id = 0;
touch_ev->device_id = hash_string(device->path);
strncpy(touch_ev->device_name, device->name, sizeof(touch_ev->device_name));
strncpy(touch_ev->device_path, device->path, sizeof(touch_ev->device_path));
// 获取当前时间戳
clock_gettime(CLOCK_MONOTONIC, &touch_ev->timestamp);
switch (ev->type) {
case EV_ABS:
switch (ev->code) {
case ABS_X:
case ABS_MT_POSITION_X:
x = ev->value;
// 转换为实际坐标(如果需要)
if (device->max_x > device->min_x) {
touch_ev->x = x;
}
break;
case ABS_Y:
case ABS_MT_POSITION_Y:
y = ev->value;
// 转换为实际坐标(如果需要)
if (device->max_y > device->min_y) {
touch_ev->y = y;
}
break;
case ABS_PRESSURE:
case ABS_MT_PRESSURE:
pressure = ev->value;
touch_ev->pressure = pressure;
break;
case ABS_MT_TRACKING_ID:
touch_id = ev->value;
touch_ev->touch_id = touch_id;
if (touch_id == -1) {
// 触摸释放
touch_ev->event_type = 2; // 释放
if (event_callback) {
event_callback(*touch_ev);
}
} else {
// 新触摸点
touch_ev->event_type = 0; // 按下
if (event_callback) {
event_callback(*touch_ev);
}
}
break;
}
break;
case EV_KEY:
if (ev->code == BTN_TOUCH) {
if (ev->value) {
touch_ev->event_type = 0; // 按下
} else {
touch_ev->event_type = 2; // 释放
}
if (event_callback) {
event_callback(*touch_ev);
}
}
break;
case EV_SYN:
if (ev->code == SYN_REPORT) {
// 报告同步事件,表示一组事件完成
touch_ev->event_type = 1; // 移动
touch_ev->x = x;
touch_ev->y = y;
touch_ev->pressure = pressure;
touch_ev->touch_id = touch_id;
if (event_callback && (x != 0 || y != 0)) {
event_callback(*touch_ev);
}
}
break;
}
}
// 监听触摸事件
void listen_touch_events() {
fd_set fds;
int max_fd = 0;
struct input_event ev;
struct touch_event touch_ev;
listener_running = 1; // 设置运行标志
while (listener_running) {
FD_ZERO(&fds);
max_fd = 0;
// 设置文件描述符集合
for (int i = 0; i < device_count; i++) {
if (devices[i].fd >= 0) {
FD_SET(devices[i].fd, &fds);
if (devices[i].fd > max_fd) {
max_fd = devices[i].fd;
}
}
}
if(listener_running == 0) break;
if (max_fd == 0) {
usleep(100000); // 如果没有设备,短暂睡眠
continue;
}
if(listener_running == 0) break;
// 使用select等待事件
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
int ret = select(max_fd + 1, &fds, NULL, NULL, &timeout);
if (ret < 0) {
perror("select错误");
break;
}
if(listener_running == 0) break;
// 检查每个设备是否有事件
for (int i = 0; i < device_count; i++) {
if (devices[i].fd >= 0 && FD_ISSET(devices[i].fd, &fds)) {
// 读取所有可用事件
while (read(devices[i].fd, &ev, sizeof(ev)) == sizeof(ev)) {
process_input_event(&ev, &devices[i], &touch_ev);
}
}
}
}
printf("listen_touch_events 成功退出\n");
}
// 添加停止监听函数
void stop_touch_listener() {
listener_running = 0; // 设置停止标志
}
// 格式化时间戳为可读字符串
void format_timestamp(struct timespec ts, char *buffer, size_t buffer_size) {
time_t sec = ts.tv_sec;
struct tm *tm_info = localtime(&sec);
strftime(buffer, buffer_size, "%H:%M:%S", tm_info);
// 添加毫秒部分
char ms_buffer[10];
snprintf(ms_buffer, sizeof(ms_buffer), ".%03ld", ts.tv_nsec / 1000000);
strncat(buffer, ms_buffer, buffer_size - strlen(buffer) - 1);
}
// 示例回调函数
void print_touch_event(struct touch_event event) {
const char *event_types[] = {"按下", "移动", "释放"};
char timestamp[32];
format_timestamp(event.timestamp, timestamp, sizeof(timestamp));
printf("时间: %s\n", timestamp);
printf("设备: %s\n", event.device_name);
printf("路径: %s\n", event.device_path);
printf("设备ID: %d\n", event.device_id);
printf("事件: %s\n", event_types[event.event_type]);
printf("坐标: (%d, %d)\n", event.x, event.y);
printf("压力: %d\n", event.pressure);
if (event.touch_id >= 0) {
printf("触摸点ID: %d\n", event.touch_id);
}
printf("---\n");
}
// 初始化触摸事件监听
int init_touch_listener(touch_event_callback callback) {
event_callback = callback;
// 发现触摸设备
if (discover_touch_devices() <= 0) {
fprintf(stderr, "未找到触摸屏设备\n");
return -1;
}
printf("找到 %d 个触摸屏设备:\n", device_count);
for (int i = 0; i < device_count; i++) {
printf("%d: %s (%s)\n", i, devices[i].name, devices[i].path);
}
// 打开设备
if (open_touch_devices() < 0) {
fprintf(stderr, "无法打开触摸设备\n");
return -1;
}
return 0;
}
// 清理资源
void cleanup_touch_listener() {
close_touch_devices();
if (devices != NULL) {
free(devices);
devices = NULL;
}
device_count = 0;
}
// 获取当前活动的触摸设备列表
int get_active_touch_devices(struct touch_device **active_devices) {
if (device_count <= 0) {
return 0;
}
*active_devices = malloc(device_count * sizeof(struct touch_device));
if (!*active_devices) {
return -1;
}
memcpy(*active_devices, devices, device_count * sizeof(struct touch_device));
return device_count;
}
// // 主函数示例
// int main() {
// // 初始化触摸监听器
// if (init_touch_listener(print_touch_event) < 0) {
// return 1;
// }
// printf("开始监听触摸事件...\n");
// printf("按Ctrl+C退出\n\n");
// // 开始监听事件
// listen_touch_events();
// // 清理资源
// cleanup_touch_listener();
// return 0;
// }
-39
View File
@@ -1,39 +0,0 @@
#ifndef TOUCH_LISTEN
#define TOUCH_LISTEN
// 触摸屏设备信息结构体
struct touch_device {
int fd;
char name[256];
char path[256];
int min_x, max_x;
int min_y, max_y;
};
// 触摸事件信息结构体
struct touch_event {
int device_id; // 设备标识(可以使用设备路径的哈希值)
int x; // X坐标
int y; // Y坐标
int pressure; // 压力值
int touch_id; // 触摸点ID(用于多点触控)
int event_type; // 事件类型: 按下、移动、释放
char device_name[256]; // 设备名称
char device_path[256]; // 设备路径(如/dev/input/event1
struct timespec timestamp; // 事件时间戳
};
// 回调函数类型定义
typedef void (*touch_event_callback)(struct touch_event event);
// 监听触摸事件
void listen_touch_events();
// 初始化触摸事件监听
int init_touch_listener(touch_event_callback callback);
void stop_touch_listener();
#endif // !TOUCH_LISTEN
-866
View File
@@ -1,866 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <libudev.h>
#include <X11/Xatom.h> // 为XA_STRING, XA_FLOAT等预定义原子
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#define MAX_LINE_LENGTH 256
#define MAX_DEVICES 50
// 全局日志文件指针
FILE *log_file = NULL;
// 设备信息结构体
typedef struct {
int device_id; // 设备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];
int matched; // 是否已匹配成功
} InputDeviceInfo;
// 配置结构体
typedef struct {
char display_name[50];
char touchscreen_name[50];
char vid[10];
char pid[10];
char usb_path[100];
int matched; // 是否已匹配成功
} TouchConfig;
// 屏幕信息结构体
typedef struct {
char name[50];
int width;
int height;
int x;
int y;
char rotation; // 旋转方向: N(正常), L(左转), R(右转), I(翻转)
} ScreenInfo;
// 函数声明
void log_message(const char* format, ...);
int read_config(const char* filename, TouchConfig configs[], int max_configs);
int read_screen_info(const char* filename, ScreenInfo screens[], int max_screens);
int find_matching_device(TouchConfig config, InputDeviceInfo devices[], int device_count, int strict);
int parse_resolution_and_position(const char* str, int* width, int* height, int* x, int* y);
void calculate_virtual_desktop(ScreenInfo screens[], int screen_count, int *total_width, int *total_height);
void calculate_ctm(ScreenInfo screen, int total_width, int total_height, float matrix[9]);
int set_ctm(int device_id, float matrix[9]);
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] touch_set \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] touch_set \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 将设备节点路径转换为 sysfs 路径
char* devnode_to_syspath(const char* devnode) {
struct stat st;
if (stat(devnode, &st) != 0) {
return NULL;
}
// 获取主设备号和次设备号
unsigned int major = major(st.st_rdev);
unsigned int minor = minor(st.st_rdev);
// 构建 sysfs 路径
char* syspath = malloc(256);
snprintf(syspath, 256, "/sys/dev/char/%u:%u", major, minor);
return syspath;
}
// 获取输入设备信息的主要函数
int get_input_devices(InputDeviceInfo result[]) {
Display *display;
int ndevices;
XIDeviceInfo *devices;
struct udev *udev_ctx;
int count = 0;
// 打开X11显示连接
display = XOpenDisplay(NULL);
if (!display) {
log_message("无法打开X显示连接\n");
return 0;
}
// 初始化udev上下文
udev_ctx = udev_new();
if (!udev_ctx) {
log_message("无法创建udev上下文\n");
XCloseDisplay(display);
return 0;
}
// 获取X输入设备列表
devices = XIQueryDevice(display, XIAllDevices, &ndevices);
if (!devices) {
log_message( "无法查询X输入设备\n");
udev_unref(udev_ctx);
XCloseDisplay(display);
return 0;
}
// 遍历所有设备
for (int i = 0; i < ndevices; i++) {
XIDeviceInfo *dev = &devices[i];
// 只关注从设备(slave devices),排除主设备和虚拟设备
// if (dev->use != XISlavePointer && dev->use != XISlaveKeyboard) {
// continue;
// }
if(dev -> use != 3) continue;
char *device_node = NULL;
// 获取设备属性
int num_props = 0;
Atom *props = XIListProperties(display, dev->deviceid, &num_props);
// log_message("正在处理设备%d:%s(use=%d)\n", dev->deviceid, dev->name, dev->use);
if (props) {
for (int j = 0; j < num_props; j++) {
Atom prop = props[j];
char *prop_name = XGetAtomName(display, prop);
if (strcmp(prop_name, "Device Node") == 0) {
// 获取设备节点路径
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned char *prop_data = NULL;
if (XIGetProperty(display, dev->deviceid, prop, 0, 100, False,
AnyPropertyType, &actual_type, &actual_format,
&nitems, &bytes_after, &prop_data) == Success) {
// 使用 XInternAtom 获取字符串原子类型
Atom string_atom = XInternAtom(display, "STRING", False);
if (actual_type == string_atom && actual_format == 8) {
device_node = strdup((char*)prop_data);
}
// log_message(" |- Device Node = %s\n", device_node);
XFree(prop_data);
}
}
XFree(prop_name);
}
XFree(props);
}
// 如果找到设备节点,通过udev查询更多信息
char* syspath = devnode_to_syspath(device_node);
// log_message("Device syspath = %s\n", syspath);
struct udev_device *udev_dev = udev_device_new_from_syspath(udev_ctx, syspath);
// const char *name = udev_device_get_property_value(udev_dev, "NAME");
// 获取供应商ID
const char *vendor_id = udev_device_get_property_value(udev_dev, "ID_VENDOR_ID");
// 获取模型ID
const char *model_id = udev_device_get_property_value(udev_dev, "ID_MODEL_ID");
// 获取物理路径
const char *physical_path = udev_device_get_property_value(udev_dev, "ID_PATH");
// 检查是否是触摸屏设备
const char *is_touchscreen = udev_device_get_property_value(udev_dev, "ID_INPUT_TOUCHSCREEN");
// log_message(" |- Device info: vid=%s, pid=%s, path=%s, touch=%s\n", vendor_id, model_id, physical_path, 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);
// 初始化当前设备信息结构体
result[count].device_id = dev->deviceid;
result[count].matched = 0;
strncpy(result[count].name, dev->name, sizeof(result[count].name));
strncpy(result[count].vid, vendor_id, sizeof(result[count].vid));
strncpy(result[count].pid, model_id, sizeof(result[count].pid));
strncpy(result[count].usb_path, physical_path, sizeof(result[count].usb_path));
strncpy(result[count].device_node, device_node, sizeof(result[count].device_node));
for(int i=0;i<9;i++){
result[count].matrix[i] = 0;
}
// result[count].device_node = NULL;
// result[count].name = strdup(dev->name);
// result[count].device_node = strdup(device_node);
// result[count].vid = strdup(vendor_id);
// result[count].pid = strdup(model_id);
// result[count].usb_path = strdup(physical_path);
result[count].is_touchscreen = 1;
log_message("发现触摸屏设备 %s(id=%d), %s:%s, path=%s\n",result[count].name, result[count].device_id,
result[count].vid, result[count].pid,result[count].usb_path);
count++;
}
udev_device_unref(udev_dev);
}
// 释放资源
XIFreeDeviceInfo(devices);
udev_unref(udev_ctx);
XCloseDisplay(display);
return count;
}
// 读取配置文件
int read_config(const char* filename, TouchConfig configs[], int max_configs) {
FILE* file = fopen(filename, "r");
if (!file) {
log_message("无法打开配置文件: %s\n", filename);
return -1;
}
char line[MAX_LINE_LENGTH];
int count = 0;
while (fgets(line, sizeof(line), file) && count < max_configs) {
// 移除换行符
line[strcspn(line, "\n")] = 0;
// 跳过空行和注释
if (line[0] == '\0' || line[0] == '#') continue;
// 解析行
char* tokens[5];
char* token = strtok(line, "|");
int i = 0;
while (token && i < 5) {
tokens[i++] = token;
token = strtok(NULL, "|");
}
if (i == 5) {
strncpy(configs[count].display_name, tokens[0], sizeof(configs[count].display_name));
strncpy(configs[count].touchscreen_name, tokens[1], sizeof(configs[count].touchscreen_name));
strncpy(configs[count].vid, tokens[2], sizeof(configs[count].vid));
strncpy(configs[count].pid, tokens[3], sizeof(configs[count].pid));
strncpy(configs[count].usb_path, tokens[4], sizeof(configs[count].usb_path));
configs[count].matched = 0;
count++;
} else {
log_message("无效的配置行: %s\n", line);
}
}
fclose(file);
return count;
}
// 读取屏幕信息
int read_screen_info(const char* filename, ScreenInfo screens[], int max_screens) {
FILE* file = fopen(filename, "r");
if (!file) {
log_message("无法打开屏幕信息文件: %s\n", filename);
return -1;
}
char line[MAX_LINE_LENGTH];
int count = 0;
while (fgets(line, sizeof(line), file) && count < max_screens) {
// 移除换行符
line[strcspn(line, "\n")] = 0;
// 跳过空行和注释
if (line[0] == '\0' || line[0] == '#') continue;
// 解析显示器名
char* name = strtok(line, "|");
if (!name) continue;
// 解析分辨率
char* resolution = strtok(NULL, "|");
if (!resolution) continue;
// 解析坐标
char* position = strtok(NULL, "|");
if (!position) continue;
// 解析旋转方向(可选,默认为N
char* rotation_str = strtok(NULL, "|");
char rotation = 'N'; // 默认正常方向
if (rotation_str) {
rotation = toupper(rotation_str[0]);
if (rotation != 'N' && rotation != 'L' && rotation != 'R' && rotation != 'I') {
log_message("无效的旋转方向: %s,使用默认值N\n", rotation_str);
rotation = 'N';
}
}
int width, height, x, y;
// 解析分辨率 (格式: 1920x1080)
if (sscanf(resolution, "%dx%d", &width, &height) != 2) {
log_message("无效的分辨率格式: %s\n", resolution);
continue;
}
// 解析坐标 (格式: 1920x0 或 1920,0)
if (sscanf(position, "%d%*[x,]%d", &x, &y) != 2) {
log_message("无效的坐标格式: %s\n", position);
continue;
}
strncpy(screens[count].name, name, sizeof(screens[count].name));
screens[count].width = width;
screens[count].height = height;
screens[count].x = x;
screens[count].y = y;
screens[count].rotation = rotation;
count++;
log_message("屏幕 %s: 分辨率 %dx%d, 位置 (%d,%d), 旋转方向 %c\n",
name, width, height, x, y, rotation);
}
fclose(file);
return count;
}
// 辅助函数:去除字符串中的空格
void remove_spaces(char* str) {
char* i = str;
char* j = str;
while (*j != '\0') {
*i = *j++;
if (*i != ' ') {
i++;
}
}
*i = '\0';
}
// 查找匹配的设备
int find_matching_device(TouchConfig config, InputDeviceInfo devices[], int device_count, int strict) {
int match_index = -1;
int match_count = 0;
int match_indices[MAX_DEVICES];
log_message("查找匹配设备: %s (VID:%s PID:%s 路径:%s)\n",
config.touchscreen_name, config.vid, config.pid, config.usb_path);
// 第一步:按设备名完全匹配
for (int i = 0; i < device_count; i++) {
// 跳过已匹配的设备
if (devices[i].matched) {
continue;
}
char device_name_no_space[256];
char config_name_no_space[256];
strcpy(device_name_no_space, devices[i].name);
strcpy(config_name_no_space, config.touchscreen_name);
remove_spaces(device_name_no_space);
remove_spaces(config_name_no_space);
if (strcmp(device_name_no_space, config_name_no_space) == 0) {
match_indices[match_count++] = i;
log_message("名称完全匹配(去除空格): %s -> %s\n", devices[i].name, device_name_no_space);
}
}
// 如果只有一个匹配项,直接返回
if (match_count == 1) {
return match_indices[0];
}
// 如果有多个匹配项,按vid:pid进一步匹配 // 严格模式
if (match_count > 1 && strict) {
// match_count = 0;
log_message("找到多个名称匹配项,按VID:PID进一步筛选\n");
int vid_pid_match_count = 0;
int vid_pid_match_indices[MAX_DEVICES];
for (int i = 0; i < match_count; i++) {
int idx = match_indices[i];
if (strcasecmp(devices[idx].vid, config.vid) == 0 &&
strcasecmp(devices[idx].pid, config.pid) == 0) {
vid_pid_match_indices[vid_pid_match_count++] = idx;
log_message("VID:PID匹配: %s (VID:%s PID:%s)\n",
devices[idx].name, devices[idx].vid, devices[idx].pid);
}
}
if (vid_pid_match_count == 1) {
return vid_pid_match_indices[0];
}
// 严格模式下,多个pid、vid匹配,按usb路径匹配
if (vid_pid_match_count > 1 && config.usb_path[0] != '\0') {
log_message("找到多个VID:PID匹配项,按USB路径进一步筛选\n");
for (int i = 0; i < vid_pid_match_count; i++) {
int idx = vid_pid_match_indices[i];
if (strcmp(devices[idx].usb_path, config.usb_path) == 0) {
log_message("USB路径匹配: %s\n", devices[idx].usb_path);
return idx; // 返回第一个匹配的
}
}
}
// 如果还是无法确定,则失败
log_message("都无法匹配,返回失败:-1\n");
return -1;
}
// 宽松匹配:要求名称和id都匹配的上
// 程序能跑到这里,说明有多个名称匹配了
if (!strict) {
log_message("未找到名称匹配项,尝试VID:PID匹配\n");
for (int i = 0; i < device_count; i++) {
// 跳过已匹配的设备
if (devices[i].matched) {
continue;
}
if (strcasecmp(devices[i].vid, config.vid) == 0 &&
strcasecmp(devices[i].pid, config.pid) == 0) {
match_indices[match_count++] = i;
log_message("VID:PID匹配: %s (VID:%s PID:%s)\n",
devices[i].name, devices[i].vid, devices[i].pid);
}
}
if (match_count == 1) {
return match_indices[0];
}
// 如果还是无法确定,返回第一个匹配项
if (match_count > 0) {
log_message("仍有多个匹配项,返回失败\n");
return -1;
}
}
log_message("未找到匹配设备\n");
return -1;
}
/* ==================================
= 矩阵坐标计算相关代码 =
======================================*/
// 计算虚拟桌面大小
void calculate_virtual_desktop(ScreenInfo screens[], int screen_count, int *total_width, int *total_height) {
int min_x = 0, min_y = 0;
int max_x = 0, max_y = 0;
for (int i = 0; i < screen_count; i++) {
int screen_right = screens[i].x + screens[i].width;
int screen_bottom = screens[i].y + screens[i].height;
if (screens[i].x < min_x) min_x = screens[i].x;
if (screens[i].y < min_y) min_y = screens[i].y;
if (screen_right > max_x) max_x = screen_right;
if (screen_bottom > max_y) max_y = screen_bottom;
}
*total_width = max_x - min_x;
*total_height = max_y - min_y;
log_message("虚拟桌面边界: x(%d 到 %d), y(%d 到 %d)\n",
min_x, max_x, min_y, max_y);
}
// 计算坐标转换矩阵的函数(考虑旋转方向)
void calculate_ctm(ScreenInfo screen, int total_width, int total_height, float matrix[9]) {
/*
* 屏幕旋转的计算
*
* 在逻辑处理中,系统需要将屏幕按相同方向旋转(逆时针为正向)
* 然后将图形向左平移回正坐标
* 最后进行传统缩放。
*
* 旋转矩阵
* cosθ -sinθ 0
* sinθ cosθ 0
* 0 0 1
*
* “向左旋转”后,屏幕需要逆时针旋转90度才能正确显示,同样的系统也是逆时针90度。
* 由此可以推算出90、180、270 三个旋转的矩阵
*
*/
float sx = (float)screen.width / total_width;
float sy = (float)screen.height / total_height;
float dx = (float)screen.x / total_width;
float dy = (float)screen.y / total_height;
// 根据旋转方向调整矩阵
switch (screen.rotation) {
case 'L': // 左转90度
matrix[0] = 0; matrix[1] = -sx; matrix[2] = sx+dx;
matrix[3] = sy; matrix[4] = 0.0f; matrix[5] = dy;
matrix[6] = 0.0f; matrix[7] = 0.0f; matrix[8] = 1.0f;
break;
case 'R': // 右转90
matrix[0] = 0.0f; matrix[1] = sx; matrix[2] = dx;
matrix[3] = -sy; matrix[4] = 0.0f; matrix[5] = sy + dy;
matrix[6] = 0.0f; matrix[7] = 0.0f; matrix[8] = 1.0f;
break;
case 'I': // 翻转180度
matrix[0] = -sx; matrix[1] = 0.0f; matrix[2] = sx + dx;
matrix[3] = 0.0f; matrix[4] = -sy; matrix[5] = sy + dy;
matrix[6] = 0.0f; matrix[7] = 0.0f; matrix[8] = 1.0f;
break;
case 'N': // 正常方向
default:
matrix[0] = sx; matrix[1] = 0.0f; matrix[2] = dx;
matrix[3] = 0.0f; matrix[4] = sy; matrix[5] = dy;
matrix[6] = 0.0f; matrix[7] = 0.0f; matrix[8] = 1.0f;
break;
}
}
// 设置设备的坐标转换矩阵
int set_ctm(int device_id, float matrix[9]) {
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "无法打开X显示连接\n");
return 0;
}
// 创建ATOM属性
Atom prop = XInternAtom(display, "Coordinate Transformation Matrix", False);
if (!prop) {
fprintf(stderr, "无法创建Coordinate Transformation Matrix属性\n");
XCloseDisplay(display);
return 0;
}
// 检查设备是否支持此属性
int num_props = 0;
Atom *props = XIListProperties(display, device_id, &num_props);
int supports_matrix = 0;
if (props) {
for (int i = 0; i < num_props; i++) {
char *prop_name = XGetAtomName(display, props[i]);
if (strcmp(prop_name, "Coordinate Transformation Matrix") == 0) {
supports_matrix = 1;
XFree(prop_name);
break;
}
XFree(prop_name);
}
XFree(props);
}
if (!supports_matrix) {
fprintf(stderr, "设备 %d 不支持坐标转换矩阵属性\n", device_id);
XCloseDisplay(display);
return 0;
}
// 创建FLOAT类型的原子
Atom float_atom = XInternAtom(display, "FLOAT", False);
if (!float_atom) {
fprintf(stderr, "无法创建FLOAT类型原子\n");
XCloseDisplay(display);
return 0;
}
// 设置属性值
XIChangeProperty(display, device_id, prop, float_atom, 32, PropModeReplace,
(unsigned char*)matrix, 9);
XFlush(display);
XCloseDisplay(display);
return 1;
}
// 示例使用
int main(int argc, char *argv[]) {
char *output_file_path = NULL;
// 处理命令行参数
if (argc > 1) {
output_file_path = argv[1];
log_message("输出文件路径: %s\n", output_file_path);
}
// 打开日志文件
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("警告: 无法打开日志文件 /opt/ktouch/sub_modules.log,仅输出到控制台\n");
}
log_message("开始触摸屏配置...\n");
TouchConfig configs[MAX_DEVICES];
ScreenInfo screens[MAX_DEVICES];
InputDeviceInfo devices[MAX_DEVICES];
int config_count = read_config("/opt/ktouch/config", configs, MAX_DEVICES);
if (config_count < 0) {
log_message("读取配置文件失败\n");
if (log_file) fclose(log_file);
return 1;
}
log_message("读取了 %d 个配置项\n", config_count);
int screen_count = read_screen_info("/tmp/ktouch/screen.txt", screens, MAX_DEVICES);
if (screen_count < 0) {
log_message("读取屏幕信息文件失败\n");
if (log_file) fclose(log_file);
return 1;
}
log_message("读取了 %d 个屏幕信息\n", screen_count);
// 计算虚拟桌面大小
int total_width, total_height;
calculate_virtual_desktop(screens, screen_count, &total_width, &total_height);
log_message("虚拟桌面大小: %dx%d\n", total_width, total_height);
int device_count = 0;
device_count = get_input_devices(devices);
log_message("找到了 %d 个触摸设备\n", device_count);
// 输出所有找到的设备信息
for (int i = 0; i < device_count; i++) {
log_message("设备 %d: %s (VID:%s PID:%s 路径:%s XInput ID:%d)\n",
i, devices[i].name, devices[i].vid, devices[i].pid,
devices[i].usb_path, devices[i].device_id);
}
// 多轮匹配:首先严格匹配,然后宽松匹配,直到无法匹配
int round = 1;
int matched_in_round = 1; // 初始化为1以进入循环
while (matched_in_round > 0) {
matched_in_round = 0;
log_message("开始第 %d 轮匹配\n", round);
// 确定匹配严格程度
int strict = (round == 1); // 第一轮严格匹配,后续宽松匹配
// 对每个未匹配的配置项查找匹配的设备
for (int i = 0; i < config_count; i++) {
if (configs[i].matched) {
continue; // 跳过已匹配的配置项
}
log_message("处理配置: %s|%s|%s|%s|%s\n",
configs[i].display_name, configs[i].touchscreen_name,
configs[i].vid, configs[i].pid, configs[i].usb_path);
int device_index = find_matching_device(configs[i], devices, device_count, strict);
if (device_index == -1) {
continue; // 未找到匹配设备,继续下一个配置项
}
log_message("匹配设备: %s (VID:%s PID:%s 路径:%s XInput ID:%d)\n",
devices[device_index].name, devices[device_index].vid,
devices[device_index].pid, devices[device_index].usb_path,
devices[device_index].device_id);
// 标记设备和配置项为已匹配
devices[device_index].matched = 1;
configs[i].matched = 1;
matched_in_round++;
// 检查XInput设备ID是否有效
if (devices[device_index].device_id == -1) {
log_message("错误: 设备 %s 没有有效的XInput ID\n", devices[device_index].name);
continue;
}
// 查找对应的屏幕信息
ScreenInfo* matched_screen = NULL;
for (int j = 0; j < screen_count; j++) {
if (strcmp(screens[j].name, configs[i].display_name) == 0) {
matched_screen = &screens[j];
break;
}
}
if (matched_screen == NULL) {
log_message("未找到 %s 对应的屏幕, 将使用第一屏\n", configs[i].display_name);
// 未找到屏幕,则返回第一个屏幕
matched_screen = &screens[0];
}
log_message("匹配屏幕: %s %dx%d 位置(%d,%d) 旋转方向 %c\n",
matched_screen->name, matched_screen->width,
matched_screen->height, matched_screen->x, matched_screen->y,
matched_screen->rotation);
// 计算并设置矩阵
float matrix[9]={0};
calculate_ctm(*matched_screen, total_width, total_height, matrix);
log_message("计算矩阵: [%f, %f, %f, %f, %f, %f, %f, %f, %f]\n",
matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5],
matrix[6], matrix[7], matrix[8]);
set_ctm(devices[device_index].device_id, matrix);
memcpy(devices[device_index].matrix, matrix, sizeof(float) * 9);
log_message("已配置 %s 为 %s 的触摸屏\n",
devices[device_index].name, configs[i].display_name);
}
log_message("第 %d 轮匹配完成,匹配了 %d 个设备\n", round, matched_in_round);
round++;
}
// 保存到文件
if (output_file_path) {
int max_retries = 3;
int retry_delay = 1; // 秒
FILE *output_file = NULL;
for (int retry = 0; retry < max_retries; retry++) {
output_file = fopen(output_file_path, "w");
if (output_file) {
break;
}
log_message("无法打开输出文件 %s (尝试 %d/%d)%d秒后重试...\n",
output_file_path, retry + 1, max_retries, retry_delay);
sleep(retry_delay);
}
if (output_file) {
int wcount = 0;
for (int i = 0; i < device_count; i++) {
if(devices[i].matched == 0){
continue;
}
wcount++;
fprintf(output_file, "%d|%s|%s|%s:%s|%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f\n",
devices[i].device_id,
devices[i].name,
devices[i].device_node,
devices[i].vid,
devices[i].pid,
devices[i].matrix[0], devices[i].matrix[1], devices[i].matrix[2],
devices[i].matrix[3], devices[i].matrix[4], devices[i].matrix[5],
devices[i].matrix[6], devices[i].matrix[7], devices[i].matrix[8]);
}
fclose(output_file);
log_message("已成功写入 %d 条记录到文件 %s\n", wcount, output_file_path);
} else {
log_message("经过 %d 次尝试后仍无法打开输出文件 %s,放弃写入\n",
max_retries, output_file_path);
}
}
// 输出未匹配的设备信息
int unmatched_devices = 0;
for (int i = 0; i < device_count; i++) {
if (!devices[i].matched) {
unmatched_devices++;
log_message("警告: 设备 %s (VID:%s PID:%s) 未匹配到任何配置\n",
devices[i].name, devices[i].vid, devices[i].pid);
}
}
// 输出未匹配的配置信息
int unmatched_configs = 0;
for (int i = 0; i < config_count; i++) {
if (!configs[i].matched) {
unmatched_configs++;
log_message("警告: 配置 %s 未匹配到任何设备\n", configs[i].touchscreen_name);
}
}
log_message("触摸屏配置完成,%d 个设备未匹配,%d 个配置未匹配\n",
unmatched_devices, unmatched_configs);
if (log_file) {
fclose(log_file);
}
return 0;
}
-771
View File
@@ -1,771 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <libudev.h>
#include <X11/Xatom.h> // 为XA_STRING, XA_FLOAT等预定义原子
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#define MAX_LINE_LENGTH 256
#define MAX_DEVICES 50
// 全局日志文件指针
FILE *log_file = NULL;
// 设备信息结构体
typedef struct {
int device_id; // 设备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];
int match_success;
} InputDeviceInfo;
// 配置结构体
typedef struct {
char display_name[50];
char touchscreen_name[50];
char vid[10];
char pid[10];
char usb_path[100];
} TouchConfig;
// 屏幕信息结构体
typedef struct {
char name[50];
int width;
int height;
int x;
int y;
} ScreenInfo;
// 函数声明
void log_message(const char* format, ...);
int read_config(const char* filename, TouchConfig configs[], int max_configs);
int read_screen_info(const char* filename, ScreenInfo screens[], int max_screens);
int find_matching_device(TouchConfig config, InputDeviceInfo devices[], int device_count);
int parse_resolution_and_position(const char* str, int* width, int* height, int* x, int* y);
void calculate_virtual_desktop(ScreenInfo screens[], int screen_count, int *total_width, int *total_height);
void calculate_ctm(ScreenInfo screen, int total_width, int total_height, float matrix[9]);
int set_ctm(int device_id, float matrix[9]);
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] touch_set \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] touch_set \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 将设备节点路径转换为 sysfs 路径
char* devnode_to_syspath(const char* devnode) {
struct stat st;
if (stat(devnode, &st) != 0) {
return NULL;
}
// 获取主设备号和次设备号
unsigned int major = major(st.st_rdev);
unsigned int minor = minor(st.st_rdev);
// 构建 sysfs 路径
char* syspath = malloc(256);
snprintf(syspath, 256, "/sys/dev/char/%u:%u", major, minor);
return syspath;
}
// 获取输入设备信息的主要函数
int get_input_devices(InputDeviceInfo result[]) {
Display *display;
int ndevices;
XIDeviceInfo *devices;
struct udev *udev_ctx;
int count = 0;
// 打开X11显示连接
display = XOpenDisplay(NULL);
if (!display) {
log_message("无法打开X显示连接\n");
return 0;
}
// 初始化udev上下文
udev_ctx = udev_new();
if (!udev_ctx) {
log_message("无法创建udev上下文\n");
XCloseDisplay(display);
return 0;
}
// 获取X输入设备列表
devices = XIQueryDevice(display, XIAllDevices, &ndevices);
if (!devices) {
log_message( "无法查询X输入设备\n");
udev_unref(udev_ctx);
XCloseDisplay(display);
return 0;
}
// 遍历所有设备
for (int i = 0; i < ndevices; i++) {
XIDeviceInfo *dev = &devices[i];
// 只关注从设备(slave devices),排除主设备和虚拟设备
// if (dev->use != XISlavePointer && dev->use != XISlaveKeyboard) {
// continue;
// }
if(dev -> use != 3) continue;
char *device_node = NULL;
// 获取设备属性
int num_props = 0;
Atom *props = XIListProperties(display, dev->deviceid, &num_props);
// log_message("正在处理设备%d:%s(use=%d)\n", dev->deviceid, dev->name, dev->use);
if (props) {
for (int j = 0; j < num_props; j++) {
Atom prop = props[j];
char *prop_name = XGetAtomName(display, prop);
if (strcmp(prop_name, "Device Node") == 0) {
// 获取设备节点路径
Atom actual_type;
int actual_format;
unsigned long nitems, bytes_after;
unsigned char *prop_data = NULL;
if (XIGetProperty(display, dev->deviceid, prop, 0, 100, False,
AnyPropertyType, &actual_type, &actual_format,
&nitems, &bytes_after, &prop_data) == Success) {
// 使用 XInternAtom 获取字符串原子类型
Atom string_atom = XInternAtom(display, "STRING", False);
if (actual_type == string_atom && actual_format == 8) {
device_node = strdup((char*)prop_data);
}
// log_message(" |- Device Node = %s\n", device_node);
XFree(prop_data);
}
}
XFree(prop_name);
}
XFree(props);
}
// 如果找到设备节点,通过udev查询更多信息
char* syspath = devnode_to_syspath(device_node);
// log_message("Device syspath = %s\n", syspath);
struct udev_device *udev_dev = udev_device_new_from_syspath(udev_ctx, syspath);
// const char *name = udev_device_get_property_value(udev_dev, "NAME");
// 获取供应商ID
const char *vendor_id = udev_device_get_property_value(udev_dev, "ID_VENDOR_ID");
// 获取模型ID
const char *model_id = udev_device_get_property_value(udev_dev, "ID_MODEL_ID");
// 获取物理路径
const char *physical_path = udev_device_get_property_value(udev_dev, "ID_PATH");
// 检查是否是触摸屏设备
const char *is_touchscreen = udev_device_get_property_value(udev_dev, "ID_INPUT_TOUCHSCREEN");
// log_message(" |- Device info: vid=%s, pid=%s, path=%s, touch=%s\n", vendor_id, model_id, physical_path, 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);
// 初始化当前设备信息结构体
result[count].device_id = dev->deviceid;
result[count].match_success = 0;
strncpy(result[count].name, dev->name, sizeof(result[count].name));
strncpy(result[count].vid, vendor_id, sizeof(result[count].vid));
strncpy(result[count].pid, model_id, sizeof(result[count].pid));
strncpy(result[count].usb_path, physical_path, sizeof(result[count].usb_path));
strncpy(result[count].device_node, device_node, sizeof(result[count].device_node));
for(int i=0;i<9;i++){
result[count].matrix[i] = 0;
}
// result[count].device_node = NULL;
// result[count].name = strdup(dev->name);
// result[count].device_node = strdup(device_node);
// result[count].vid = strdup(vendor_id);
// result[count].pid = strdup(model_id);
// result[count].usb_path = strdup(physical_path);
result[count].is_touchscreen = 1;
log_message("发现触摸屏设备 %s(id=%d), %s:%s, path=%s\n",result[count].name, result[count].device_id,
result[count].vid, result[count].pid,result[count].usb_path);
count++;
}
udev_device_unref(udev_dev);
}
// 释放资源
XIFreeDeviceInfo(devices);
udev_unref(udev_ctx);
XCloseDisplay(display);
return count;
}
// 读取配置文件
int read_config(const char* filename, TouchConfig configs[], int max_configs) {
FILE* file = fopen(filename, "r");
if (!file) {
log_message("无法打开配置文件: %s\n", filename);
return -1;
}
char line[MAX_LINE_LENGTH];
int count = 0;
while (fgets(line, sizeof(line), file) && count < max_configs) {
// 移除换行符
line[strcspn(line, "\n")] = 0;
// 跳过空行和注释
if (line[0] == '\0' || line[0] == '#') continue;
// 解析行
char* tokens[5];
char* token = strtok(line, "|");
int i = 0;
while (token && i < 5) {
tokens[i++] = token;
token = strtok(NULL, "|");
}
if (i == 5) {
strncpy(configs[count].display_name, tokens[0], sizeof(configs[count].display_name));
strncpy(configs[count].touchscreen_name, tokens[1], sizeof(configs[count].touchscreen_name));
strncpy(configs[count].vid, tokens[2], sizeof(configs[count].vid));
strncpy(configs[count].pid, tokens[3], sizeof(configs[count].pid));
strncpy(configs[count].usb_path, tokens[4], sizeof(configs[count].usb_path));
count++;
} else {
log_message("无效的配置行: %s\n", line);
}
}
fclose(file);
return count;
}
// 读取屏幕信息
int read_screen_info(const char* filename, ScreenInfo screens[], int max_screens) {
FILE* file = fopen(filename, "r");
if (!file) {
log_message("无法打开屏幕信息文件: %s\n", filename);
return -1;
}
char line[MAX_LINE_LENGTH];
int count = 0;
while (fgets(line, sizeof(line), file) && count < max_screens) {
// 移除换行符
line[strcspn(line, "\n")] = 0;
// 跳过空行和注释
if (line[0] == '\0' || line[0] == '#') continue;
// 解析显示器名
char* name = strtok(line, "|");
if (!name) continue;
// 解析分辨率
char* resolution = strtok(NULL, "|");
if (!resolution) continue;
// 解析坐标
char* position = strtok(NULL, "|");
if (!position) continue;
int width, height, x, y;
// 解析分辨率 (格式: 1920x1080)
if (sscanf(resolution, "%dx%d", &width, &height) != 2) {
log_message("无效的分辨率格式: %s\n", resolution);
continue;
}
// 解析坐标 (格式: 1920x0 或 1920,0)
if (sscanf(position, "%d%*[x,]%d", &x, &y) != 2) {
log_message("无效的坐标格式: %s\n", position);
continue;
}
strncpy(screens[count].name, name, sizeof(screens[count].name));
screens[count].width = width;
screens[count].height = height;
screens[count].x = x;
screens[count].y = y;
count++;
log_message("屏幕 %s: 分辨率 %dx%d, 位置 (%d,%d)\n", name, width, height, x, y);
}
fclose(file);
return count;
}
// 辅助函数:去除字符串中的空格
void remove_spaces(char* str) {
char* i = str;
char* j = str;
while (*j != '\0') {
*i = *j++;
if (*i != ' ') {
i++;
}
}
*i = '\0';
}
// 查找匹配的设备
int find_matching_device(TouchConfig config, InputDeviceInfo devices[], int device_count) {
int match_index = -1;
int match_count = 0;
int match_indices[MAX_DEVICES];
log_message("查找匹配设备: %s (VID:%s PID:%s 路径:%s)\n",
config.touchscreen_name, config.vid, config.pid, config.usb_path);
// 第一步:按设备名完全匹配
for (int i = 0; i < device_count; i++) {
// if (strcmp(devices[i].name, config.touchscreen_name) == 0) {
// match_indices[match_count++] = i;
// log_message("名称完全匹配: %s\n", devices[i].name);
// }
char device_name_no_space[256];
char config_name_no_space[256];
strcpy(device_name_no_space, devices[i].name);
strcpy(config_name_no_space, config.touchscreen_name);
remove_spaces(device_name_no_space);
remove_spaces(config_name_no_space);
if (strcmp(device_name_no_space, config_name_no_space) == 0) {
match_indices[match_count++] = i;
log_message("名称完全匹配(去除空格): %s -> %s\n", devices[i].name, device_name_no_space);
}
}
// 如果只有一个匹配项,直接返回
if (match_count == 1) {
return match_indices[0];
}
// 如果有多个匹配项,按vid:pid进一步匹配
if (match_count > 1) {
log_message("找到多个名称匹配项,按VID:PID进一步筛选\n");
int vid_pid_match_count = 0;
int vid_pid_match_indices[MAX_DEVICES];
for (int i = 0; i < match_count; i++) {
int idx = match_indices[i];
if (strcasecmp(devices[idx].vid, config.vid) == 0 &&
strcasecmp(devices[idx].pid, config.pid) == 0) {
vid_pid_match_indices[vid_pid_match_count++] = idx;
log_message("VID:PID匹配: %s (VID:%s PID:%s)\n",
devices[idx].name, devices[idx].vid, devices[idx].pid);
}
}
if (vid_pid_match_count == 1) {
return vid_pid_match_indices[0];
}
// 如果还有多个匹配项,按usb路径匹配
if (vid_pid_match_count > 1 && config.usb_path[0] != '\0') {
log_message("找到多个VID:PID匹配项,按USB路径进一步筛选\n");
for (int i = 0; i < vid_pid_match_count; i++) {
int idx = vid_pid_match_indices[i];
if (strcmp(devices[idx].usb_path, config.usb_path) == 0) {
log_message("USB路径匹配: %s\n", devices[idx].usb_path);
return idx; // 返回第一个匹配的
}
}
}
// 如果还是无法确定,返回第一个匹配项
if (vid_pid_match_count > 0) {
log_message("仍有多个匹配项,返回第一个\n");
return vid_pid_match_indices[0];
}
}
// // 如果没有名称匹配项,尝试按vid:pid匹配
// log_message("未找到名称匹配项,尝试VID:PID匹配\n");
// for (int i = 0; i < device_count; i++) {
// if (strcasecmp(devices[i].vid, config.vid) == 0 &&
// strcasecmp(devices[i].pid, config.pid) == 0) {
// match_indices[match_count++] = i;
// log_message("VID:PID匹配: %s (VID:%s PID:%s)\n",
// devices[i].name, devices[i].vid, devices[i].pid);
// }
// }
// if (match_count == 1) {
// return match_indices[0];
// }
// // 如果还有多个匹配项,按usb路径匹配
// if (match_count > 1 && config.usb_path[0] != '\0') {
// log_message("找到多个VID:PID匹配项,按USB路径进一步筛选\n");
// for (int i = 0; i < match_count; i++) {
// int idx = match_indices[i];
// if (strcmp(devices[idx].usb_path, config.usb_path) == 0) {
// log_message("USB路径匹配: %s\n", devices[idx].usb_path);
// return idx;
// }
// }
// }
// // 如果还是无法确定,返回第一个匹配项或-1
// if (match_count > 0) {
// log_message("仍有多个匹配项,返回第一个\n");
// return match_indices[0];
// }
log_message("未找到匹配设备\n");
return -1;
}
/* ==================================
= 矩阵坐标计算相关代码 =
======================================*/
// 计算虚拟桌面大小
void calculate_virtual_desktop(ScreenInfo screens[], int screen_count, int *total_width, int *total_height) {
int min_x = 0, min_y = 0;
int max_x = 0, max_y = 0;
for (int i = 0; i < screen_count; i++) {
int screen_right = screens[i].x + screens[i].width;
int screen_bottom = screens[i].y + screens[i].height;
if (screens[i].x < min_x) min_x = screens[i].x;
if (screens[i].y < min_y) min_y = screens[i].y;
if (screen_right > max_x) max_x = screen_right;
if (screen_bottom > max_y) max_y = screen_bottom;
}
*total_width = max_x - min_x;
*total_height = max_y - min_y;
log_message("虚拟桌面边界: x(%d 到 %d), y(%d 到 %d)\n",
min_x, max_x, min_y, max_y);
}
// 计算坐标转换矩阵的函数(预留算法接口)
void calculate_ctm(ScreenInfo screen, int total_width, int total_height, float matrix[9]) {
float scale_x = (float)screen.width / total_width;
float scale_y = (float)screen.height / total_height;
float offset_x = (float)screen.x / total_width;
float offset_y = (float)screen.y / total_height;
// 设置变换矩阵
matrix[0] = scale_x; matrix[1] = 0.0f; matrix[2] = offset_x;
matrix[3] = 0.0f; matrix[4] = scale_y; matrix[5] = offset_y;
matrix[6] = 0.0f; matrix[7] = 0.0f; matrix[8] = 1.0f;
}
// 设置设备的坐标转换矩阵
int set_ctm(int device_id, float matrix[9]) {
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "无法打开X显示连接\n");
return 0;
}
// 创建ATOM属性
Atom prop = XInternAtom(display, "Coordinate Transformation Matrix", False);
if (!prop) {
fprintf(stderr, "无法创建Coordinate Transformation Matrix属性\n");
XCloseDisplay(display);
return 0;
}
// 检查设备是否支持此属性
int num_props = 0;
Atom *props = XIListProperties(display, device_id, &num_props);
int supports_matrix = 0;
if (props) {
for (int i = 0; i < num_props; i++) {
char *prop_name = XGetAtomName(display, props[i]);
if (strcmp(prop_name, "Coordinate Transformation Matrix") == 0) {
supports_matrix = 1;
XFree(prop_name);
break;
}
XFree(prop_name);
}
XFree(props);
}
if (!supports_matrix) {
fprintf(stderr, "设备 %d 不支持坐标转换矩阵属性\n", device_id);
XCloseDisplay(display);
return 0;
}
// 创建FLOAT类型的原子
Atom float_atom = XInternAtom(display, "FLOAT", False);
if (!float_atom) {
fprintf(stderr, "无法创建FLOAT类型原子\n");
XCloseDisplay(display);
return 0;
}
// 设置属性值
XIChangeProperty(display, device_id, prop, float_atom, 32, PropModeReplace,
(unsigned char*)matrix, 9);
XFlush(display);
XCloseDisplay(display);
return 1;
}
// // 释放设备信息结构体内存
// void free_input_devices(struct InputDeviceInfo *devices, int num_devices) {
// for (int i = 0; i < num_devices; i++) {
// free(devices[i].name);
// free(devices[i].device_node);
// free(devices[i].vid);
// free(devices[i].pid);
// free(devices[i].usb_path);
// }
// free(devices);
// }
// 示例使用
int main(int argc, char *argv[]) {
char *output_file_path = NULL;
// 处理命令行参数
if (argc > 1) {
output_file_path = argv[1];
log_message("输出文件路径: %s\n", output_file_path);
}
// 打开日志文件
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("警告: 无法打开日志文件 /opt/ktouch/sub_modules.log,仅输出到控制台\n");
}
log_message("开始触摸屏配置...\n");
TouchConfig configs[MAX_DEVICES];
ScreenInfo screens[MAX_DEVICES];
InputDeviceInfo devices[MAX_DEVICES];
int config_count = read_config("/opt/ktouch/config", configs, MAX_DEVICES);
if (config_count < 0) {
log_message("读取配置文件失败\n");
if (log_file) fclose(log_file);
return 1;
}
log_message("读取了 %d 个配置项\n", config_count);
int screen_count = read_screen_info("/tmp/ktouch/screen.txt", screens, MAX_DEVICES);
if (screen_count < 0) {
log_message("读取屏幕信息文件失败\n");
if (log_file) fclose(log_file);
return 1;
}
log_message("读取了 %d 个屏幕信息\n", screen_count);
// 计算虚拟桌面大小
int total_width, total_height;
calculate_virtual_desktop(screens, screen_count, &total_width, &total_height);
log_message("虚拟桌面大小: %dx%d\n", total_width, total_height);
int device_count = 0;
device_count = get_input_devices(devices);
log_message("找到了 %d 个触摸设备\n", device_count);
// 输出所有找到的设备信息
for (int i = 0; i < device_count; i++) {
log_message("设备 %d: %s (VID:%s PID:%s 路径:%s XInput ID:%d)\n",
i, devices[i].name, devices[i].vid, devices[i].pid,
devices[i].usb_path, devices[i].device_id);
}
// 对每个配置项查找匹配的设备
for (int i = 0; i < config_count; i++) {
log_message("处理配置: %s|%s|%s|%s|%s\n",
configs[i].display_name, configs[i].touchscreen_name,
configs[i].vid, configs[i].pid, configs[i].usb_path);
int device_index = find_matching_device(configs[i], devices, device_count);
if (device_index == -1) {
log_message("未找到匹配 %s 的设备\n", configs[i].touchscreen_name);
continue;
}
log_message("匹配设备: %s (VID:%s PID:%s 路径:%s XInput ID:%d)\n",
devices[device_index].name, devices[device_index].vid,
devices[device_index].pid, devices[device_index].usb_path,
devices[device_index].device_id);
devices[device_index].match_success = 1;
// 检查XInput设备ID是否有效
if (devices[device_index].device_id == -1) {
log_message("错误: 设备 %s 没有有效的XInput ID\n", devices[device_index].name);
continue;
}
// 查找对应的屏幕信息
ScreenInfo* matched_screen = NULL;
for (int j = 0; j < screen_count; j++) {
if (strcmp(screens[j].name, configs[i].display_name) == 0) {
matched_screen = &screens[j];
break;
}
}
if (matched_screen == NULL) {
log_message("未找到 %s 对应的屏幕, 将使用第一屏\n", configs[i].display_name);
// 未找到屏幕,则返回第一个屏幕
matched_screen = &screens[0];
}
log_message("匹配屏幕: %s %dx%d 位置(%d,%d)\n",
matched_screen->name, matched_screen->width,
matched_screen->height, matched_screen->x, matched_screen->y);
// 计算并设置矩阵
float matrix[9];
calculate_ctm(*matched_screen, total_width, total_height, matrix);
log_message("计算矩阵: [%f, %f, %f, %f, %f, %f, %f, %f, %f]\n",
matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5],
matrix[6], matrix[7], matrix[8]);
set_ctm(devices[device_index].device_id, matrix);
memcpy(devices[device_index].matrix, matrix, sizeof(float) * 9);
log_message("已配置 %s 为 %s 的触摸屏\n",
devices[device_index].name, configs[i].display_name);
}
// 保存到文件
if (output_file_path) {
int max_retries = 3;
int retry_delay = 1; // 秒
FILE *output_file = NULL;
for (int retry = 0; retry < max_retries; retry++) {
output_file = fopen(output_file_path, "w");
if (output_file) {
break;
}
log_message("无法打开输出文件 %s (尝试 %d/%d)%d秒后重试...\n",
output_file_path, retry + 1, max_retries, retry_delay);
sleep(retry_delay);
}
if (output_file) {
int wcount = 0;
for (int i = 0; i < device_count; i++) {
wcount++;
if(devices[i].match_success == 0){
continue;
}
fprintf(output_file, "%d|%s|%s|%s:%s|%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f\n",
devices[i].device_id,
devices[i].name,
devices[i].device_node,
devices[i].vid,
devices[i].pid,
devices[i].matrix[0], devices[i].matrix[1], devices[i].matrix[2],
devices[i].matrix[3], devices[i].matrix[4], devices[i].matrix[5],
devices[i].matrix[6], devices[i].matrix[7], devices[i].matrix[8]);
}
fclose(output_file);
log_message("已成功写入 %d 条记录到文件 %s\n", wcount, output_file_path);
} else {
log_message("经过 %d 次尝试后仍无法打开输出文件 %s,放弃写入\n",
max_retries, output_file_path);
}
}
log_message("触摸屏配置完成\n");
if (log_file) {
fclose(log_file);
}
return 0;
}
-386
View File
@@ -1,386 +0,0 @@
#include <libudev.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <locale.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <ctype.h>
#define DEFAULT_POLL_INTERVAL 5 // 默认轮询间隔为5秒
#define MAX_RETRY_ATTEMPTS 3 // 最大重试次数
#define RETRY_DELAY 1 // 重试延迟(秒)
#define COOLDOWN_SECONDS 7 // 触摸屏检测冷却时间(秒)
FILE *log_file = NULL; // 日志文件指针
// 函数提前声明
int write_file_content_with_retry(const char *file_path, int value);
void sigint_handler(int sig);
int is_touchscreen(struct udev_device *dev);
int read_file_content_with_retry(const char *file_path);
void monitor_usb_devices(const char *control_file_path, int poll_interval);
void print_usage(const char *program_name);
volatile sig_atomic_t keep_running = 1;
void sigint_handler(int sig) {
keep_running = 0;
}
// 日志函数
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
// 获取当前时间
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
// 输出到控制台
printf("[%s] usb_ds \t", time_str);
vprintf(format, args);
// 输出到文件
if (log_file) {
fprintf(log_file, "[%s] usb_ds \t", time_str);
vfprintf(log_file, format, args);
fflush(log_file); // 确保立即写入文件
}
va_end(args);
}
// 检查设备是否为触摸屏(简化版)
int is_touchscreen(struct udev_device *dev) {
const char *name = udev_device_get_property_value(dev, "NAME");
const char *id_input_touchscreen = udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
// 检查ID_INPUT_TOUCHSCREEN属性
if (id_input_touchscreen != NULL && strcmp(id_input_touchscreen, "1") == 0) {
return 1;
}
// 检查设备名称中是否包含"touch"相关关键词(不区分大小写)
if (name != NULL) {
// 转换为小写以进行不区分大小写的比较
char lower_name[256];
strncpy(lower_name, 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, "touchscreen") != NULL ||
strstr(lower_name, "touch") != NULL ||
strstr(lower_name, "ilitek") != NULL ||
strstr(lower_name, "tablet") != NULL) {
return 1;
}
}
return 0;
}
// 读取文件内容(带重试机制)
int read_file_content_with_retry(const char *file_path) {
int attempts = 0;
int content = -1;
while (attempts < MAX_RETRY_ATTEMPTS && keep_running) {
FILE *file = fopen(file_path, "r");
if (file == NULL) {
if (errno == ENOENT) {
// 文件不存在,创建并初始化为0
printf("控制文件不存在,创建文件并初始化为0\n");
if (write_file_content_with_retry(file_path, 0) == 0) {
content = 0;
break;
}
} else {
perror("打开文件失败");
}
} else {
if (fscanf(file, "%d", &content) == 1) {
fclose(file);
break;
} else {
fclose(file);
fprintf(stderr, "读取文件内容失败\n");
}
}
attempts++;
if (attempts < MAX_RETRY_ATTEMPTS) {
printf("重试读取文件(%d/%d)...\n", attempts, MAX_RETRY_ATTEMPTS);
sleep(RETRY_DELAY);
}
}
if (attempts >= MAX_RETRY_ATTEMPTS) {
fprintf(stderr, "达到最大重试次数,放弃读取文件\n");
}
return content;
}
// 写入文件内容(带重试机制)
int write_file_content_with_retry(const char *file_path, int value) {
int attempts = 0;
int success = -1;
while (attempts < MAX_RETRY_ATTEMPTS && keep_running) {
FILE *file = fopen(file_path, "w");
if (file == NULL) {
perror("打开文件失败");
} else {
if (fprintf(file, "%d", value) > 0) {
success = 0;
fclose(file);
break;
} else {
fclose(file);
fprintf(stderr, "写入文件内容失败\n");
}
}
attempts++;
if (attempts < MAX_RETRY_ATTEMPTS) {
printf("重试写入文件(%d/%d)...\n", attempts, MAX_RETRY_ATTEMPTS);
sleep(RETRY_DELAY);
}
}
if (attempts >= MAX_RETRY_ATTEMPTS) {
fprintf(stderr, "达到最大重试次数,放弃写入文件\n");
}
return success;
}
// 监控USB设备事件
void monitor_usb_devices(const char *control_file_path, int poll_interval) {
struct udev *udev;
struct udev_monitor *mon;
struct udev_device *dev;
int fd;
int use_control_file = 0;
// 检查是否使用控制文件
if (control_file_path != NULL) {
use_control_file = 1;
log_message("使用控制文件: %s\n", control_file_path);
// 初始化控制文件(如果不存在)
if (access(control_file_path, F_OK) != 0) {
printf("控制文件不存在,创建并初始化为0\n");
if (write_file_content_with_retry(control_file_path, 0) != 0) {
fprintf(stderr, "无法创建控制文件,禁用控制文件功能\n");
use_control_file = 0;
}
}
}
// 创建udev对象
udev = udev_new();
if (!udev) {
// fprintf(stderr, "无法创建udev对象\n");
exit(EXIT_FAILURE);
}
// 创建监控对象,监控USB设备
mon = udev_monitor_new_from_netlink(udev, "udev");
udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL);
udev_monitor_filter_add_match_subsystem_devtype(mon, "input", NULL);
udev_monitor_enable_receiving(mon);
// 获取监控文件描述符
fd = udev_monitor_get_fd(mon);
printf("开始监控USB设备...\n");
printf("轮询间隔: %d秒\n", poll_interval);
printf("按Ctrl+C退出\n\n");
// 主循环,监控设备事件
time_t last_trigger_time = 0; // 上次触发控制文件的时间
while (keep_running) {
fd_set fds;
struct timeval tv;
int ret;
int file_status = 0;
int touchscreen_detected = 0;
// 如果使用控制文件,在每轮查询开始前检查文件状态
if (use_control_file) {
file_status = read_file_content_with_retry(control_file_path);
if (file_status == -1) {
// 读取文件失败,继续监控但不处理事件
log_message("读取控制文件失败,继续监控但不处理事件\n");
sleep(poll_interval);
continue;
}
if (file_status != 0) {
// 文件状态不为0,不处理事件
printf("控制文件状态为%d,不处理事件\n", file_status);
sleep(poll_interval);
continue;
}
}
FD_ZERO(&fds);
FD_SET(fd, &fds);
tv.tv_sec = poll_interval;
tv.tv_usec = 0;
ret = select(fd+1, &fds, NULL, NULL, &tv);
if (ret > 0 && FD_ISSET(fd, &fds)) {
// 获取设备
dev = udev_monitor_receive_device(mon);
if (dev) {
const char *action = udev_device_get_action(dev);
const char *devpath = udev_device_get_devpath(dev);
const char *product = udev_device_get_property_value(dev, "ID_MODEL");
const char *vendor = udev_device_get_property_value(dev, "ID_VENDOR");
// 只处理添加设备的事件
if (action && strcmp(action, "add") == 0) {
log_message("检测到新设备:\n");
log_message(" 设备路径: %s, %s:%s\n", devpath, vendor ? vendor : "未知",product ? product : "未知");
// 检查是否为指针设备
const char *id_input_mouse = udev_device_get_property_value(dev, "ID_INPUT_MOUSE");
const char *id_input_touchpad = udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD");
const char *id_input_joystick = udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK");
const char *id_input_touchscreen = udev_device_get_property_value(dev, "ID_INPUT_TOUCHSCREEN");
if (id_input_mouse != NULL || id_input_touchpad != NULL || id_input_joystick != NULL || id_input_touchscreen != NULL) {
printf(" 类型: 指针设备\n");
// 检查是否为触摸屏
if (is_touchscreen(dev)) {
printf(" 子类型: 触摸屏\n");
touchscreen_detected = 1;
}
}
// printf("\n");
}
udev_device_unref(dev);
}
} else if (ret == 0) {
// 超时,没有事件发生
if (use_control_file) {
// 检查控制文件状态
int current_status = read_file_content_with_retry(control_file_path);
if (current_status == -1) {
printf("读取控制文件失败\n");
} else if (current_status != file_status) {
printf("控制文件状态已从%d变为%d\n", file_status, current_status);
}
}
}
// 一轮检测完成后,如果需要更新控制文件状态(带冷却时间防重复触发)
if (use_control_file && touchscreen_detected) {
time_t now = time(NULL);
if (difftime(now, last_trigger_time) < COOLDOWN_SECONDS) {
log_message("冷却中,跳过本次触发(距上次 %.0f 秒)\n", difftime(now, last_trigger_time));
} else {
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");
}
}
}
}
// 清理资源
udev_monitor_unref(mon);
udev_unref(udev);
}
void print_usage(const char *program_name) {
printf("用法: %s [控制文件路径] [轮询间隔(秒)]\n", program_name);
printf("选项:\n");
printf(" 控制文件路径: 用于控制监控的文件路径(如/tmp/usb_monitor.ctl)\n");
printf(" 轮询间隔: 检查间隔时间(秒),默认%d秒\n", DEFAULT_POLL_INTERVAL);
printf("示例:\n");
printf(" %s /tmp/usb_monitor.ctl 5\n", program_name);
printf(" %s\n", program_name);
}
int main(int argc, char *argv[]) {
setlocale(LC_ALL, "");
const char *control_file_path = NULL;
int poll_interval = DEFAULT_POLL_INTERVAL;
// 解析命令行参数
if (argc > 1) {
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
print_usage(argv[0]);
return EXIT_SUCCESS;
}
control_file_path = argv[1];
}
if (argc > 2) {
poll_interval = atoi(argv[2]);
if (poll_interval <= 0) {
fprintf(stderr, "轮询间隔必须大于0\n");
return EXIT_FAILURE;
}
}
log_file = fopen("/opt/ktouch/sub_modules.log", "a");
if (!log_file) {
printf("无法打开日志文件,将只输出到控制台\n");
}
log_message("USB触摸屏设备检测程序\n");
printf("=====================\n");
// 设置信号处理
signal(SIGINT, sigint_handler);
signal(SIGTERM, sigint_handler);
// 启动监控
monitor_usb_devices(control_file_path, poll_interval);
log_message("程序已退出\n");
return EXIT_SUCCESS;
}