VulcanBoard.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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. from os import path, getenv, name
  15. import subprocess
  16. import sys
  17. import yaml
  18. from termcolor import colored
  19. import colorama
  20. from kivy.app import App
  21. from kivy.uix.button import Button
  22. from kivy.uix.gridlayout import GridLayout
  23. from kivy.utils import get_color_from_hex
  24. def error_msg(msg: str):
  25. print(colored("[*] Error: {}".format(msg), "red"))
  26. sys.exit(1)
  27. def log(message: str, color="green") -> None:
  28. print(colored("[*] {}".format(message), color)) # pyright: ignore
  29. def get_config_path():
  30. if name == "nt":
  31. return path.join(getenv("APPDATA", ""), "VulcanBoard", "config.yml")
  32. xdg_config_home = getenv("XDG_CONFIG_HOME", path.expanduser("~/.config"))
  33. return path.join(xdg_config_home, "VulcanBoard", "config.yml")
  34. def load_config( # pylint: disable=inconsistent-return-statements
  35. config_file_path,
  36. ):
  37. try:
  38. with open(config_file_path, "r", encoding="utf-8") as config_reader:
  39. return yaml.safe_load(config_reader)
  40. except (FileNotFoundError, PermissionError, IOError) as error:
  41. error_msg(
  42. f"Error: Could not access config file at {config_file_path}. Reason: {error}"
  43. )
  44. except yaml.YAMLError as error:
  45. error_msg(f"Error parsing config file. Reason: {error}")
  46. class VulcanBoardApp(App):
  47. def build(self) -> GridLayout:
  48. config = load_config(get_config_path())
  49. columns = config.get("COLUMNS")
  50. rows = config.get("ROWS")
  51. self.validate_dimensions(columns, rows)
  52. buttons_config = config.get("BUTTONS", [])
  53. button_map = {
  54. (btn["button"][0], btn["button"][1]): btn for btn in buttons_config
  55. }
  56. layout = GridLayout(cols=columns, rows=rows, spacing=5, padding=5)
  57. # Populate grid with buttons and placeholders
  58. for row in range(rows):
  59. for col in range(columns):
  60. defined_button = button_map.get((row, col))
  61. if defined_button:
  62. btn = Button(
  63. text=defined_button.get("txt", ""),
  64. background_color=get_color_from_hex(
  65. defined_button.get("color", "ffffff")
  66. ),
  67. font_size=defined_button.get("fontsize", 14),
  68. halign="center",
  69. valign="middle",
  70. text_size=(
  71. None,
  72. None,
  73. ), # Enable text alignment within button
  74. )
  75. # pylint: disable=no-member
  76. cmd = defined_button.get("cmd", "")
  77. # pylint: disable=no-member
  78. btn.bind( # pyright: ignore
  79. on_release=lambda instance, cmd=cmd: self.execute_command_async(
  80. cmd
  81. )
  82. )
  83. else:
  84. btn = Button(
  85. background_color=get_color_from_hex("cccccc"),
  86. )
  87. layout.add_widget(btn)
  88. return layout
  89. def validate_dimensions(self, columns, rows):
  90. for dimension in (columns, rows):
  91. if not isinstance(dimension, int) and (dimension <= 0):
  92. error_msg(f"invalid dimension: {dimension}")
  93. def execute_command_async(self, cmd):
  94. if cmd:
  95. try:
  96. subprocess.Popen( # pylint: disable=consider-using-with
  97. cmd, shell=True
  98. )
  99. log(f"Executed command: {cmd}")
  100. except Exception as e:
  101. log(f"Error executing command: {e}", color="yellow")
  102. if __name__ == "__main__":
  103. colorama.init()
  104. VulcanBoardApp().run()