Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Some background: I'm trying to make the caps lock light on an external keyboard light up on-command when I run a command from the command line. Ideally it would be scriptable, so that I can use it for alerts and things (I disassembled the keyboard).

I got this command which looks like it should work:

fd=fdopen("/dev/console"); ioctl(fd, 0x4B32, 0x04);

When I try to run this command on either OS X or on a Ubuntu server I have laying around, this happens:

me@server1:~$ fd=fdopen("/dev/console"); ioctl(fd, 0x4B32, 0x04);
-bash: syntax error near unexpected token `('
me@server1:~$ fd=fdopen(/dev/console);
-bash: syntax error near unexpected token `('

So it looks like the problem is in the first part. Do I need to install a set of software/utilities? How can I manually control the caps lock light on my external keyboard?

share|improve this question

3 Answers 3

Your problem is that you try to enter C code into a shell prompt, this doesn't work for obvious reasons. You could either put it into a correct C file, compile it and get a valid binary you could execute:

#include <linux/kd.h>

#include <sys/ioctl.h>

#include <fcntl.h>
#include <unistd.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>

static void
usage(char *argv0)
{
    fprintf(stderr, "Usage: %s <on|off>\n", argv0);
    exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
    int fd;
    int on;
    unsigned char state;

    if (argc != 2)
        usage(argv[0]);

    if (strcmp(argv[1], "on") == 0)
        on = 1;
    else if(strcmp(argv[1], "off") == 0)
        on = 0;
    else
        usage(argv[0]);


    fd = open("/dev/console", O_RDWR);
    if (fd == -1)
        err(EXIT_FAILURE, "open /dev/console");

    if (ioctl(fd, KDGETLED, &state) == -1)
        err(EXIT_FAILURE, "KDGETLED");

    if (on)
        state |= LED_CAP;
    else
        state &= ~LED_CAP;

    if (ioctl(fd, KDSETLED, state) == -1)
        err(EXIT_FAILURE, "KDSETLED");

    close(fd);

    return 0;
}

Put this in a file called e.g. caps.c and compile it:

$ gcc -o caps caps.c

Then you can run it as

$ ./caps on

to turn on the LED or

$ ./caps off

to turn it off (toggling is left as an exercise for the reader).

Note: To open /dev/console you'll need super user rights.

Yet another note: This also does not prevent your terminal or X server from changing the CapsLock LED occasionally (e.g. when Caps is pressed). This also does not work on OS X but only on Linux since there's not standardized way to do this. Last of all, you cannot change LEDs on several keyboards individually.

share|improve this answer
    
This doesn't seem to be working - could the keyboard being external play a role? –  Undo Apr 18 at 14:25
    
Oh, sorry, I misread that »from the console«. I'll change the answer accordingly. –  Andreas Wiese Apr 18 at 14:31
    
+1 Great minds think alike ;) –  goldilocks Apr 18 at 15:01

That C code would not work anyway -- fdopen() requires more than one argument and returns a file stream, not a file descriptor.

I can remember doing this a few years ago, I thought via Xlib, but I could not find my old code (occasionally the collection gets purged) but I figured someone must have a simple app for this on github or something. Low and behold, the first page I found was this one which while I have no memory of posting it, it was written by me four years ago ("akashiraffee" was a name I used online a lot at the time).

It does not in fact use Xlib -- it uses ioctl(), like you were trying to do -- so should work sans GUI. I tidied it up and tried it here with a 5 LED keyboard:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <linux/kd.h>
#include <sys/ioctl.h>

int main (void) {
    int tty = open("/dev/console", 0), led;
    unsigned long int arg;

    if (tty < 3) {
        perror("open: ");
        return -1;
    }

    if (ioctl(tty,KDGKBTYPE, &arg) > 0) perror("ioctl: ");
    if (arg == KB_101) puts("You have a 101 key keyboard.");

    for (led = 1; led < 9; led++) {
        if (ioctl(tty,KDSETLED, led) > 0) perror("ioctl led on: ");
        printf("LED %d on...hit enter", led);
        getchar();
        if (ioctl(tty,KDSETLED, led+0xff) > 0) perror("ioctl led off: ");
        printf("off (hit enter)\n");
        getchar();
    }

    close(tty);

    return 0;
} 

Compile that gcc whatever.c -o testleds then ./testleds (note you need superuser privilleges). The first 7 worked for me; some of them them were individual LEDs and some of them are combinations.

I disassembled the keyboard

Of course, you need the ENTER to work to test that. If it doesn't, leave a comment and I will change to test automatically using a delay instead. If it does work in the end, I'd be happy to turn it into something you can use to toggle the lights by number (e.g. toggleLED 3).

share|improve this answer
    
Enter should still work, as long as I can find which little dot thing used to be ENTER. I'll try this when I get a chance, probably in an hour or two. –  Undo Apr 18 at 15:03

Linux

What you've posted is a snippet of C code. (And not even a working one.) It isn't going to work at a shell prompt.

There is no shell interface to ioctl, but you can use Perl.

#!/usr/bin/perl
require "sys/ioctl.ph";
if (@ARGV) {
    $ioctl = 0x4b32; # KDSETLED
    $ARGV[0] =~ /^[0-8]+$/ or die "$0: $ARGV[0]: invalid argument";
    $arg = int($ARGV[0]);
} else {
    $ioctl = 0x4b31; # KDGETLED
    $arg = "?";
}
sysopen CONSOLE, "/dev/console", "r" or die "$0: /dev/console: $!\n";
ioctl CONSOLE, $ioctl, $arg or die "$0: ioctl: $!\n";
print ord($arg), "\n" unless @ARGV;

Or Python:

#!/usr/bin/env python
import array, fcntl, os, sys
console = open("/dev/console", "r")
if len(sys.argv) > 1:
    fcntl.ioctl(console, 0x4b32, int(sys.argv[1])) # KDSETLED
else:
    arg = array.array('B', [0xff]) # KDGETLED
    fcntl.ioctl(console, 0x4b31, arg, True)
    print arg[0]

Either program prints the current LED settings if executed with no argument, and changes the LED if passed an argument. The LED settings are expressed as the sum of 1 for Scroll Lock, 2 for Num Lock and 4 for Caps Lock. You can pass the argument 8 to reset the LEDs to correspond to the OS's lock settings.

Instead of calling your own program, you can also call the setleds utility from the standard console tools.

You need to either be logged on a text mode console (not under X and not remotely), or to run as root.

Linux, specific keyboard

You can also turn on and off the LEDs of a particular input device by accessing that device instead of the console device. The ioctl is different: you need to use the evdev interface and send EV_LED events.

The easiest way to use this interface is to install the evdev Python package (pip install evdev).

</dev/input/by-id/usb-_USB_Keyboard-event-kbd \
python -c 'import evdev; dev = evdev.InputDevice("/dev/stdin"); print dev.leds()'

See “Getting and setting LED states” in the tutorial for more examples.

OSX

On Mac OS X, you can access the keyboard LEDs through the HID interface. You can find examples and explanations in Mac OS X Internals or on Psychic Origami.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.