Interrupt a blocking evdev reading
Question: I have a module that captures all reads from a barcode scanner. My problem is
that I can not close it properly. After sending a KeyboardInterrupt or
SystemExit signal, it stops only when I scan a barcode.

I tried to add this method to the BarcodeScanner class, but it still doesn't
work:

    
    
    def interrupt(self):
        """Envoi la demande d'arrêt."""
    
        super(BarcodeScanner, self).interrupt()
        self.device.write(ecodes.EV_KEY, ecodes.KEY_ESC, 0)
        self.device.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0)
    

How can I do that?

**interruptable.py**

    
    
    #!/usr/bin/env python3
    # -*-coding:Utf-8 -*
    
    
    class Interruptable:
        """Classe intégrant une boucle sans fin pouvant être stoppée proprement."""
    
        interruptables = []
    
        def __init__(self):
            """Crée un objet interruptible."""
    
            self._interrupted = False
            Interruptable.interruptables.append(self)
    
        def interrupt(self):
            """Envoi la demande d'arrêt."""
    
            self._interrupted = True
    
        def reset(self):
            """Permet le redémarrage d'un service."""
    
            self._interrupted = False
    
        def interrupted(self):
            """Verifie si la demande d'arrêt à déjà été envoyée."""
    
            return self._interrupted
    

**barcode_scanner.py**

    
    
    #!/usr/bin/env python3
    # -*-coding:Utf-8 -*
    
    from evdev import InputDevice, ecodes, list_devices, categorize
    import signal, sys
    import threading
    from time import sleep
    
    from tymsoft.interruptable import Interruptable
    
    class BarcodeScanner(Interruptable):
        """TODO"""
    
        def __init__(self, device_name='Bar Code', layout='AZERTY_FR'):
            self.modifiers = {
                # 0: None, 29: u'LCTRL', 42: u'LSHFT', 54: u'RSHFT', 56: u'LALT', 97:u'RCTRL', 100: u'RALT'
                0: 0, 29: 1, 42: 2, 54: 3, 56: 4, 97: 5, 100: 6
            }
            self.layouts = {
                'AZERTY_FR': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'&', u'', u'1', u'1', u'', u'', u''],
                    3: [u'é', u'', u'2', u'2', u'', u'', u'~'],
                    4: [u'"', u'', u'3', u'3', u'', u'', u'#'],
                    5: [u'\'', u'', u'4', u'4', u'', u'', u'{'],
                    6: [u'(', u'', u'5', u'5', u'', u'', u'['],
                    7: [u'-', u'', u'6', u'6', u'', u'', u'|'],
                    8: [u'è', u'', u'7', u'7', u'', u'', u'`'],
                    9: [u'_', u'', u'8', u'8', u'', u'', u'\\'],
                    10: [u'ç', u'', u'9', u'9', u'', u'', u'^'],
                    11: [u'à', u'', u'0', u'0', u'', u'', u'@'],
                    12: [u')', u'', u'°', u'°', u'', u'', u']'],
                    13: [u'=', u'', u'+', u'+', u'', u'', u'}'],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'a', u'', u'A', u'A', u'', u'', u''],
                    17: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u'€'],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'^', u'', u'¨', u'¨', u'', u'', u''],
                    27: [u'$', u'', u'£', u'£', u'', u'', u'¤'],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    40: [u'ù', u'', u'%', u'%', u'', u'', u''],
                    41: [u'²', u'', u'', u'', u'', u'', u''],
                    43: [u'*', u'', u'µ', u'µ', u'', u'', u''],
                    44: [u'w', u'', u'W', u'W', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u',', u'', u'?', u'?', u'', u'', u''],
                    51: [u';', u'', u'.', u'.', u'', u'', u''],
                    52: [u':', u'', u'/', u'/', u'', u'', u''],
                    53: [u'!', u'', u'§', u'§', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    86: [u'<', u'', u'>', u'>', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                },
                'QWERTY_US': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'1', u'', u'!', u'!', u'', u'', u''],
                    3: [u'2', u'', u'@', u'@', u'', u'', u''],
                    4: [u'3', u'', u'#', u'#', u'', u'', u''],
                    5: [u'4', u'', u'$', u'$', u'', u'', u''],
                    6: [u'5', u'', u'%', u'%', u'', u'', u''],
                    7: [u'6', u'', u'^', u'^', u'', u'', u''],
                    8: [u'7', u'', u'&', u'&', u'', u'', u''],
                    9: [u'8', u'', u'*', u'*', u'', u'', u''],
                    10: [u'9', u'', u'(', u'(', u'', u'', u''],
                    11: [u'0', u'', u')', u')', u'', u'', u''],
                    12: [u'-', u'', u'_', u'_', u'', u'', u''],
                    13: [u'=', u'', u'+', u'+', u'', u'', u''],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    17: [u'w', u'', u'W', u'W', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u''],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'[', u'', u'{', u'{', u'', u'', u''],
                    27: [u']', u'', u'}', u'}', u'', u'', u''],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'a', u'', u'A', u'A', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u';', u'', u':', u':', u'', u'', u''],
                    40: [u'\'', u'', u'"', u'"', u'', u'', u''],
                    41: [u'`', u'', u'~', u'~', u'', u'', u''],
                    43: [u'\\', u'', u'|', u'|', u'', u'', u''],
                    44: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    51: [u',', u'', u'<', u'<', u'', u'', u''],
                    52: [u'.', u'', u'>', u'>', u'', u'', u''],
                    53: [u'/', u'', u'?', u'?', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                }
            }
            self.eol = [(0, 28), (0, 96), (29, 39)]
    
            self.layout = layout
    
            self.device = None
            devices = map(InputDevice, list_devices())
            for device in devices:
                if device_name in device.name:
                    self.device = InputDevice(device.fn)
                    print('Lecteur de codes-barres : ' + device.name)
                    break
    
            self._listeners = []
            Interruptable.__init__(self)
            self.device.grab()
    
        def start_capture(self):
            """TODO"""
    
            def run():
                barcode = ''
                modifier = 0
                keycode = 0
                for event in self.device.read_loop():
                    if not self.interrupted():
                        if event.type == ecodes.EV_KEY:
                            data = categorize(event)
                            if data.keystate == 1:
                                if data.scancode in self.modifiers:
                                    modifier = data.scancode
                                elif data.scancode in self.layouts[self.layout]:
                                    keycode = data.scancode
                        elif event.type == ecodes.EV_SYN:
                            if (modifier, keycode) in self.eol:
                                if len(barcode) > 0:
                                    for listener in self._listeners:
                                        listener(barcode)
                                    barcode = ''
                            elif keycode != 0:
                                barcode += self.layouts[self.layout][keycode][self.modifiers[modifier]]
                            modifier = 0
                            keycode = 0
                    else:
                        self.device.ungrab()
                        break
    
    
            thread = threading.Thread(target=run, name='barcode_scanner')
            thread.start()
    
        def register_listener(self, callback):
            self._listeners.append(callback)
    


Answer: I solved the issue by replacing the read_loop() method by a loop of
select()+read(). In this way, I could put a timeout on the select.

    
    
    #!/usr/bin/env python3
    # -*-coding:Utf-8 -*
    
    from evdev import InputDevice, ecodes, list_devices, categorize
    from select import select
    import signal, sys
    import threading
    from time import sleep
    
    from tymsoft.interruptable import Interruptable
    
    class BarcodeScanner(Interruptable):
        """TODO"""
    
        def __init__(self, device_name='Bar Code', layout='AZERTY_FR'):
            self.modifiers = {
                # 0: None, 29: u'LCTRL', 42: u'LSHFT', 54: u'RSHFT', 56: u'LALT', 97:u'RCTRL', 100: u'RALT'
                0: 0, 29: 1, 42: 2, 54: 3, 56: 4, 97: 5, 100: 6
            }
            self.layouts = {
                'AZERTY_FR': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'&', u'', u'1', u'1', u'', u'', u''],
                    3: [u'é', u'', u'2', u'2', u'', u'', u'~'],
                    4: [u'"', u'', u'3', u'3', u'', u'', u'#'],
                    5: [u'\'', u'', u'4', u'4', u'', u'', u'{'],
                    6: [u'(', u'', u'5', u'5', u'', u'', u'['],
                    7: [u'-', u'', u'6', u'6', u'', u'', u'|'],
                    8: [u'è', u'', u'7', u'7', u'', u'', u'`'],
                    9: [u'_', u'', u'8', u'8', u'', u'', u'\\'],
                    10: [u'ç', u'', u'9', u'9', u'', u'', u'^'],
                    11: [u'à', u'', u'0', u'0', u'', u'', u'@'],
                    12: [u')', u'', u'°', u'°', u'', u'', u']'],
                    13: [u'=', u'', u'+', u'+', u'', u'', u'}'],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'a', u'', u'A', u'A', u'', u'', u''],
                    17: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u'€'],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'^', u'', u'¨', u'¨', u'', u'', u''],
                    27: [u'$', u'', u'£', u'£', u'', u'', u'¤'],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    40: [u'ù', u'', u'%', u'%', u'', u'', u''],
                    41: [u'²', u'', u'', u'', u'', u'', u''],
                    43: [u'*', u'', u'µ', u'µ', u'', u'', u''],
                    44: [u'w', u'', u'W', u'W', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u',', u'', u'?', u'?', u'', u'', u''],
                    51: [u';', u'', u'.', u'.', u'', u'', u''],
                    52: [u':', u'', u'/', u'/', u'', u'', u''],
                    53: [u'!', u'', u'§', u'§', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    86: [u'<', u'', u'>', u'>', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                },
                'QWERTY_US': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'1', u'', u'!', u'!', u'', u'', u''],
                    3: [u'2', u'', u'@', u'@', u'', u'', u''],
                    4: [u'3', u'', u'#', u'#', u'', u'', u''],
                    5: [u'4', u'', u'$', u'$', u'', u'', u''],
                    6: [u'5', u'', u'%', u'%', u'', u'', u''],
                    7: [u'6', u'', u'^', u'^', u'', u'', u''],
                    8: [u'7', u'', u'&', u'&', u'', u'', u''],
                    9: [u'8', u'', u'*', u'*', u'', u'', u''],
                    10: [u'9', u'', u'(', u'(', u'', u'', u''],
                    11: [u'0', u'', u')', u')', u'', u'', u''],
                    12: [u'-', u'', u'_', u'_', u'', u'', u''],
                    13: [u'=', u'', u'+', u'+', u'', u'', u''],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    17: [u'w', u'', u'W', u'W', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u''],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'[', u'', u'{', u'{', u'', u'', u''],
                    27: [u']', u'', u'}', u'}', u'', u'', u''],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'a', u'', u'A', u'A', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u';', u'', u':', u':', u'', u'', u''],
                    40: [u'\'', u'', u'"', u'"', u'', u'', u''],
                    41: [u'`', u'', u'~', u'~', u'', u'', u''],
                    43: [u'\\', u'', u'|', u'|', u'', u'', u''],
                    44: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    51: [u',', u'', u'<', u'<', u'', u'', u''],
                    52: [u'.', u'', u'>', u'>', u'', u'', u''],
                    53: [u'/', u'', u'?', u'?', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                }
            }
            self.eol = [(0, 28), (0, 96), (29, 39)]
    
            self.layout = layout
    
            self.device = None
            devices = map(InputDevice, list_devices())
            for device in devices:
                if device_name in device.name:
                    self.device = InputDevice(device.fn)
                    print('Lecteur de codes-barres : ' + device.name)
                    break
    
            self._listeners = []
            Interruptable.__init__(self)
            self.device.grab()
    
        def start_capture(self):
            """TODO"""
    
            def run():
                barcode = ''
                modifier = 0
                keycode = 0
                while not self.interrupted():
                    select([self.device], [], [], 0.25)
                    try:
                        for event in self.device.read():
                                if event.type == ecodes.EV_KEY:
                                    data = categorize(event)
                                    if data.keystate == 1:
                                        if data.scancode in self.modifiers:
                                            modifier = data.scancode
                                        elif data.scancode in self.layouts[self.layout]:
                                            keycode = data.scancode
                                elif event.type == ecodes.EV_SYN:
                                    if (modifier, keycode) in self.eol:
                                        if len(barcode) > 0:
                                            for listener in self._listeners:
                                                listener(barcode)
                                            barcode = ''
                                    elif keycode != 0:
                                        barcode += self.layouts[self.layout][keycode][self.modifiers[modifier]]
                                    modifier = 0
                                    keycode = 0
                    except BlockingIOError:
                        pass
                self.device.ungrab()
    
            thread = threading.Thread(target=run, name='barcode_scanner')
            thread.start()
    
        def register_listener(self, callback):
            self._listeners.append(callback)
    

