重构项目源代码存储结构
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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脚本用于现场快速检查
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
HDMI-0|ILITEK Multi-Touch-V5000|1435117611|/dev/input/event10
|
||||
VGA-0|ILITEK ILITEK-TP|1435117617|/dev/input/event16
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
// }
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user