You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C编写X11锁屏程序并拦截所有键盘输入?

C语言实现X11锁屏程序(拦截键盘输入)

要实现类似i3lock的锁屏程序,核心是创建全屏顶层窗口并独占键盘输入,阻止其他程序接收键盘事件,直到用户输入正确密码。以下是具体实现步骤和代码示例:

核心实现步骤

1. 创建全屏顶层窗口

  • 初始化X11显示连接,获取屏幕尺寸
  • 创建覆盖整个屏幕的窗口,设置为全屏状态,避免被窗口管理器移动、关闭
  • 设置窗口属性,让窗口管理器将其识别为锁屏类窗口(避免被其他窗口遮挡)

2. 独占键盘输入

使用XGrabKeyboard函数抓取键盘,让所有键盘事件直接发送到锁屏窗口,其他程序无法接收键盘输入。需处理抓取失败的情况(比如已有程序占用键盘)。

3. 处理键盘输入与密码验证

  • 在事件循环中拦截所有KeyPress事件,提取输入字符
  • 处理特殊按键(回车验证、退格删除、Esc清空输入)
  • 使用系统密码验证机制(推荐PAM,无需root权限更安全)验证输入的密码

4. 防止非法退出

  • 忽略窗口关闭事件(WM_DELETE_WINDOW)
  • 处理系统信号(如SIGINT、SIGTERM),确保退出前释放键盘抓取

完整代码示例

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <unistd.h>
#include <signal.h>
#include <security/pam_appl.h>

static Display *global_dpy;

// 生成重复字符的字符串(用于显示密码星号)
char *strrep(const char *s, int n) {
    char *res = malloc(n + 1);
    if (!res) return NULL;
    memset(res, s[0], n);
    res[n] = '\0';
    return res;
}

// PAM对话函数,用于传递输入的密码
static int pam_conv(int num_msg, const struct pam_message **msg, 
                    struct pam_response **resp, void *appdata_ptr) {
    *resp = malloc(num_msg * sizeof(struct pam_response));
    if (!*resp) return PAM_BUF_ERR;
    
    for (int i = 0; i < num_msg; i++) {
        (*resp)[i].resp = strdup((char *)appdata_ptr);
        (*resp)[i].resp_retcode = 0;
    }
    return PAM_SUCCESS;
}

// 信号处理函数,确保退出时释放资源
void sig_handler(int sig) {
    if (global_dpy) {
        XUngrabKeyboard(global_dpy, CurrentTime);
        XCloseDisplay(global_dpy);
    }
    exit(0);
}

int main() {
    Display *dpy;
    Window win;
    XEvent ev;
    Atom wm_state, fullscreen, wm_delete;
    char passwd[256] = {0};
    int passwd_len = 0;
    struct passwd *pw = getpwuid(getuid());
    if (!pw) {
        fprintf(stderr, "无法获取用户信息\n");
        exit(1);
    }

    // 注册信号处理
    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);

    // 打开X11显示
    dpy = XOpenDisplay(NULL);
    if (!dpy) {
        fprintf(stderr, "无法打开显示\n");
        exit(1);
    }
    global_dpy = dpy;

    int scr = DefaultScreen(dpy);
    Window root = RootWindow(dpy, scr);
    int width = DisplayWidth(dpy, scr);
    int height = DisplayHeight(dpy, scr);

    // 创建全屏窗口
    win = XCreateSimpleWindow(dpy, root, 0, 0, width, height, 0,
                              BlackPixel(dpy, scr), BlackPixel(dpy, scr));

    // 设置窗口为全屏、顶层
    wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
    fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
    XChangeProperty(dpy, win, wm_state, XA_ATOM, 32, PropModeReplace,
                    (unsigned char *)&fullscreen, 1);

    // 设置窗口不可关闭
    wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(dpy, win, &wm_delete, 1);

    // 显示窗口并获取焦点
    XMapWindow(dpy, win);
    XSetInputFocus(dpy, win, RevertToNone, CurrentTime);
    XFlush(dpy);

    // 抓取键盘,独占所有输入
    if (!XGrabKeyboard(dpy, win, True, GrabModeAsync, GrabModeAsync, CurrentTime)) {
        fprintf(stderr, "无法抓取键盘,可能已有程序占用\n");
        XCloseDisplay(dpy);
        exit(1);
    }

    // 事件循环
    while (1) {
        XNextEvent(dpy, &ev);
        switch (ev.type) {
            case KeyPress: {
                char buf[32];
                int len = XLookupString(&ev.xkey, buf, sizeof(buf), NULL, NULL);
                if (len <= 0) break;

                if (buf[0] == '\n' || buf[0] == '\r') {
                    // 验证密码(使用PAM)
                    pam_handle_t *pamh = NULL;
                    struct pam_conv conv = { pam_conv, passwd };
                    int ret = pam_start("login", pw->pw_name, &conv, &pamh);
                    
                    if (ret == PAM_SUCCESS) {
                        ret = pam_authenticate(pamh, 0);
                        if (ret == PAM_SUCCESS) {
                            // 密码正确,退出锁屏
                            pam_end(pamh, ret);
                            XUngrabKeyboard(dpy, CurrentTime);
                            XCloseDisplay(dpy);
                            exit(0);
                        } else {
                            // 密码错误,清空输入
                            fprintf(stderr, "密码错误\n");
                            memset(passwd, 0, sizeof(passwd));
                            passwd_len = 0;
                        }
                        pam_end(pamh, ret);
                    } else {
                        fprintf(stderr, "PAM初始化失败\n");
                        memset(passwd, 0, sizeof(passwd));
                        passwd_len = 0;
                    }
                } else if (buf[0] == '\b' && passwd_len > 0) {
                    // 退格键删除最后一个字符
                    passwd[--passwd_len] = '\0';
                } else if (buf[0] == 27) {
                    // Esc键清空输入
                    memset(passwd, 0, sizeof(passwd));
                    passwd_len = 0;
                } else if (passwd_len < sizeof(passwd) - 1) {
                    // 普通字符添加到密码
                    passwd[passwd_len++] = buf[0];
                }

                // 刷新窗口,显示密码输入状态
                XClearWindow(dpy, win);
                GC gc = XCreateGC(dpy, win, 0, NULL);
                XSetForeground(dpy, gc, WhitePixel(dpy, scr));
                
                char *stars = strrep("*", passwd_len);
                char prompt[256];
                snprintf(prompt, sizeof(prompt), "请输入密码:%s", stars ? stars : "");
                XDrawString(dpy, win, gc, 50, height/2, prompt, strlen(prompt));
                
                XFreeGC(dpy, gc);
                free(stars);
                XFlush(dpy);
                break;
            }
            case ClientMessage: {
                // 忽略窗口关闭请求
                if ((Atom)ev.xclient.data.l[0] == wm_delete) {
                    break;
                }
                // 处理全屏状态变更,确保窗口保持全屏
                if ((Atom)ev.xclient.data.l[0] == wm_state) {
                    XChangeProperty(dpy, win, wm_state, XA_ATOM, 32, PropModeReplace,
                                    (unsigned char *)&fullscreen, 1);
                }
                break;
            }
            case ConfigureNotify: {
                // 窗口大小变更时重新设置为全屏
                width = ev.xconfigure.width;
                height = ev.xconfigure.height;
                XResizeWindow(dpy, win, width, height);
                break;
            }
            default:
                // 忽略其他事件
                break;
        }
    }

    return 0;
}

编译与运行

编译时需要链接X11、PAM和crypt库:

gcc lock.c -o xlock -lX11 -lpam -lcrypt

运行程序后,输入用户密码回车即可解锁;Esc清空输入,退格键删除字符。

注意事项

  1. 多屏幕支持:上述代码仅处理主屏幕,若需支持多屏幕,需遍历所有屏幕创建对应窗口或创建覆盖整个虚拟桌面的窗口。
  2. TTY切换拦截:X11程序无法拦截Ctrl+Alt+F1这类TTY切换快捷键,若需阻止,需修改系统配置(如禁用TTY切换),但这通常不推荐。
  3. 权限问题:使用PAM验证无需root权限;若直接读取/etc/shadow,程序需以root权限运行或配置特殊权限,安全性较低。
  4. 窗口管理器兼容性:部分窗口管理器可能需要额外设置窗口属性(如_NET_WM_WINDOW_TYPE_SPLASH)来确保窗口始终在顶层。

内容的提问来源于stack exchange,提问作者h00die

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:55:27