Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fda9e38
Localize open about in the header bar hamburger menu
Core447 Jan 3, 2024
f7928a0
Remove unmaintained spanish locals
Core447 Jan 4, 2024
39d202d
Add wallpaper pack support to store
Core447 Jan 6, 2024
db251b8
Make wallpapers installable
Core447 Jan 7, 2024
59c2f6e
Localize store main page
Core447 Jan 8, 2024
c1155d8
Add description to readme
Core447 Jan 10, 2024
0dcffc9
Add wallpaper pack manager to backend
Core447 Jan 11, 2024
0398e36
Change version in about dialog
Core447 Jan 12, 2024
2f8173a
Change application id toplevel domain from com to dev
Core447 Jan 12, 2024
c7b4004
Add helper methods to allow plugins to easily run front and backend i…
Core447 Jan 14, 2024
81d5a22
Run install script on plugin installation
Core447 Jan 14, 2024
62e43ae
Update requirements.txt
Core447 Jan 14, 2024
654eaf3
Fix: PluginBase wrong backend launch command
Core447 Jan 14, 2024
4eaccb6
Fix: PluginBase backend access error if not started
Core447 Jan 14, 2024
f7c573f
Fix: Wrong arguments error for actionbase backend
Core447 Jan 17, 2024
d570476
Add app version to globals
Core447 Jan 20, 2024
e780611
Fix: Error if plugin backend is access without being connected
Core447 Jan 21, 2024
9361e2f
Add controller to page change callback call
Core447 Jan 29, 2024
1b576ca
Fix: StoreBackend clone_repo error if local path contains space
Core447 Feb 10, 2024
9ea019f
Fix: Installed wallpapers not showing as installed
Core447 Feb 10, 2024
2f3aee7
Add info page to wallpaper packs in store
Core447 Feb 10, 2024
99ec186
Add info page to plugins in store
Core447 Feb 10, 2024
87d79ea
Fix: Store crash if no correct attribution provided
Core447 Feb 10, 2024
1898297
Fix: StorePages set_url not found
Core447 Feb 10, 2024
37c9d59
Fix: wrong spelling of badge
Core447 Feb 10, 2024
e577d9d
Add image fill modes to generate_key_image
Core447 Feb 10, 2024
07e79ce
AssetManager: Load icons from packs dynamically
Core447 Feb 10, 2024
9aa47ce
Fix: Wrong ActionBase launch_backend command
Core447 Feb 10, 2024
4be5c7a
PluginManager keep track of plugin backends
Core447 Feb 10, 2024
8ca86bd
Add plugin backends to PluginManger
Core447 Feb 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,7 @@ Assets/AssetManager/Assets.json
pages/
settings/

Pyro5

# Keep .gitkeep files
!/**/.gitkeep
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# StreamController
# StreamController

A nice GTK4 app to control your StreamDecks.
11 changes: 9 additions & 2 deletions globals.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Pyro5.api
import os
from typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -9,9 +11,11 @@
from src.backend.SettingsManager import SettingsManager
from src.backend.DeckManagement.DeckManager import DeckManager
from src.backend.PluginManager.PluginManager import PluginManager
from backend.IconPackManagement.IconPackManager import IconPackManager
from src.backend.IconPackManagement.IconPackManager import IconPackManager
from src.backend.WallpaperPackManagement.WallpaperPackManager import WallpaperPackManager
from src.windows.Store.StoreBackend import StoreBackend

top_level_dir:str = os.path.dirname(__file__)
lm:"LocaleManager" = None
media_manager:"MediaManager" = None #MediaManager
asset_manager:"AssetManager" = None #AssetManager
Expand All @@ -23,4 +27,7 @@
video_extensions = ["mp4", "mov", "MP4", "MOV", "mkv", "MKV", "webm", "WEBM", "gif", "GIF"]
image_extensions = ["png", "jpg", "jpeg"]
icon_pack_manager: "IconPackManager" = None
store_backend: "StoreBackend" = None
wallpaper_pack_manager: "WallpaperPackManager" = None
store_backend: "StoreBackend" = None
pyro_daemon: Pyro5.api.Daemon = None
app_version: str = "0.0.2-alpha"
8 changes: 7 additions & 1 deletion locales/de_DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,11 @@
"settings-store-settings-header": "Store",
"settings-store-settings-auto-update": "Automatische Aktualisierung der Assets",
"settings-ui-settings-title": "UI Einstellungen",
"settings-ui-settings-key-grid-header": "Tastenraster"
"settings-ui-settings-key-grid-header": "Tastenraster",
"open-about": "Über",
"store.plugins.section": "Plugins",
"store.icons.section": "Icons",
"store.wallpapers.section": "Hintergründe",
"store.badges.official": "Offiziell",
"store.badges.verified": "Verifiziert"
}
8 changes: 7 additions & 1 deletion locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,11 @@
"settings-store-settings-header": "Store",
"settings-store-settings-auto-update": "Auto-update assets",
"settings-ui-settings-title": "UI Settings",
"settings-ui-settings-key-grid-header": "Key Grid"
"settings-ui-settings-key-grid-header": "Key Grid",
"open-about": "About",
"store.plugins.section": "Plugins",
"store.icons.section": "Icons",
"store.wallpapers.section": "Wallpapers",
"store.badges.official": "Official",
"store.badges.verified": "Verified"
}
25 changes: 0 additions & 25 deletions locales/es_ES.json

This file was deleted.

8 changes: 7 additions & 1 deletion locales/fr_FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,11 @@
"right-area-no-page-selected-error": "Aucune page sélectionnée",
"right-area-no-deck-selected-error": "Aucun deck sélectionnée",
"deck-switcher-no-decks": "Aucun Deck Détecté",
"open-settings": "Ouvrir les paramètres"
"open-settings": "Ouvrir les paramètres",
"open-about": "A propos",
"store.plugins.section": "Plugins",
"store.icons.section": "Icônes",
"store.wallpapers.section": "Fonds d'Écran",
"store.badges.official": "Officiel",
"store.badges.verified": "Vérifié"
}
4 changes: 3 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from src.backend.PluginManager.PluginManager import PluginManager
from src.backend.DeckManagement.HelperMethods import get_sys_args_without_param
from src.backend.IconPackManagement.IconPackManager import IconPackManager
from src.backend.WallpaperPackManagement.WallpaperPackManager import WallpaperPackManager
from src.windows.Store.StoreBackend import StoreBackend

# Import globals
Expand Down Expand Up @@ -64,7 +65,7 @@ def load():
log.info("Loading app")
gl.deck_manager = DeckManager()
gl.deck_manager.load_decks()
gl.main = Main(application_id="com.core447.StreamController", deck_manager=gl.deck_manager)
gl.main = Main(application_id="dev.core447.StreamController", deck_manager=gl.deck_manager)

@log.catch
def create_cache_folder():
Expand All @@ -77,6 +78,7 @@ def create_global_objects():
gl.settings_manager = SettingsManager()
gl.page_manager = PageManager(gl.settings_manager)
gl.icon_pack_manager = IconPackManager()
gl.wallpaper_pack_manager = WallpaperPackManager()

# Store
gl.store_backend = StoreBackend()
Expand Down
8 changes: 6 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ async-lru==2.0.4
certifi==2023.11.17
charset-normalizer==3.3.2
contourpy==1.2.0
cPython==0.0.6
cycler==0.12.1
dbus-python==1.3.2
decord==0.6.0
distlib==0.3.8
dnspython==2.4.2
evdev==1.6.1
filelock==3.13.1
fonttools==4.47.0
Expand All @@ -23,8 +25,6 @@ Levenshtein==0.23.0
loguru==0.7.2
matplotlib==3.8.2
numpy==1.26.2
obs-websocket-py==1.0
obsws-python==1.6.2
opencv-python==4.8.1.78
packaging==23.2
Pillow==10.1.0
Expand All @@ -38,14 +38,18 @@ pydantic==2.5.3
pydantic_core==2.14.6
PyGObject==3.46.0
PyGObject-stubs==2.10.0
pymongo==4.6.1
pyparsing==3.1.1
Pyro5==5.15
python-dateutil==2.8.2
python-Levenshtein==0.23.0
pyudev==0.24.1
rapidfuzz==3.6.1
requests==2.31.0
serpent==1.41
six==1.16.0
smmap==5.0.1
streamcontroller-plugin-tools>=0.0.4
streamdeck==0.9.5
typing_extensions==4.9.0
urllib3==2.1.0
Expand Down
8 changes: 8 additions & 0 deletions src/backend/AssetManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ def has_by_sha256(self, sha256: str) -> bool:

def has_by_id(self, id: str) -> bool:
return self.get_by_id(id) is not None

def has_by_internal_path(self, internal_path: str) -> bool:
return self.get_by_internal_path(internal_path) is not None

def get_by_name(self, name: str) -> dict:
for asset in self:
Expand All @@ -156,6 +159,11 @@ def get_by_id(self, id: str) -> dict:
if asset["id"] == id:
return asset

def get_by_internal_path(self, internal_path: str) -> dict:
for asset in self:
if asset["internal-path"] == internal_path:
return asset

def get_all(self) -> list:
return self

Expand Down
23 changes: 19 additions & 4 deletions src/backend/DeckManagement/DeckController.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from PIL import Image, ImageDraw, ImageFont, ImageOps
from StreamDeck.DeviceManager import DeviceManager
from StreamDeck.ImageHelpers import PILHelper
from PIL import Image, ImageSequence
from PIL import Image, ImageSequence, ImageOps
from StreamDeck.Transport.Transport import TransportError
from time import sleep
import math
Expand Down Expand Up @@ -106,7 +106,13 @@ def __init__(self, deck_manager, deck):


@log.catch
def generate_key_image(self, image_path=None, image=None, labels=None, image_margins=[0, 0, 0, 0], key=None, add_background=True, shrink=False):
def generate_key_image(self, image_path=None, image=None, labels=None, image_margins=[0, 0, 0, 0], key=None, add_background=True, shrink=False, fill_mode:str = "cover"):
"""
fill_mode:
cover: fill the whole key while maintaining aspect ratio
fit: scale the image to fit the key while maintaining aspect ratio (not respecting margins)
stretch: fill the whole key while changing the aspect ratio
"""
# margins = [left, top, right, bottom]
DEFAULT_FONT = "Assets/Fonts/Roboto-Regular.ttf"
if image != None:
Expand All @@ -120,7 +126,16 @@ def generate_key_image(self, image_path=None, image=None, labels=None, image_mar
image_height = math.floor(self.deck.key_image_format()["size"][1]-image_margins[1]-image_margins[3])
image_width = math.floor(self.deck.key_image_format()["size"][0]-image_margins[0]-image_margins[2])

image = image.resize((image_width, image_height), Image.Resampling.LANCZOS)
match fill_mode:
case "cover":
image = ImageOps.cover(image, (image_width, image_height), Image.Resampling.LANCZOS)

case "fit":
# Create overlay
image = ImageOps.contain(image, (image_width, image_height), Image.Resampling.LANCZOS)

case "stretch":
image = image.resize((image_width, image_height), Image.Resampling.LANCZOS)

# Generate transparent background to draw everything on
alpha_bg = Image.new("RGBA", self.deck.key_image_format()["size"], (0, 0, 0, 0))
Expand Down Expand Up @@ -497,7 +512,7 @@ def get_from_page(self, page):
load_screensaver(self)

# Notify plugin actions
gl.plugin_manager.trigger_signal(signal= Signals.ChangePage, path= self.active_page.json_path)
gl.plugin_manager.trigger_signal(controller=self, signal=Signals.ChangePage, path=self.active_page.json_path)

def reload_page(self, load_brightness: bool = True, load_background: bool = True, load_keys: bool = True, load_screensaver: bool = True):
# Reset deck
Expand Down
44 changes: 43 additions & 1 deletion src/backend/PluginManager/ActionBase.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from loguru import logger as log
from copy import copy
import subprocess
import os
import Pyro5.api

# Import own modules
from src.backend.PluginManager.Signals import Signal
Expand All @@ -10,6 +13,7 @@
# Import locale manager
from locales.LocaleManager import LocaleManager

@Pyro5.api.expose
class ActionBase:
# Change to match your action
ACTION_NAME = ""
Expand All @@ -23,6 +27,8 @@ def __init__(self, deck_controller, page, coords):
if self.ACTION_NAME in ["", None]:
raise ValueError("Please specify an action name")

self._backend: Pyro5.api.Proxy = None

self.deck_controller = deck_controller
self.page = page
self.page_coords = coords
Expand Down Expand Up @@ -202,4 +208,40 @@ def connect(self, signal:Signal = None, callback: callable = None) -> None:
raise TypeError("callback must be callable")

# Connect
gl.plugin_manager.connect_signal(signal = signal, callback = callback)
gl.plugin_manager.connect_signal(signal = signal, callback = callback)

def launch_backend(self, backend_path: str, venv_activate_path: str = None):
uri = self.add_to_pyro()

## Launch
command = ""
if venv_activate_path is not None:
command = f"source{venv_activate_path} && "
command += "python3 "
command += f"{backend_path}"
command += f" --uri={uri}"

subprocess.Popen(command, shell=True, start_new_session=True)

def add_to_pyro(self) -> str:
daemon = gl.plugin_manager.pyro_daemon
uri = daemon.register(self)
return str(uri)

def register_backend(self, backend_uri:str):
"""
Internal method, do not call manually
"""
self._backend = Pyro5.api.Proxy(backend_uri)
gl.plugin_manager.backends.append(self._backend)

@property
def backend(self):
if self._backend is not None:
# Transfer ownership
self._backend._pyroClaimOwnership()
return self._backend

@backend.setter
def backend(self, value):
self._backend = value
42 changes: 41 additions & 1 deletion src/backend/PluginManager/PluginBase.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import inspect
import json
import Pyro5.api
import subprocess

from loguru import logger as log

Expand All @@ -16,6 +18,7 @@
# Import own modules
from locales.LocaleManager import LocaleManager

@Pyro5.api.expose
class PluginBase:
plugins = {}

Expand All @@ -32,6 +35,7 @@ def __init_subclass__(cls, *args, **kwargs) -> None:
cls.PLUGIN = None

def __init__(self):
self._backend: Pyro5.api.Proxy = None
# Verify variables
if self.PLUGIN_NAME in ["", None]:
raise ValueError("Please specify a plugin name")
Expand Down Expand Up @@ -79,4 +83,40 @@ def add_css_stylesheet(self, path):
)

def register_page(self, path: str) -> None:
gl.page_manager.register_page(path)
gl.page_manager.register_page(path)

def launch_backend(self, backend_path: str, venv_path: str = None):
uri = self.add_to_pyro()

## Launch
command = ""
if venv_path is not None:
command = f"source {venv_path}/bin/activate && "
command += "python3 "
command += f"{backend_path}"
command += f" --uri={uri}"

subprocess.Popen(command, shell=True, start_new_session=True)

def add_to_pyro(self) -> str:
daemon = gl.plugin_manager.pyro_daemon
uri = daemon.register(self)
return str(uri)

def register_backend(self, backend_uri:str):
"""
Internal method, do not call manually
"""
self._backend = Pyro5.api.Proxy(backend_uri)
gl.plugin_manager.backends.append(self._backend)

@property
def backend(self):
# Transfer ownership
if self._backend is not None:
self._backend._pyroClaimOwnership()
return self._backend

@backend.setter
def backend(self, value):
self._backend = value
Loading