diff --git a/devdeck/controls/clock_control.py b/devdeck/controls/clock_control.py index be59a37..8e2e547 100644 --- a/devdeck/controls/clock_control.py +++ b/devdeck/controls/clock_control.py @@ -1,6 +1,5 @@ -import threading +import asyncio from datetime import datetime -from time import sleep from devdeck_core.controls.deck_control import DeckControl @@ -9,34 +8,34 @@ class ClockControl(DeckControl): def __init__(self, key_no, **kwargs): super().__init__(key_no, **kwargs) - self.thread = None self.running = False - def initialize(self): - self.thread = threading.Thread(target=self._update_display) + async def initialize(self): + asyncio.create_task(self.periodic_task(1, self._update_display)) self.running = True - self.thread.start() - def _update_display(self): + async def periodic_task(self, interval, task): while self.running is True: - with self.deck_context() as context: - now = datetime.now() - - with context.renderer() as r: - r.text(now.strftime("%H:%M"))\ - .center_horizontally() \ - .center_vertically(-100) \ - .font_size(150)\ - .end() - r.text(now.strftime("%a, %d %b")) \ - .center_horizontally() \ - .center_vertically(100) \ - .font_size(75) \ - .end() - sleep(1) - - def dispose(self): + await task() + await asyncio.sleep(interval) + + async def _update_display(self): + with self.deck_context() as context: + now = datetime.now() + + with context.renderer() as r: + r.text(now.strftime("%H:%M")) \ + .center_horizontally() \ + .center_vertically(-100) \ + .font_size(150) \ + .end() + r.text(now.strftime("%a, %d %b")) \ + .center_horizontally() \ + .center_vertically(100) \ + .font_size(75) \ + .end() + + async def dispose(self): self.running = False if self.thread: self.thread.join() - diff --git a/devdeck/controls/command_control.py b/devdeck/controls/command_control.py index fdbabe4..afd3d5a 100644 --- a/devdeck/controls/command_control.py +++ b/devdeck/controls/command_control.py @@ -10,12 +10,12 @@ def __init__(self, key_no, **kwargs): self.__logger = logging.getLogger('devdeck') super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): with self.deck_context() as context: with context.renderer() as r: r.image(os.path.expanduser(self.settings['icon'])).end() - def pressed(self): + async def pressed(self): try: Popen(self.settings['command'], stdout=DEVNULL, stderr=DEVNULL) except Exception as ex: diff --git a/devdeck/controls/mic_mute_control.py b/devdeck/controls/mic_mute_control.py index c9f4c8f..ed9d3cc 100644 --- a/devdeck/controls/mic_mute_control.py +++ b/devdeck/controls/mic_mute_control.py @@ -13,12 +13,12 @@ def __init__(self, key_no, **kwargs): self.__logger = logging.getLogger('devdeck') super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): if self.pulse is None: self.pulse = pulsectl.Pulse('MicMuteControl') self.__render_icon() - def pressed(self): + async def pressed(self): mic = self.__get_mic() if mic is None: return diff --git a/devdeck/controls/name_list_control.py b/devdeck/controls/name_list_control.py index ff0874b..2952fb0 100644 --- a/devdeck/controls/name_list_control.py +++ b/devdeck/controls/name_list_control.py @@ -9,13 +9,13 @@ def __init__(self, key_no, **kwargs): self.name_index = 0 super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): self.name_index = 0 with self.deck_context() as context: with context.renderer() as r: r.image(os.path.join(os.path.dirname(__file__), "../assets/font-awesome", 'users.png')).end() - def pressed(self): + async def pressed(self): if 'names' not in self.settings or len(self.settings['names']) == 0: return with self.deck_context() as context: diff --git a/devdeck/controls/timer_control.py b/devdeck/controls/timer_control.py index 5af7a96..fe69dcc 100644 --- a/devdeck/controls/timer_control.py +++ b/devdeck/controls/timer_control.py @@ -14,12 +14,12 @@ def __init__(self, key_no, **kwargs): self.thread = None super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): with self.deck_context() as context: with context.renderer() as r: r.image(os.path.join(os.path.dirname(__file__), "../assets/font-awesome", 'stopwatch.png')).end() - def pressed(self): + async def pressed(self): if self.start_time is None: self.start_time = datetime.datetime.now() self.thread = threading.Thread(target=self._update_display) diff --git a/devdeck/controls/volume_level_control.py b/devdeck/controls/volume_level_control.py index 5ba5885..b3fe727 100644 --- a/devdeck/controls/volume_level_control.py +++ b/devdeck/controls/volume_level_control.py @@ -14,13 +14,13 @@ def __init__(self, key_no, **kwargs): self.__logger = logging.getLogger('devdeck') super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): if self.pulse is None: self.pulse = pulsectl.Pulse('VolumeLevelControl') self.volume = float(self.settings['volume']) / 100 self.__render_icon() - def pressed(self): + async def pressed(self): output = self.__get_output() if output is None: return diff --git a/devdeck/controls/volume_mute_control.py b/devdeck/controls/volume_mute_control.py index a7d05cf..b51bbac 100644 --- a/devdeck/controls/volume_mute_control.py +++ b/devdeck/controls/volume_mute_control.py @@ -13,11 +13,11 @@ def __init__(self, key_no, **kwargs): self.__logger = logging.getLogger('devdeck') super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): self.pulse = pulsectl.Pulse() self.__render_icon() - def pressed(self): + async def pressed(self): output = self.__get_output() if output is None: return diff --git a/devdeck/deck_context.py b/devdeck/deck_context.py index 8f138dd..2bcf484 100644 --- a/devdeck/deck_context.py +++ b/devdeck/deck_context.py @@ -30,11 +30,11 @@ def set_key_image_native(self, key_no, icon): image = PILHelper.to_native_format(self.__deck, icon) self.__deck.set_key_image(key_no, image) - def set_active_deck(self, deck): - self.__devdeck.set_active_deck(deck) + async def set_active_deck(self, deck): + await self.__devdeck.set_active_deck(deck) - def pop_active_deck(self): - self.__devdeck.pop_active_deck() + async def pop_active_deck(self): + await self.__devdeck.pop_active_deck() def renderer(self, key_no): return RendererManager(key_no, self) diff --git a/devdeck/deck_manager.py b/devdeck/deck_manager.py index 89b1bf9..9ba5023 100644 --- a/devdeck/deck_manager.py +++ b/devdeck/deck_manager.py @@ -9,38 +9,38 @@ def __init__(self, deck): self.__deck = deck self.__deck.set_brightness(50) self.__deck.reset() - self.__deck.set_key_callback(self.key_callback) + self.__deck.set_key_callback_async(self.key_callback) self.decks = [] - def set_active_deck(self, deck): + async def set_active_deck(self, deck): self.__logger.info("Setting active deck: %s", type(deck).__name__) for deck_itr in self.decks: - deck_itr.clear_deck_context() + await deck_itr.clear_deck_context() self.decks.append(deck) - self.get_active_deck().render(DeckContext(self, self.__deck)) + await self.get_active_deck().render(DeckContext(self, self.__deck)) def get_active_deck(self): if not self.decks: return None return self.decks[-1] - def pop_active_deck(self): + async def pop_active_deck(self): if len(self.decks) == 1: return popped_deck = self.decks.pop() self.__logger.info("Exiting deck: %s", type(popped_deck).__name__) - popped_deck.clear_deck_context() - self.get_active_deck().render(DeckContext(self, self.__deck)) + await popped_deck.clear_deck_context() + await self.get_active_deck().render(DeckContext(self, self.__deck)) - def key_callback(self, deck, key, state): + async def key_callback(self, deck, key, state): if state: - self.get_active_deck().pressed(key) + await self.get_active_deck().pressed(key) else: - self.get_active_deck().released(key) + await self.get_active_deck().released(key) - def close(self): + async def close(self): keys = self.__deck.key_count() for deck in self.decks: - deck.dispose() + await deck.dispose() for key_no in range(keys): self.__deck.set_key_image(key_no, None) \ No newline at end of file diff --git a/devdeck/decks/single_page_deck_controller.py b/devdeck/decks/single_page_deck_controller.py index 78b1c03..ca0025e 100644 --- a/devdeck/decks/single_page_deck_controller.py +++ b/devdeck/decks/single_page_deck_controller.py @@ -12,7 +12,7 @@ def __init__(self, key_no, **kwargs): self.settings = kwargs super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): with self.deck_context() as context: with context.renderer() as r: r.image(os.path.expanduser(self.settings['icon'])).end() diff --git a/devdeck/decks/volume_deck.py b/devdeck/decks/volume_deck.py index dc89edd..aa451d9 100644 --- a/devdeck/decks/volume_deck.py +++ b/devdeck/decks/volume_deck.py @@ -11,7 +11,7 @@ def __init__(self, key_no, **kwargs): self.__logger = logging.getLogger('devdeck') super().__init__(key_no, **kwargs) - def initialize(self): + async def initialize(self): self.pulse = pulsectl.Pulse() self.__render_icon() diff --git a/devdeck/main.py b/devdeck/main.py index 1747404..1986e66 100644 --- a/devdeck/main.py +++ b/devdeck/main.py @@ -1,3 +1,4 @@ +import asyncio import logging import os import sys @@ -13,7 +14,7 @@ from devdeck.settings.validation_error import ValidationError -def main(): +async def main(): os.makedirs(os.path.join(str(Path.home()), '.devdeck'), exist_ok=True) root = logging.getLogger('devdeck') @@ -76,22 +77,14 @@ def main(): # Instantiate deck main_deck = deck_settings.deck_class()(None, **deck_settings.settings()) - deck_manager.set_active_deck(main_deck) - - for t in threading.enumerate(): - if t is threading.currentThread(): - continue - - if t.is_alive(): - try: - t.join() - except KeyboardInterrupt as ex: - deck_manager.close() - deck.close() + await deck_manager.set_active_deck(main_deck) + return deck_manager if len(streamdecks) == 0: root.info("No streamdecks detected, exiting.") if __name__ == '__main__': - main() + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.run_forever() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ec2d440..11472d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ assertpy cerberus -devdeck-core==1.0.7 emoji jsonschema pillow diff --git a/tests/devdeck/test_deck_manager.py b/tests/devdeck/test_deck_manager.py index ab9acea..7cf705e 100644 --- a/tests/devdeck/test_deck_manager.py +++ b/tests/devdeck/test_deck_manager.py @@ -8,48 +8,48 @@ class TestDeckManager: @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') - def test_set_active_deck(self, first_mock_deck, second_mock_deck): + async def test_set_active_deck(self, first_mock_deck, second_mock_deck): dev_deck = DeckManager(first_mock_deck) assert_that(dev_deck.get_active_deck()).is_none() # Sets active deck - dev_deck.set_active_deck(first_mock_deck) + await dev_deck.set_active_deck(first_mock_deck) assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck) # Set active deck to another instance - dev_deck.set_active_deck(second_mock_deck) + await dev_deck.set_active_deck(second_mock_deck) first_mock_deck.clear_deck_context.assert_called_once() assert_that(dev_deck.get_active_deck()).is_equal_to(second_mock_deck) @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') - def test_pop_active_deck(self, first_mock_deck, second_mock_deck): + async def test_pop_active_deck(self, first_mock_deck, second_mock_deck): dev_deck = DeckManager(first_mock_deck) # Two active decks and the second is active - dev_deck.set_active_deck(first_mock_deck) - dev_deck.set_active_deck(second_mock_deck) + await dev_deck.set_active_deck(first_mock_deck) + await dev_deck.set_active_deck(second_mock_deck) assert_that(dev_deck.get_active_deck()).is_equal_to(second_mock_deck) - dev_deck.pop_active_deck() + await dev_deck.pop_active_deck() second_mock_deck.clear_deck_context.assert_called_once() assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck) @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') - def test_pop_active_deck_does_not_remove_root_deck(self, first_mock_deck): + async def test_pop_active_deck_does_not_remove_root_deck(self, first_mock_deck): dev_deck = DeckManager(first_mock_deck) - dev_deck.set_active_deck(first_mock_deck) + await dev_deck.set_active_deck(first_mock_deck) assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck) # Root deck is still active even if a pop is attempted - dev_deck.pop_active_deck() + await dev_deck.pop_active_deck() assert_that(dev_deck.get_active_deck()).is_equal_to(first_mock_deck) @mock.patch('StreamDeck.Devices.StreamDeck.StreamDeck') - def test_key_callback_propogates_to_active_deck(self, first_mock_deck): + async def test_key_callback_propogates_to_active_deck(self, first_mock_deck): dev_deck = DeckManager(first_mock_deck) - dev_deck.set_active_deck(first_mock_deck) + await dev_deck.set_active_deck(first_mock_deck) # Pressed dev_deck.key_callback(first_mock_deck, 12, True)