← 전체로 돌아가기
스킬 linux

리눅스 키 입력 시스템 제어

특정 키 입력으로 리눅스 시스템 제어 및 자동 실행

linuxc-programmingsystemdkeyboard-inputsystem-control

리눅스에서 특정 키(예: F16 셧다운, F24 윈도우 재부팅) 입력으로 시스템을 제어하는 C 프로그램을 만들고, 부팅 시 자동 실행되도록 설정하는 방법이야.

  • C 프로그램: linux/input.h로 키보드 이벤트를 감지하고 system()으로 셸 명령을 실행해. 장치명(cat /proc/bus/input/devices로 확인)과 키코드(TARGET_KEYCODE) 설정이 필요해.

  • 컴파일/설치:

gcc your_program.c -o your_program
sudo mv your_program /usr/local/bin/
sudo chmod +x /usr/local/bin/your_program
  • Systemd 서비스: /etc/systemd/system/your_program.service 파일을 만들고 ExecStart=/usr/local/bin/your_program 등을 설정 후 활성화해.
sudo systemctl daemon-reload && sudo systemctl enable --now your_program.service

여기서 배울 것

  1. C 언어로 리눅스 키보드 입력 감지 및 제어
  2. system() 함수로 셸 명령 실행하기
  3. systemd 서비스로 프로그램 자동 실행 설정
원본 파일 보기 (.claude/skills/tn-linux-custom-startup-program/SKILL.md)
---
name: Linux 커스텀 키 입력 시스템 제어 프로그램
description: This skill should be used when the user asks to create and configure a custom C program to run at Linux startup, enabling specific system actions (like shutdown or rebooting to Windows) to be triggered by keyboard input.
version: 1.0.0
source: /home/son/prj/resume/backup_notes_260317/notion/Tech Note/Linux custom Startup Program 2d6d7efd824b8005a53ce2efcaeea3fb.md
---

# Linux custom Startup Program

c로 코드 작성

- shutdown
    
    ```c
    son@son-5070ti:~/aa$ cat shutdown.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <linux/input.h>
    #include <string.h>
    #include <dirent.h>
    #include <errno.h>
    
    // --- CONFIGURATION ---
    // 찾고자 하는 키보드의 정확한 이름 (cat /proc/bus/input/devices 에서 Name="..." 부분 참고)
    #define TARGET_DEVICE_NAME "USB Composite Device Keyboard"
    #define TARGET_KEYCODE 186
    #define KEY_PRESS_VALUE 1
    
    // 셧다운 실행 함수 (기존과 동일)
    void execute_delayed_shutdown() {
        const char *cmd = "/bin/bash -c \"/usr/bin/logger 'F16 pressed. Shutdown in 5s...' && sleep 5 && /sbin/shutdown -h now\" &";
        printf("Triggering shutdown sequence...\n");
        if (system(cmd) == -1) {
            perror("Error executing shutdown command");
        }
    }
    
    // 이름을 기반으로 장치 경로를 찾는 함수
    // 찾으면 동적으로 할당된 경로 문자열 반환 (예: "/dev/input/event3"), 못 찾으면 NULL
    char* find_device_by_name(const char *target_name) {
        DIR *dir;
        struct dirent *entry;
        char path[512];
        char name[256] = "Unknown";
        int fd;
    
        // /dev/input 디렉토리 열기
        if ((dir = opendir("/dev/input")) == NULL) {
            perror("Cannot open /dev/input");
            return NULL;
        }
    
        // 디렉토리 내의 파일들을 순회
        while ((entry = readdir(dir)) != NULL) {
            // "event"로 시작하는 파일만 확인 (예: event0, event1...)
            if (strncmp(entry->d_name, "event", 5) == 0) {
                snprintf(path, sizeof(path), "/dev/input/%s", entry->d_name);
    
                // 읽기 전용으로 열어서 이름 확인
                fd = open(path, O_RDONLY);
                if (fd < 0) continue;
    
                // 장치 이름 가져오기 (ioctl 사용)
                if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
                    close(fd);
                    continue;
                }
    
                close(fd); // 이름만 확인하고 일단 닫음
    
                // 이름 비교 (부분 일치가 아니라 정확히 포함되는지 확인)
                // strstr을 사용하여 target_name이 장치 이름 안에 포함되어 있는지 확인
                if (strstr(name, target_name) != NULL) {
                    printf("Found device: %s -> %s\n", name, path);
                    closedir(dir);
                    return strdup(path); // 찾은 경로 반환
                }
            }
        }
    
        closedir(dir);
        return NULL; // 못 찾음
    }
    
    int main() {
        int fd = -1;
        struct input_event ev;
        char *device_path = NULL;
    
        printf("Daemon started. Waiting for device with name: '%s'...\n", TARGET_DEVICE_NAME);
    
        // 1. 장치 찾기 루프 (장치가 연결될 때까지 무한 대기)
        while (1) {
            device_path = find_device_by_name(TARGET_DEVICE_NAME);
    
            if (device_path != NULL) {
                // 경로를 찾았으면 open 시도
                fd = open(device_path, O_RDONLY);
                if (fd >= 0) {
                    // 성공적으로 열림
                    printf("Successfully opened input device: %s\n", device_path);
                    free(device_path); // strdup으로 할당된 메모리 해제
                    break; // 루프 탈출
                } else {
                    perror("Found device but failed to open");
                    free(device_path);
                }
            }
    
            // 못 찾았거나 열기 실패시 2초 대기
            sleep(2);
        }
    
        // 2. 메인 모니터링 루프
        printf("Monitoring keycode %d...\n", TARGET_KEYCODE);
    
        while (read(fd, &ev, sizeof(ev)) > 0) {
            if (ev.type == EV_KEY && ev.code == TARGET_KEYCODE && ev.value == KEY_PRESS_VALUE) {
                execute_delayed_shutdown();
            }
        }
    
        // 장치가 뽑히거나 에러 발생 시
        perror("Error reading input device");
        close(fd);
        return EXIT_FAILURE;
    }
    
    ```
    
- window
    
    ```c
    son@son-5070ti:~/aa$ cat reboot_windows.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <linux/input.h>
    #include <string.h>
    #include <dirent.h>
    #include <errno.h>
    
    // --- CONFIGURATION ---
    // 1. 키보드 이름 (cat /proc/bus/input/devices | grep Name 확인 필요)
    #define TARGET_DEVICE_NAME "USB Composite Device Keyboard"
    
    // 2. 윈도우 부트 ID (efibootmgr 명령어 확인 필요)
    #define WINDOWS_BOOT_ID "0000"
    
    // 3. 감지할 키코드 (F24)
    #define TARGET_KEYCODE 194
    
    // 이름을 기반으로 장치 경로를 찾는 함수 (이전 코드와 동일)
    char* find_device_by_name(const char *target_name) {
        DIR *dir;
        struct dirent *entry;
        char path[512];
        char name[256] = "Unknown";
        int fd;
    
        if ((dir = opendir("/dev/input")) == NULL) {
            perror("Cannot open /dev/input");
            return NULL;
        }
    
        while ((entry = readdir(dir)) != NULL) {
            if (strncmp(entry->d_name, "event", 5) == 0) {
                snprintf(path, sizeof(path), "/dev/input/%s", entry->d_name);
                fd = open(path, O_RDONLY);
                if (fd < 0) continue;
    
                if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
                    close(fd);
                    continue;
                }
                close(fd);
    
                if (strstr(name, target_name) != NULL) {
                    printf("Found device: %s -> %s\n", name, path);
                    closedir(dir);
                    return strdup(path);
                }
            }
        }
        closedir(dir);
        return NULL;
    }
    
    void execute_windows_reboot() {
        char cmd[100];
    
        printf("F24 pressed! Setting next boot to Windows (%s)...\n", WINDOWS_BOOT_ID);
    
        // 1. 다음 부팅을 윈도우로 설정
        snprintf(cmd, sizeof(cmd), "efibootmgr -n %s", WINDOWS_BOOT_ID);
        if (system(cmd) == -1) {
            perror("Failed to set boot order");
        }
    
        // 2. 잠시 대기 후 재부팅 (로그 기록 및 안전 대기)
        printf("Rebooting system in 3 seconds...\n");
        system("sleep 3");
        system("reboot");
    }
    
    int main() {
        int fd = -1;
        struct input_event ev;
        char *device_path = NULL;
    
        printf("Daemon started. Waiting for device: '%s'...\n", TARGET_DEVICE_NAME);
    
        // 1. 장치 찾기 (무한 대기)
        while (1) {
            device_path = find_device_by_name(TARGET_DEVICE_NAME);
            if (device_path != NULL) {
                fd = open(device_path, O_RDONLY);
                if (fd >= 0) {
                    printf("Successfully connected to %s\n", device_path);
                    free(device_path);
                    break;
                }
                free(device_path);
            }
            sleep(2); // 못 찾으면 2초 대기
        }
    
        // 2. 키 입력 감시
        printf("Monitoring for Keycode %d (Reboot to Windows)...\n", TARGET_KEYCODE);
    
        while (read(fd, &ev, sizeof(ev)) > 0) {
            if (ev.type == EV_KEY && ev.code == TARGET_KEYCODE && ev.value == 1) { // value 1 is Key Press
                execute_windows_reboot();
                // 재부팅 명령이 실행되면 곧 프로세스가 종료되므로 break 불필요
            }
        }
    
        perror("Device disconnected or read error");
        close(fd);
        return EXIT_FAILURE;
    }
    
    ```
    

gcc로 컴파일

```c
gcc [src] -o [target]
```

move

```c
sudo mv my_program /usr/local/bin/
sudo chmod +x /usr/local/bin/my_program
```

서비스 파일

```c
sudo vi /etc/systemd/system/my_program.service
```

service code

```c
[Unit]
Description=My GCC Compiled Program #shutdown
After=network.target
#fast => After=basic.target

[Service]
ExecStart=/usr/local/bin/my_program # shutdown
Restart=always
# restartsec?
# RestartSec=3
User=your_username # root

[Install]
WantedBy=multi-user.target

```

fast?

```c
#fast => After=basic.target
```

execute

```c
sudo systemctl daemon-reload
sudo systemctl enable my_program
sudo systemctl start my_program
```