From f3beec141c3839a3ca270d2ad2fb7da3b81356c0 Mon Sep 17 00:00:00 2001 From: DYefremov Date: Sat, 7 Nov 2020 18:38:40 +0300 Subject: [PATCH] added epg display in control panel --- app/connections.py | 13 +- app/ui/control.glade | 856 +++++++++++++++++++------------------- app/ui/control.py | 123 +++++- app/ui/dialogs.glade | 7 +- app/ui/main_app_window.py | 10 +- app/ui/main_window.glade | 3 +- app/ui/style.css | 4 + 7 files changed, 565 insertions(+), 451 deletions(-) diff --git a/app/connections.py b/app/connections.py index 73e2a8bb..23c7eb39 100644 --- a/app/connections.py +++ b/app/connections.py @@ -56,6 +56,11 @@ class HttpRequestType(Enum): POWER = "powerstate?newstate=" REMOTE = "remotecontrol?command=" VOL = "vol?set=set" + # EPG + EPG = "epgservice?sRef=" + # Timer + TIMER = "" + TIMER_LIST = "timerlist" # Screenshot GRUB = "grab?format=jpg&" @@ -398,7 +403,7 @@ class HttpAPI: elif req_type is HttpRequestType.GRUB: data = None # Must be disabled for token-based security. url = "{}/{}{}".format(self._main_url, req_type.value, ref) - elif req_type in (HttpRequestType.REMOTE, HttpRequestType.POWER, HttpRequestType.VOL): + elif req_type in (HttpRequestType.REMOTE, HttpRequestType.POWER, HttpRequestType.VOL, HttpRequestType.EPG): url += ref def done_callback(f): @@ -452,6 +457,12 @@ def get_response(req_type, url, data=None): elif req_type is HttpRequestType.PLAYER_LIST: return [{el.tag: el.text for el in el.iter()} for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2file")] + elif req_type is HttpRequestType.EPG: + return {"event_list": [{el.tag: el.text for el in el.iter()} for el in + ETree.fromstring(f.read().decode("utf-8")).iter("e2event")]} + elif req_type is HttpRequestType.TIMER_LIST: + return {"timer_list": [{el.tag: el.text for el in el.iter()} for el in + ETree.fromstring(f.read().decode("utf-8")).iter("e2timer")]} else: return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()} except HTTPError as e: diff --git a/app/ui/control.glade b/app/ui/control.glade index 8191181a..bc9bb191 100644 --- a/app/ui/control.glade +++ b/app/ui/control.glade @@ -49,174 +49,32 @@ Author: Dmitriy Yefremov False gtk-go-forward - + + True False - - - True - False - 10 - 10 - 5 - 5 - vertical - 2 - - - True - True - True - app.on_standby - Standby - True - - - False - True - 0 - - - - - True - True - True - app.on_wake_up - Wake Up - True - - - False - True - 1 - - - - - True - True - True - app.on_reboot - Reboot - True - - - False - True - 2 - - - - - True - True - True - app.on_restart_gui - Restart GUI - True - - - False - True - 3 - - - - - True - False - - - False - True - 4 - - - - - True - True - True - app.on_shutdown - Shutdown - True - - - False - True - 5 - - - - - main - 1 - - + 16 + view-refresh + 1 - + + True False - - - True - False - 10 - 10 - 5 - 5 - vertical - 2 - - - True - True - True - app.on_screenshot_all - All - True - - - False - True - 0 - - - - - True - True - True - app.on_screenshot_video - Video - True - - - False - True - 1 - - - - - True - True - True - app.on_screenshot_osd - OSD - True - - - False - True - 2 - - - - - main - 1 - - + 16 + window-new + 1 + + + True + False + 16 + application-exit + + + True + False + 16 + system-log-out + 1 True @@ -228,19 +86,29 @@ Author: Dmitriy Yefremov 1 10 - - 245 + True False + 16 + document-revert + 1 + + + 320 + True + False + 2 + 2 vertical + True False center center 10 10 - 10 + 5 stack @@ -253,90 +121,146 @@ Author: Dmitriy Yefremov True False - 10 - 10 5 5 crossfade + - + True False - vertical - 5 + 0 + out - + True False - center - 10 - True - expand + 5 + 5 + vertical + 5 - + True - True - False - True - Power - power_menu + False + center + 5 + 0.5 + none - + True False - system-log-out + center + center + 5 + 5 + 5 + 5 + True + expand + + + 32 + True + True + True + Standby + app.on_standby + standby_image + + + False + True + 0 + + + + + True + True + True + Wake Up + app.on_wake_up + wake_up_image + + + False + True + 1 + + + + + True + True + True + Reboot + app.on_reboot + reboot_image + + + False + True + 2 + + + + + True + True + True + Restart GUI + app.on_restart_gui + restart_gui_image + + + False + True + 3 + + + + + True + True + True + Shutdown + app.on_shutdown + shutdown_image + + + False + True + 4 + + - - - True - True - 1 - - - - - True - True - True - Screenshot - screenshots_menu - - + + True False - zoom-best-fit + Power False True - 1 + 0 - - - False - True - 0 - - - - - True - False - center - center - 0 - in True False + 25 + 25 5 5 vertical + 5 True @@ -442,8 +366,6 @@ Author: Dmitriy Yefremov center 5 5 - 5 - 5 True expand @@ -481,176 +403,258 @@ Author: Dmitriy Yefremov 1 - - - - - - - - False - True - 1 - - - - - Grab screenshot - True - True - False - center - True - - - False - True - 2 - - - - - True - True - False - True - vertical - volume_adjustment - audio-volume-muted-symbolic + + + Grab screenshot + True + True + False + center + True + + + False + True + 2 + + + + + True + True + False + True + vertical + volume_adjustment + audio-volume-muted-symbolic audio-volume-high-symbolic audio-volume-low-symbolic audio-volume-medium-symbolic - - - - True - True - center + + + + True + True + center + center + none + + + + + True + True + center + center + none + + + + + False + True + 3 + + + + + True + False + + + False + True + 4 + + + + + True + False + 5 + True + + + G + status-bar-button + 24 + True + True + True + app.on_green + + + + 1 + 0 + + + + + Y + status-bar-button + 24 + True + True + True + app.on_yellow + + + + 2 + 0 + + + + + B + status-bar-button + 24 + True + True + True + app.on_blue + + + + 3 + 0 + + + + + R + status-bar-button + 24 + True + True + True + app.on_red + + + + 0 + 0 + + + + + False + True + 5 + + + + + False + True + 1 + + + + + True + False center - none + 0.5 + none + + + True + False + center + center + 5 + 5 + 5 + 5 + expand + + + All + True + True + True + center + app.on_screenshot_all + + + True + True + 0 + + + + + Video + True + True + True + center + app.on_screenshot_video + + + True + True + 1 + + + + + OSD + True + True + True + center + app.on_screenshot_osd + + + True + True + 2 + + + + + + + True + False + Screenshot + + + + False + True + 2 + - - - True - True - center - center - none + + + False + start + gtk-missing-image + 6 + + True + True + 3 + - - False - True - 3 - - - - True - False - - - False - True - 4 - - - - - True - False - 5 - True - - - status-bar-button - 24 - True - True - True - app.on_green - - - - 1 - 0 - - - - - status-bar-button - 24 - True - True - True - app.on_yellow - - - - 2 - 0 - - - - - status-bar-button - 24 - True - True - True - app.on_blue - - - - 3 - 0 - - - - - status-bar-button - 24 - True - True - True - app.on_red - - - - 0 - 0 - - - - - False - True - 5 - - - - - False - start - 10 - gtk-missing-image - 6 - - - True - True - 6 - + + @@ -664,52 +668,28 @@ audio-volume-medium-symbolic vertical 5 - + True - False + True + in + + + True + False + + + True + False + + + + + True True - 1 - - - - - True - False - expand - - - Add - True - True - True - - - True - True - 0 - - - - - button - True - True - True - - - True - True - 2 - - - - - False - True - 2 + 0 @@ -720,14 +700,27 @@ audio-volume-medium-symbolic - + False vertical 5 - + True - False + True + in + + + True + False + + + True + False + + + + True @@ -736,7 +729,7 @@ audio-volume-medium-symbolic - + True False True @@ -789,8 +782,8 @@ audio-volume-medium-symbolic - timer - Timer + timers + Timers 2 @@ -805,10 +798,9 @@ audio-volume-medium-symbolic True False - 10 - 10 + 5 + 5 10 - 5 vertical 5 @@ -927,7 +919,7 @@ audio-volume-medium-symbolic diff --git a/app/ui/control.py b/app/ui/control.py index ac9d27b2..3dd641d9 100644 --- a/app/ui/control.py +++ b/app/ui/control.py @@ -1,21 +1,66 @@ """ Receiver control module via HTTP API. """ import os +from datetime import datetime +from enum import Enum from gi.repository import GLib -from .uicommons import Gtk, UI_RESOURCES_PATH -from ..commons import run_task, run_with_delay, log +from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH +from ..commons import run_task, run_with_delay, log, run_idle from ..connections import HttpRequestType, HttpAPI class ControlBox(Gtk.HBox): + class Tool(Enum): + """ The currently displayed tool. """ + REMOTE = "control" + EPG = "epg" + TIMERS = "timers" + + class EpgRow(Gtk.HBox): + def __init__(self, event: dict, **properties): + super().__init__(**properties) + + self._ev_id = event.get("e2eventid", "") + self._ref = event.get("e2eventservicereference", "") + self.set_orientation(Gtk.Orientation.VERTICAL) + + title_label = Gtk.Label(event.get("e2eventtitle", "")) + + description = Gtk.Label() + description.set_markup("{}".format(event.get("e2eventdescription", ""))) + description.set_line_wrap(True) + description.set_max_width_chars(25) + + start = int(event.get("e2eventstart", "0")) + start_time = datetime.fromtimestamp(start) + end_time = datetime.fromtimestamp(start + int(event.get("e2eventduration", "0"))) + time_label = Gtk.Label() + time_label.set_margin_top(5) + time_str = "{} - {}".format(start_time.strftime("%A, %H:%M"), end_time.strftime("%H:%M")) + time_label.set_markup("{}".format(time_str)) + + self.add(time_label) + self.add(title_label) + self.add(description) + sep = Gtk.Separator() + sep.set_margin_top(5) + self.add(sep) + self.set_spacing(5) + + self.show_all() + def __init__(self, app, http_api, settings, *args, **kwargs): super().__init__(*args, **kwargs) self._http_api = http_api self._settings = settings + self._update_epg = False + self._app = app - handlers = {"on_volume_changed": self.on_volume_changed} + handlers = {"on_visible_tool": self.on_visible_tool, + "on_volume_changed": self.on_volume_changed, + "on_epg_press": self.on_epg_press} builder = Gtk.Builder() builder.add_from_file(UI_RESOURCES_PATH + "control.glade") @@ -23,13 +68,24 @@ class ControlBox(Gtk.HBox): self.add(builder.get_object("main_box")) self._screenshot_image = builder.get_object("screenshot_image") - self._screenshots_button = builder.get_object("screenshots_button") + self._screenshot_button_box = builder.get_object("screenshot_button_box") self._screenshot_check_button = builder.get_object("screenshot_check_button") self._screenshot_check_button.bind_property("active", self._screenshot_image, "visible") self._snr_value_label = builder.get_object("snr_value_label") self._ber_value_label = builder.get_object("ber_value_label") self._agc_value_label = builder.get_object("agc_value_label") self._volume_button = builder.get_object("volume_button") + self._epg_list_box = builder.get_object("epg_list_box") + self._timers_list_box = builder.get_object("timers_list_box") + self._app._control_revealer.bind_property("visible", self, "visible") + builder.get_object("stack_switcher").set_visible(settings.is_enable_experimental) + builder.get_object("epg_box").set_visible(settings.is_enable_experimental) + + self.init_actions(app) + self.connect("hide", self.on_hide) + self.show() + + def init_actions(self, app): # Remote controller actions app.set_action("on_up", lambda a, v: self.on_remote_action(HttpAPI.Remote.UP)) app.set_action("on_down", lambda a, v: self.on_remote_action(HttpAPI.Remote.DOWN)) @@ -53,7 +109,19 @@ class ControlBox(Gtk.HBox): app.set_action("on_screenshot_video", self.on_screenshot_video) app.set_action("on_screenshot_osd", self.on_screenshot_osd) - self.show() + @property + def update_epg(self): + return self._update_epg + + def on_visible_tool(self, stack, param): + tool = self.Tool(stack.get_visible_child_name()) + self._update_epg = tool is self.Tool.EPG + + if tool is self.Tool.TIMERS: + self.update_timer_list() + + def on_hide(self, item): + self._update_epg = False # ***************** Remote controller ********************* # @@ -97,11 +165,16 @@ class ControlBox(Gtk.HBox): from gi.repository import GdkPixbuf loader = GdkPixbuf.PixbufLoader.new_with_type("jpeg") - loader.set_size(200, 120) - loader.write(data) - pix = loader.get_pixbuf() - loader.close() - GLib.idle_add(self._screenshot_image.set_from_pixbuf, pix) + loader.set_size(280, 165) + try: + loader.write(data) + pix = loader.get_pixbuf() + except GLib.Error: + pass # NOP + else: + GLib.idle_add(self._screenshot_image.set_from_pixbuf, pix) + finally: + loader.close() def on_screenshot_all(self, action, value=None): self._http_api.send(HttpRequestType.GRUB, "mode=all" if self._http_api.is_owif else "d=", @@ -123,7 +196,7 @@ class ControlBox(Gtk.HBox): img = data.get("img_data", None) if img: is_darwin = self._settings.is_darwin - GLib.idle_add(self._screenshots_button.set_sensitive, is_darwin) + GLib.idle_add(self._screenshot_button_box.set_sensitive, is_darwin) path = os.path.expanduser("~/Desktop") if is_darwin else None try: @@ -135,7 +208,7 @@ class ControlBox(Gtk.HBox): cmd = ["open" if is_darwin else "xdg-open", tf.name] subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() finally: - GLib.idle_add(self._screenshots_button.set_sensitive, True) + GLib.idle_add(self._screenshot_button_box.set_sensitive, True) def on_power_action(self, action): self._http_api.send(HttpRequestType.POWER, action, lambda resp: log("Power status changed...")) @@ -145,4 +218,30 @@ class ControlBox(Gtk.HBox): self._ber_value_label.set_text(str(sig.get("e2ber", None) or "0").strip()) self._agc_value_label.set_text(sig.get("e2acg", "0 %").strip()) + # ************************ EPG **************************** # + def on_service_changed(self, ref): + self._app._wait_dialog.show() + self._http_api.send(HttpRequestType.EPG, ref, self.update_epg_data) + + @run_idle + def update_epg_data(self, epg): + list(map(self._epg_list_box.remove, (r for r in self._epg_list_box))) + list(map(lambda e: self._epg_list_box.add(self.EpgRow(e)), epg.get("event_list", []))) + self._app._wait_dialog.hide() + + def on_epg_press(self, list_box: Gtk.ListBox, event: Gdk.EventButton): + if event.get_event_type() == Gdk.EventType.DOUBLE_BUTTON_PRESS and len(list_box) > 0: + pass + + # *********************** Timers *************************** # + + def update_timer_list(self): + self._app._wait_dialog.show() + self._http_api.send(HttpRequestType.TIMER_LIST, "", self.update_timers_data) + + @run_idle + def update_timers_data(self, timers): + timers = timers.get("timer_list", []) + list(map(self._timers_list_box.remove, (r for r in self._timers_list_box))) + self._app._wait_dialog.hide() diff --git a/app/ui/dialogs.glade b/app/ui/dialogs.glade index f9b75aed..5c3c5ee3 100644 --- a/app/ui/dialogs.glade +++ b/app/ui/dialogs.glade @@ -176,6 +176,7 @@ Author: Dmitriy Yefremov True False + 2 vertical @@ -212,10 +213,10 @@ Author: Dmitriy Yefremov 1 - + diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py index 48c02111..2540ea2f 100644 --- a/app/ui/main_app_window.py +++ b/app/ui/main_app_window.py @@ -90,6 +90,7 @@ class Application(Gtk.Application): "on_tree_view_key_release": self.on_tree_view_key_release, "on_bouquets_selection": self.on_bouquets_selection, "on_satellite_editor_show": self.on_satellite_editor_show, + "on_fav_selection": self.on_fav_selection, "on_services_selection": self.on_services_selection, "on_fav_cut": self.on_fav_cut, "on_bouquets_cut": self.on_bouquets_cut, @@ -250,7 +251,7 @@ class Application(Gtk.Application): self._signal_level_bar.bind_property("visible", builder.get_object("record_button"), "visible") self._receiver_info_box.bind_property("visible", self._http_status_image, "visible", 4) self._receiver_info_box.bind_property("visible", self._signal_box, "visible") - # Remote controller + # Control self._control_button = builder.get_object("control_button") self._receiver_info_box.bind_property("visible", self._control_button, "visible") self._control_revealer = builder.get_object("control_revealer") @@ -1641,6 +1642,13 @@ class Application(Gtk.Application): self._data_hash = self.get_data_hash() yield True + def on_fav_selection(self, model, path, column): + if self._control_box and self._control_box.update_epg: + ref = self.get_service_ref(path) + if not ref: + return + self._control_box.on_service_changed(ref) + def on_services_selection(self, model, path, column): self.update_service_bar(model, path) diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade index 13ee6bc1..066b9316 100644 --- a/app/ui/main_window.glade +++ b/app/ui/main_window.glade @@ -2409,6 +2409,7 @@ Author: Dmitriy Yefremov + multiple @@ -3133,11 +3134,9 @@ Author: Dmitriy Yefremov - 240 False end crossfade - 500 diff --git a/app/ui/style.css b/app/ui/style.css index 4eb55416..4342567f 100644 --- a/app/ui/style.css +++ b/app/ui/style.css @@ -7,6 +7,10 @@ margin: 1px; } +.control-box { + padding: 5px; +} + .red-button { background-color: red; }