blob: 35308f41da6841d0a6b2ad8a9d5037ccabd6e796 [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Routines to modify keyboard LED state."""
import ast
import fcntl
import logging
import os
import sys
import threading
from six.moves import xrange
# Constants from /usr/include/linux/kd.h.
KDSETLED = 0x4B32
LED_SCR = 1
LED_NUM = 2
LED_CAP = 4
# Resets LEDs to the default values (console_ioctl.h says that any higher-
# order bit than LED_CAP will do).
LED_RESET = 8
# Number of VTs on which to set the keyboard LEDs.
MAX_VTS = 8
# Set of FDs of all TTYs on which to set LEDs. Lazily initialized by SetLeds.
_tty_fds = None
_tty_fds_lock = threading.Lock()
def SetLeds(state):
"""Sets the current LEDs on VTs [0,MAX_VTS) to the given state.
Errors are ignored.
(We set the LED on all VTs because /dev/console may not work reliably under
the combination of X and autotest.)
Args:
pattern: A bitwise OR of zero or more of LED_SCR, LED_NUM, and LED_CAP.
Returns:
True if able to set at least one LED, and False otherwise.
"""
global _tty_fds # pylint: disable=global-statement
with _tty_fds_lock:
if _tty_fds is None:
_tty_fds = []
for tty in xrange(MAX_VTS):
dev = '/dev/tty%d' % tty
try:
_tty_fds.append(os.open(dev, os.O_RDWR))
except Exception:
logging.exception('Unable to open %s', dev)
if not _tty_fds:
return False
for fd in _tty_fds:
try:
fcntl.ioctl(fd, KDSETLED, state)
except Exception:
pass
return True
class Blinker(object):
"""Blinks LEDs asynchronously according to a particular pattern.
Start() and Stop() are not thread-safe and must be invoked from the same
thread.
This can also be used as a context manager:
with leds.Blinker(...):
...do something that will take a while...
"""
thread = None
def __init__(self, pattern):
"""Constructs the blinker (but does not start it).
Args:
pattern: A list of tuples. Each element is (state, duration),
where state contains the LEDs that should be lit (a bitwise
OR of LED_SCR, LED_NUM, and/or LED_CAP). For example,
((LED_SCR|LED_NUM|LED_CAP, .2),
(0, .05))
would turn all LEDs on for .2 s, then all off for 0.05 s,
ad infinitum.
"""
self.pattern = pattern
self.done = threading.Event()
def Start(self):
"""Starts blinking in a separate thread until Stop is called.
May only be invoked once.
"""
assert not self.thread
self.thread = threading.Thread(target=self._Run)
self.thread.start()
def Stop(self):
"""Stops blinking."""
self.done.set()
if self.thread:
self.thread.join()
self.thread = None
def __enter__(self):
self.Start()
def __exit__(self, exc_type, exc_value, traceback):
del exc_type, exc_value, traceback # Unused.
self.Stop()
def _Run(self):
while True: # Repeat pattern forever
for state, duration in self.pattern:
if not SetLeds(state):
return # Failure, end this thread
self.done.wait(duration)
if self.done.is_set():
SetLeds(LED_RESET)
return
def main():
"""Blinks the pattern in sys.argv[1] if present, or the famous theme from
William Tell otherwise.
"""
if len(sys.argv) > 1:
blinker = Blinker(ast.literal_eval(sys.argv[1]))
else:
DURATION_SCALE = .125
def Blip(state, duration=1):
return [(state, duration * .6 * DURATION_SCALE),
(0, duration * .4 * DURATION_SCALE)]
blinker = Blinker(
2 * (2 * Blip(LED_NUM) + Blip(LED_NUM, 2)) + 2 * Blip(LED_NUM) +
Blip(LED_CAP, 2) + Blip(LED_SCR, 2) + Blip(LED_CAP | LED_SCR, 2) +
2 * Blip(LED_NUM) + Blip(LED_NUM, 2) + 2 * Blip(LED_NUM) +
Blip(LED_CAP, 2) + 2 * Blip(LED_SCR) + Blip(LED_CAP, 2) +
Blip(LED_NUM, 2) + Blip(LED_CAP | LED_NUM, 2)
)
with blinker:
# Wait for newline, and then quit gracefully
sys.stdin.readline()
if __name__ == '__main__':
main()