리눅스 키 입력 시스템 제어
특정 키 입력으로 리눅스 시스템 제어 및 자동 실행
리눅스에서 특정 키(예: 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
여기서 배울 것
- C 언어로 리눅스 키보드 입력 감지 및 제어
- system() 함수로 셸 명령 실행하기
- 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
```