momo zone

调核人的blog

把键盘LED 变成硬盘 LED

非常实用的程序,把键盘上无用的scroll 灯变成硬盘指示灯,免得每次想看的时候我都要弯腰。代码中可以使用kbd 和 X11提供的两种接口来控制LED。

/*
 * To compile:
 *   Console mode: gcc -Wall -O3 -o hddled hddled.c
 *   X mode:       gcc -Wall -O3 -DX -lX11 -o hddled hddled.c
 *
 * Options:
 * -d, --detach               Detach from terminal
 * -r, --refresh=VALUE        Refresh interval (default: 50 ms)
 *
 * Console options:
 * -C, --console=DEVICE       Console device (default: /dev/console)
 *
 * X options:
 * -D, --display=DISPLAY      X display (default: $DISPLAY)
 */

#define PACKAGE_STRING    "hddled 0.2"
#define PACKAGE_BUGREPORT "hddled@very.puzzling.org"

#define VMSTAT "/proc/vmstat"

#define _GNU_SOURCE

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#ifdef X
# include <X11/X.h>
# include <X11/Xlib.h>
# define SCROLL_LOCK 1

/* X display connection */
typedef Display *data_t;
static char *o_display = NULL;

#else
# include 
# include <linux/kd.h>
# include <sys/ioctl.h>
# define CONSOLE "/dev/console"

/* File descriptor */
typedef int data_t;
static char *o_console = NULL;

#endif

const char *argp_program_version = PACKAGE_STRING;
const char *argp_program_bug_address = "";

static unsigned int o_refresh = 50; /* milliseconds */
static int o_detach = 0;

static volatile sig_atomic_t running = 1;
static char *line = NULL;
static size_t len = 0;

/*
static void sig_hdl(int signo){
	remove("/var/run/hddled.lock");
	printf("killed\n");
	exit(0);
}
*/

/* Reread the vmstat file */
int
activity(FILE *vmstat) {
	static unsigned int prev_pgpgin, prev_pgpgout;
	unsigned int pgpgin, pgpgout;
	int found_pgpgin, found_pgpgout;
	int result;

	/* Reload the vmstat file */
	result = TEMP_FAILURE_RETRY(fseek(vmstat, 0L, SEEK_SET));
	if (result) {
		perror("Could not rewind " VMSTAT);
		return result;
	}

	/* Clear glibc's buffer */
	result = TEMP_FAILURE_RETRY(fflush(vmstat));
	if (result) {
		perror("Could not flush input stream");
		return result;
	}

	/* Extract the I/O stats */
	found_pgpgin = found_pgpgout = 0;
	while (getline(&line, &len, vmstat) != -1 && errno != EINTR) {
		if (sscanf(line, "pgpgin %u", &pgpgin))
			found_pgpgin++;
		else if (sscanf(line, "pgpgout %u", &pgpgout))
			found_pgpgout++;
		if (found_pgpgin && found_pgpgout)
			break;
	}
	if (!found_pgpgin || !found_pgpgout) {
		fprintf(stderr, "Could not find required lines in " VMSTAT);
		return -1;
	}

	/* Anything changed? */
	result =
		(prev_pgpgin  != pgpgin) ||
		(prev_pgpgout != pgpgout);
	prev_pgpgin = pgpgin;
	prev_pgpgout = pgpgout;
	return result;
}

/* Update the keyboard LED */
void
led(data_t data, int on) {
	static int current = 1; /* Ensure the LED turns off on first call */
	if (current == on)
		return;

#ifdef X
	{
		static XKeyboardControl control;
		control.led = SCROLL_LOCK;
		control.led_mode = on ? LedModeOn : LedModeOff;
		XChangeKeyboardControl(data, KBLed | KBLedMode, &control);
		XFlush(data);
	}
#else
	{
		unsigned long state;
		ioctl(data, KDGETLED, &state);
		if (on)
			state |= LED_CAP;
		else
			state &= ~LED_CAP;
//		printf("flash:\n");
		ioctl(data, KDSETLED, state);
	}
#endif

	current = on;
}

/* Signal handler -- break out of the main loop */
void
shutdown(int sig) { running = 0;remove("/var/run/hddled.lock"); }

/* Argp parser function */
error_t
parse_options(int key, char *arg, struct argp_state *state) {
	switch (key) {
	case 'd':
		o_detach = 1;
		break;
	case 'r':
		o_refresh = strtol(arg, NULL, 10);
		if (o_refresh < 10)
			argp_failure(state, EXIT_FAILURE, 0,
				"refresh interval must be at least 10");
		break;
#ifdef X
	case 'D':
		o_display = strdup(arg);
		break;
#else
	case 'C':
		o_console = strdup(arg);
		break;
#endif
	}
	return 0;
}

int
main(int argc, char **argv) {
	struct argp_option options[] = {
		{  "detach", 'd',      NULL, 0, "Detach from terminal" },
		{ "refresh", 'r',   "VALUE", 0, "Refresh interval (default: 50 ms)" },
#ifdef X
		{ "display", 'D', "DISPLAY", 0, "X display (default: $DISPLAY)", 1 },
#else
		{ "console", 'C', "DEVICE",  0, "Console device (default: " CONSOLE ")", 1 },
#endif
		{ 0 },
	};
	struct argp parser = {
		NULL, parse_options, NULL,
		"Show hard disk activity using the Scroll Lock LED.",
		NULL, NULL, NULL
	};
	int status = EXIT_FAILURE;
	FILE *vmstat = NULL;
	data_t data = 0;
	struct timespec delay;
	int fd;
	fd=open("/var/run/hddled.lock",O_RDONLY);
	if(fd!=-1){
		printf("already running\n");
		close(fd);
		exit(1);
	}

	fd=open("/var/run/hddled.lock",O_RDWR|O_CREAT,0664);
	if(fd==-1){
		printf("create lock file failed,exit\n");
		exit(1);
	}
	close(fd);
/*
	if(signal(SIGTERM,sig_hdl)==SIG_ERR){
		printf("can't handle SIGTERM\n");
		exit (1);
	}

	if(signal(SIGINT,sig_hdl)==SIG_ERR){
                printf("can't handle SIGINT\n");
                exit (1);
         }
*/
	/* Parse the command-line */
	parser.options = options;
	if (argp_parse(&parser, argc, argv, ARGP_NO_ARGS, NULL, NULL))
		goto out;

	delay.tv_sec = o_refresh / 1000;
	delay.tv_nsec = 1000000 * (o_refresh % 1000);

#ifdef X
	/* Open the X display */
	data = XOpenDisplay(o_display);
	if (data == NULL) {
		if (o_display)
			fprintf(stderr, "Could not open display: %s\n", o_display);
		else
			fprintf(stderr, "Could not open default display\n");
		goto out;
	}
#else
	/* Open the console */
//	data = open(o_console ? o_console : CONSOLE, O_RDONLY);
	data = open("/dev/console",O_NOCTTY);
	if (data < 0) {
		fprintf(stderr, "Could not open console: %s: %s\n",
			o_console ? o_console : CONSOLE, strerror(errno));
		goto out;
	}
#endif

	/* Open the vmstat file */
	vmstat = fopen(VMSTAT, "r");
	if (!vmstat) {
		perror("Could not open " VMSTAT " for reading");
		goto out;
	}

	/* Ensure the LED is off */
	led(data, 0);

	/* Save the current I/O stat values */
	if (activity(vmstat) < 0)
		goto out;

	/* Detach from terminal? */
	if (o_detach) {
		pid_t child = fork();
		if (child < 0) {
			perror("Could not detach from terminal");
			goto out;
		}
		if (child) {
			/* I am the parent */
#ifdef X
			/* Don't close the display on my child */
			data = NULL;
#endif
			status = EXIT_SUCCESS;
			goto out;
		}
	}

	/* We catch these signals so we can clean up */
	{
		struct sigaction action;
		memset(&action, 0, sizeof(action));
		action.sa_handler = shutdown;
		sigemptyset(&action.sa_mask);
		action.sa_flags = 0; /* We block on usleep; don't use SA_RESTART */
		sigaction(SIGHUP, &action, NULL);
		sigaction(SIGINT, &action, NULL);
		sigaction(SIGTERM, &action, NULL);
	}

	/* Loop until signal received */
	while (running) {
		int a;
		if (nanosleep(&delay, NULL) < 0)
			break;
		a = activity(vmstat);
		if (a < 0)
			break;
		led(data, a);
	}

	/* Ensure the LED is off */
	led(data, 0);

	status = EXIT_SUCCESS;

out:
	if (line) free(line);
	if (vmstat) fclose(vmstat);
#ifdef X
	if (data) XCloseDisplay(data);
	if (o_display) free(o_display);
#else
	if (data) close(data);
	if (o_console) free(o_console);
#endif

	return status;
}

这里还有一个内核模式下的LED设置程序:

/*
 *  kbleds.c - Blink keyboard leds until the module is unloaded.
 */

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h>		/* For fg_console, MAX_NR_CONSOLES */
#include <linux/kd.h>		/* For KDSETLED */
#include <linux/console_struct.h>	/* For vc_cons */

MODULE_DESCRIPTION("Example module illustrating the use of Keyboard LEDs.");
MODULE_AUTHOR("Daniele Paolo Scarpazza");
MODULE_LICENSE("GPL");

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY   HZ/5
#define ALL_LEDS_ON   0x07
#define RESTORE_LEDS  0xFF

/*
 * Function my_timer_func blinks the keyboard LEDs periodically by invoking
 * command KDSETLED of ioctl() on the keyboard driver. To learn more on virtual
 * terminal ioctl operations, please see file:
 *     /usr/src/linux/drivers/char/vt_ioctl.c, function vt_ioctl().
 *
 * The argument to KDSETLED is alternatively set to 7 (thus causing the led
 * mode to be set to LED_SHOW_IOCTL, and all the leds are lit) and to 0xFF
 * (any value above 7 switches back the led mode to LED_SHOW_FLAGS, thus
 * the LEDs reflect the actual keyboard status).  To learn more on this,
 * please see file:
 *     /usr/src/linux/drivers/char/keyboard.c, function setledstate().
 *
 */

static void my_timer_func(unsigned long ptr)
{
	int *pstatus = (int *)ptr;

	if (*pstatus == ALL_LEDS_ON)
		*pstatus = RESTORE_LEDS;
	else
		*pstatus = ALL_LEDS_ON;

	(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
			    *pstatus);

	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);
}

static int __init kbleds_init(void)
{
	int i;

	printk(KERN_INFO "kbleds: loading\n");
	printk(KERN_INFO "kbleds: fgconsole is %x\n", fg_console);
	for (i = 0; i < MAX_NR_CONSOLES; i++) {
		if (!vc_cons[i].d)
			break;
		printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lx\n", i,
		       MAX_NR_CONSOLES, vc_cons[i].d->vc_num,
		       (unsigned long)vc_cons[i].d->vc_tty);
	}
	printk(KERN_INFO "kbleds: finished scanning consoles\n");

	my_driver = vc_cons[fg_console].d->vc_tty->driver;
	printk(KERN_INFO "kbleds: tty driver magic %x\n", my_driver->magic);

	/*
	 * Set up the LED blink timer the first time
	 */
	init_timer(&my_timer);
	my_timer.function = my_timer_func;
	my_timer.data = (unsigned long)&kbledstatus;
	my_timer.expires = jiffies + BLINK_DELAY;
	add_timer(&my_timer);

	return 0;
}

static void __exit kbleds_cleanup(void)
{
	printk(KERN_INFO "kbleds: unloading...\n");
	del_timer(&my_timer);
	(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
			    RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);

再提供一个关于LED控制的用户工具
名称:setleds
使用权限:一般使用者
使用方式:
setleds [-v] [-L] [-D] [-F] [{+|-}num] [{+|-}caps] [{+|-}scroll]说明:
用来设定键盘上方三个 LED 的状态。在 Linux 中,每一个虚拟主控台都有独立的设定。
参数:
-F
预设的选项,设定虚拟主控台的状态。
-D
除了改变虚拟主控台的状态外,还改变预设的状态。
-L
不改变虚拟主控台的状态,但直接改变 LED 显示的状态。这会使得 LDE 显示和目前虚拟主控台的状态不符合。我们可以在稍后用 -L 且不含其它选项的 setleds 命令回复正常状态。
-num +num
将数字键打开或关闭。
-caps +caps
把大小写键打开或关闭。
-scroll +scroll
把选项键打开或关闭。
范例:
将数字键打开,其馀二个灯关闭。
# setleds +num -caps -scroll

Advertisements

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: