소스 검색

1-2 states of length and cmd only works + add button dimension to error msg

Noah Vogt 2 달 전
부모
커밋
65900ebaa5
3개의 변경된 파일147개의 추가작업 그리고 30개의 파일을 삭제
  1. 85 16
      VulcanBoard.py
  2. 1 0
      config/__init__.py
  3. 61 14
      config/load.py

+ 85 - 16
VulcanBoard.py

@@ -16,8 +16,9 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 # pylint: disable=invalid-name
-import subprocess
+import threading
 import sys
+import asyncio
 
 import colorama
 
@@ -26,14 +27,22 @@ from kivy.uix.gridlayout import GridLayout
 from kivy.utils import get_color_from_hex
 from kivy.config import Config as kvConfig
 from kivy.core.window import Window
+from kivy.clock import Clock
 
 from util import log, error_exit_gui
-from config import get_config_path, ConfigLoader, Config
+from config import (
+    get_config_path,
+    ConfigLoader,
+    Config,
+    get_state_from_id,
+    get_state_id_from_exit_code,
+)
 from ui import AutoResizeButton
 
 
 class VulcanBoardApp(App):
     def build(self):
+        self.loop = self.ensure_asyncio_loop_running()
         self.icon = "icon.jpg"
         config_loader = ConfigLoader(get_config_path())
         config = config_loader.get_config()  # pyright: ignore
@@ -63,24 +72,33 @@ class VulcanBoardApp(App):
                 for col in range(config.columns):
                     defined_button = button_map.get((row, col))
                     if defined_button:
+                        states = defined_button.get("states", [])
+                        print("STATES")
+                        print(states)
+                        state_id = [0]
+                        state = get_state_from_id(states, state_id[0])
+                        print("STATE")
+                        print(state)
+
                         btn = AutoResizeButton(
-                            text=defined_button.get("txt", ""),
+                            text=state.get("txt", ""),
                             background_color=get_color_from_hex(
-                                defined_button.get("bg_color", "aaaaff")
+                                state.get("bg_color", "aaaaff")
                             ),
                             color=get_color_from_hex(
-                                defined_button.get("fg_color", "ffffff")
+                                state.get("fg_color", "ffffff")
                             ),
                             halign="center",
                             valign="middle",
                             background_normal="",
                         )
 
-                        cmd = defined_button.get("cmd", "")
                         # pylint: disable=no-member
                         btn.bind(  # pyright: ignore
-                            on_release=lambda _, cmd=cmd: self.execute_command_async(
-                                cmd
+                            on_release=lambda btn_instance, states=states, state_id=state_id: self.async_task(
+                                self.execute_command_async(
+                                    states, state_id, btn_instance
+                                )
                             )
                         )
                     else:
@@ -91,19 +109,70 @@ class VulcanBoardApp(App):
 
             return layout
 
+    def async_task(self, coroutine):
+        asyncio.run_coroutine_threadsafe(coroutine, self.loop)
+
+    def ensure_asyncio_loop_running(self):
+        if hasattr(self, "loop"):
+            return self.loop  # Already created
+
+        loop = asyncio.new_event_loop()
+
+        def run_loop():
+            asyncio.set_event_loop(loop)
+            loop.run_forever()
+
+        threading.Thread(target=run_loop, daemon=True).start()
+        return loop
+
     def config_error_exit(self, popup):
         popup.dismiss()
         sys.exit(1)
 
-    def execute_command_async(self, cmd):
-        if cmd:
-            try:
-                subprocess.Popen(  # pylint: disable=consider-using-with
-                    cmd, shell=True
+    async def execute_command_async(
+        self, states: list, state_id: list[int], btn: AutoResizeButton
+    ):
+        new_state_id = get_state_id_from_exit_code(states, state_id[0])
+        try:
+            print(states[new_state_id]["cmd"])
+            process = await asyncio.create_subprocess_shell(
+                states[new_state_id]["cmd"], shell=True
+            )
+            exit_code = await process.wait()
+            print(f"EXIT {exit_code}")
+            log(f"Executed command: {states[new_state_id]['cmd']}")
+        except Exception as e:
+            exit_code = 1
+            log(f"Error executing command: {e}", color="yellow")
+
+        if len(states) != 1:
+            state_id[0] = exit_code  # pyright: ignore
+
+            Clock.schedule_once(
+                lambda _: self.update_button_feedback(
+                    states, btn, exit_code  # pyright: ignore
                 )
-                log(f"Executed command: {cmd}")
-            except Exception as e:
-                log(f"Error executing command: {e}", color="yellow")
+            )
+
+    def update_button_feedback(
+        self, states: list, btn: AutoResizeButton, exit_code: int
+    ):
+        state = get_state_from_id(
+            states, get_state_id_from_exit_code(states, exit_code)
+        )
+
+        btn.text = state.get("txt", "")
+        btn.background_color = get_color_from_hex(
+            state.get("bg_color", "cc0000")
+        )
+        # btn.foreground_color = get_color_from_hex(
+        #     state.get("bg_color", "cc0000")
+        #         )
+
+
+def start_asyncio_loop():
+    asyncio.set_event_loop(asyncio.new_event_loop())
+    asyncio.get_event_loop().run_forever()
 
 
 if __name__ == "__main__":

+ 1 - 0
config/__init__.py

@@ -16,3 +16,4 @@
 from .path import get_config_path
 from .classes import Config
 from .load import ConfigLoader
+from .load import get_state_from_id, get_state_id_from_exit_code

+ 61 - 14
config/load.py

@@ -23,6 +23,29 @@ from .classes import Config
 from .validate import is_valid_hexcolor
 
 
+def get_state_from_id(states: list, state_id: int) -> dict:
+    for state in states:
+        possible_id = state.get("id", None)
+        if isinstance(possible_id, int):
+            if possible_id == state_id:
+                return state
+
+    return {}
+
+
+def get_state_id_from_exit_code(states: list, exit_code: int) -> int:
+    found_state_id = False
+    for found_states in states:
+        if found_states["id"] == exit_code:
+            found_state_id = True
+            break
+
+    if not found_state_id:
+        exit_code = 1
+
+    return exit_code
+
+
 @dataclass
 class ConfigLoader:
     config_path: str
@@ -84,29 +107,53 @@ class ConfigLoader:
                 or (0 > dimensions[1] or dimensions[1] > self.columns - 1)
             ):
                 raise CustomException(
-                    f"invalid button 'position' subentry: '{dimensions}'"
+                    f"invalid 'position' subentry: '{dimensions}'"
                 )
 
-            for entry in ("txt", "cmd"):
-                if not isinstance(result := button.get(entry, ""), str):
-                    raise CustomException(
-                        f"invalid button '{entry}' subentry: '{result}'"
-                    )
+            btn_dims = f"button ({dimensions[0]}, {dimensions[1]})"
 
-            if not isinstance(
-                bg_color := button.get("bg_color", "cccccc"), str
-            ) or not is_valid_hexcolor(bg_color):
+            if not isinstance(states := button.get("states", ""), list):
                 raise CustomException(
-                    f"invalid button 'bg_color' subentry: '{bg_color}'"
+                    f"invalid {btn_dims} 'states' subentry: '{states}'"
                 )
 
-            if not isinstance(
-                fg_color := button.get("fg_color", "ffffff"), str
-            ) or not is_valid_hexcolor(bg_color):
+            if len(states) == 0:
                 raise CustomException(
-                    f"invalid button 'fg_color' subentry: '{fg_color}'"
+                    f"invalid {btn_dims} 'states' subentry: list cannot be empty"
                 )
 
+            defined_state_ids = set()
+            for state in states:
+                if not (
+                    isinstance(state, dict)
+                    or isinstance(state.get("id", None), int)
+                    or isinstance(state.get("cmd", None), str)
+                    or isinstance(state.get("txt", None), str)
+                ):
+                    raise CustomException(
+                        f"invalid {btn_dims}: invalid state detected"
+                    )
+                defined_state_ids.add(state.get("id", None))
+
+                if not isinstance(
+                    bg_color := state.get("bg_color", "cccccc"), str
+                ) or not is_valid_hexcolor(bg_color):
+                    raise CustomException(
+                        f"invalid {btn_dims} 'bg_color' subentry: '{bg_color}'"
+                    )
+
+                if not isinstance(
+                    fg_color := state.get("fg_color", "ffffff"), str
+                ) or not is_valid_hexcolor(fg_color):
+                    raise CustomException(
+                        f"invalid {btn_dims} 'fg_color' subentry: '{fg_color}'"
+                    )
+
+            print(defined_state_ids)
+            # TODO: add const or rethink needed state id's
+            if not 0 in defined_state_ids:
+                raise CustomException(f"invalid {btn_dims}: missing state id 0")
+
     def __validate_dimensions(self) -> None:
         for dimension in (self.columns, self.rows):
             if not isinstance(dimension, int) or (dimension <= 0):