Basic X Window keyboard and mouse input blocking

Here’s a small Python script using python-xlib that will block the keyboard and mouse input and then display random-colored, random-sized rectangles whenever an event happens. I tested it in Xubuntu 14.10, not sure if it is possible to run on non-Linux platforms. Obviously, you’ll need to install python-xlib. I use virtualenv to do that, but there’s also a “regular” Debian package available.

from Xlib import Xatom, Xutil
from Xlib.display import Display, X
import sys
import signal 
import random

class bunch(dict):
  __getattr__ = dict.__getitem__
  __setattr__ = dict.__setitem__

def check_for_magic_keys(state, event):
  keys = state['keys']
  if event.type == X.KeyPress:
    keys[event.detail] = True
  elif event.type == X.KeyRelease:
    keys[event.detail] = False

  keycode_alt = 64
  keycode_1 = 10
  keycode_delete = 119

  magic_keys = keys.get(keycode_alt) and keys.get(keycode_1) and keys.get(keycode_delete)
  if magic_keys:
    print("Magic keys pressed, exiting")
    return True

  return False

def random_color(screen):
  red = random.randrange(0, 65536)
  green = random.randrange(0, 65536)
  blue = random.randrange(0, 65536)

  return screen.default_colormap.alloc_color(red, green, blue).pixel

def random_rectangle(screen, window):
  x = random.randrange(0, screen.width_in_pixels)
  y = random.randrange(0, screen.height_in_pixels)
  width = random.randrange(0, screen.width_in_pixels - x)
  height = random.randrange(0, screen.height_in_pixels - y)

  window.window.fill_rectangle(
    gc = window.gc,
    x = x,
    y = y,
    width  = width,
    height = height,
  )

def draw(state, event):
  screen = state.display.screen()
  foreground = random_color(screen)
  background = random_color(screen)

  state.window.gc.change(
    foreground = foreground,
    background = background,
  )

  random_rectangle(screen, state.window)

def handle_event(state, event):
  debug = False
  if debug:
    print(event)
    return True

  if check_for_magic_keys(state, event):
    return False

  draw(state, event)
  return True

def grab_keyboard_and_mouse(root):
  root.grab_keyboard(
    owner_events = True,
    pointer_mode = X.GrabModeAsync,
    keyboard_mode = X.GrabModeAsync,
    time = X.CurrentTime
  )
  
  root.grab_pointer(
    owner_events = True,
    event_mask = X.ButtonPressMask | X.ButtonReleaseMask | X.PointerMotionMask,
    pointer_mode = X.GrabModeAsync,
    keyboard_mode = X.GrabModeAsync,
    confine_to = 0,
    cursor = 0,
    time = X.CurrentTime
  )

def create_window(display, root):
  screen = display.screen()

  window = root.create_window(
    x = 0,
    y = 0,
    width = screen.width_in_pixels,
    height = screen.height_in_pixels,
    border_width = 0,
    depth = screen.root_depth)
  
  atom_net_wm_state = display.intern_atom('_NET_WM_STATE', True)
  atom_net_wm_state_fullscreen = display.intern_atom('_NET_WM_STATE_FULLSCREEN', True)

  window.change_property(
    property = atom_net_wm_state,
    type = Xatom.ATOM,
    format = 32,
    data = [atom_net_wm_state_fullscreen], 
  )

  window.set_wm_normal_hints(
    flags = Xutil.PPosition | Xutil.PSize | Xutil.PMinSize,
    min_width = screen.width_in_pixels,
    min_height = screen.height_in_pixels,
  )

  gc = window.create_gc(
    foreground = screen.black_pixel,
    background = screen.white_pixel,
  )

  return bunch(window = window, gc = gc)

def event_loop(state):
  display = state.display

  while True:
    event = display.next_event()
    display.allow_events(
      mode = X.AsyncBoth,
      time = X.CurrentTime)            

    if not handle_event(state, event):
      break

def main():
  display = Display()
  root = display.screen().root

  grab_keyboard_and_mouse(root)
  window = create_window(display, root)
  window.window.map()

  state = bunch(
    display = display,
    root = root,
    window = window,
    keys = bunch())

  # Comment these out after you have confirmed the magic key works
  signal.signal(signal.SIGALRM, lambda a, b: sys.exit(1))
  signal.alarm(4)

  event_loop(state)

if __name__ == '__main__':
  main()

How it works:

  • It creates a window that covers the whole desktop
  • It then grabs both the key and the mouse input
  • Whenever an event happens, it will first check for a magic key
  • If it is a magic key, it will exit
  • If it is not a magic key, it will display a random rectangle and continue to run

The magic key is currently set to Alt + 1 + Del, pressed at the same time. There’s also a small debug section in the event loop that can be enabled by setting debug = True. Printing events like this allows for reconfiguration if needed, e.g. to change the magic key.

The above version has the signal.alarm call – I left this there, so that it exits after 4 seconds while testing. Otherwise, if it doesn’t work, you have no input. Well, actually you do – I found that Alt + F1 works, so you can escape to a console and kill the python process, but while testing I found it easier to just do a timed exit. In order to be used for longer periods, the signal lines should be commented out.