VulcanBoard.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env python3
  2. # Copyright © 2024 Noah Vogt <noah@noahvogt.com>
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. # You should have received a copy of the GNU General Public License
  12. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. # pylint: disable=invalid-name
  14. import threading
  15. import sys
  16. import asyncio
  17. import colorama
  18. from kivy.app import App
  19. from kivy.uix.gridlayout import GridLayout
  20. from kivy.utils import get_color_from_hex
  21. from kivy.config import Config as kvConfig
  22. from kivy.core.window import Window
  23. from kivy.clock import Clock
  24. from util import log, error_exit_gui
  25. from config import (
  26. get_config_path,
  27. ConfigLoader,
  28. Config,
  29. get_state_from_id,
  30. get_state_id_from_exit_code,
  31. )
  32. from ui import AutoResizeButton
  33. class VulcanBoardApp(App):
  34. def build(self):
  35. self.loop = self.ensure_asyncio_loop_running()
  36. self.icon = "icon.jpg"
  37. config_loader = ConfigLoader(get_config_path())
  38. config = config_loader.get_config() # pyright: ignore
  39. if isinstance(config, str):
  40. error_exit_gui(config)
  41. else:
  42. config: Config = config
  43. Window.borderless = config.borderless
  44. kvConfig.set("kivy", "window_icon", "icon.ico")
  45. kvConfig.set("kivy", "exit_on_escape", "0")
  46. button_map = {
  47. (btn["position"][0], btn["position"][1]): btn
  48. for btn in config.buttons
  49. }
  50. layout = GridLayout(
  51. cols=config.columns,
  52. rows=config.rows,
  53. spacing=config.spacing,
  54. padding=config.padding,
  55. )
  56. # Populate grid with buttons and placeholders
  57. for row in range(config.rows):
  58. for col in range(config.columns):
  59. defined_button = button_map.get((row, col))
  60. if defined_button:
  61. states = defined_button.get("states", [])
  62. print("STATES")
  63. print(states)
  64. state_id = [0]
  65. state = get_state_from_id(states, state_id[0])
  66. print("STATE")
  67. print(state)
  68. btn = AutoResizeButton(
  69. text=state.get("txt", ""),
  70. background_color=get_color_from_hex(
  71. state.get("bg_color", "aaaaff")
  72. ),
  73. color=get_color_from_hex(
  74. state.get("fg_color", "ffffff")
  75. ),
  76. halign="center",
  77. valign="middle",
  78. background_normal="",
  79. )
  80. # pylint: disable=no-member
  81. btn.bind( # pyright: ignore
  82. on_release=lambda btn_instance, states=states, state_id=state_id: self.async_task(
  83. self.execute_command_async(
  84. states, state_id, btn_instance
  85. )
  86. )
  87. )
  88. else:
  89. btn = AutoResizeButton(
  90. background_color=get_color_from_hex("cccccc"),
  91. )
  92. layout.add_widget(btn)
  93. return layout
  94. def async_task(self, coroutine):
  95. asyncio.run_coroutine_threadsafe(coroutine, self.loop)
  96. def ensure_asyncio_loop_running(self):
  97. if hasattr(self, "loop"):
  98. return self.loop # Already created
  99. loop = asyncio.new_event_loop()
  100. def run_loop():
  101. asyncio.set_event_loop(loop)
  102. loop.run_forever()
  103. threading.Thread(target=run_loop, daemon=True).start()
  104. return loop
  105. def config_error_exit(self, popup):
  106. popup.dismiss()
  107. sys.exit(1)
  108. async def execute_command_async(
  109. self, states: list, state_id: list[int], btn: AutoResizeButton
  110. ):
  111. new_state_id = get_state_id_from_exit_code(states, state_id[0])
  112. try:
  113. print(states[new_state_id]["cmd"])
  114. process = await asyncio.create_subprocess_shell(
  115. states[new_state_id]["cmd"], shell=True
  116. )
  117. exit_code = await process.wait()
  118. print(f"EXIT {exit_code}")
  119. log(f"Executed command: {states[new_state_id]['cmd']}")
  120. except Exception as e:
  121. exit_code = 1
  122. log(f"Error executing command: {e}", color="yellow")
  123. if len(states) != 1:
  124. state_id[0] = exit_code # pyright: ignore
  125. Clock.schedule_once(
  126. lambda _: self.update_button_feedback(
  127. states, btn, exit_code # pyright: ignore
  128. )
  129. )
  130. def update_button_feedback(
  131. self, states: list, btn: AutoResizeButton, exit_code: int
  132. ):
  133. state = get_state_from_id(
  134. states, get_state_id_from_exit_code(states, exit_code)
  135. )
  136. btn.text = state.get("txt", "")
  137. btn.background_color = get_color_from_hex(
  138. state.get("bg_color", "cc0000")
  139. )
  140. # btn.foreground_color = get_color_from_hex(
  141. # state.get("bg_color", "cc0000")
  142. # )
  143. def start_asyncio_loop():
  144. asyncio.set_event_loop(asyncio.new_event_loop())
  145. asyncio.get_event_loop().run_forever()
  146. if __name__ == "__main__":
  147. colorama.init()
  148. VulcanBoardApp().run()