From c1e69b6965fe71d4c8eeba01c2d751bfe57811a3 Mon Sep 17 00:00:00 2001 From: egon984 Date: Thu, 4 Sep 2025 19:24:39 +0200 Subject: [PATCH 01/11] New channels navigation --- usr/lib/hypnotix/common.py | 3 +++ usr/lib/hypnotix/hypnotix.py | 44 ++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/usr/lib/hypnotix/common.py b/usr/lib/hypnotix/common.py index 0e60b5f0..c4e12932 100755 --- a/usr/lib/hypnotix/common.py +++ b/usr/lib/hypnotix/common.py @@ -98,11 +98,14 @@ def __init__(self, provider, info): self.group_title = None self.title = None self.url = None + self.lcn = None match = EXTINF.fullmatch(info) if match is not None: res = match.groupdict() if 'params' in res: params = dict(PARAMS.findall(res['params'])) + if "tvg-lcn" in params and params['tvg-lcn'].strip() != "": + self.lcn = params['tvg-lcn'].strip() if "tvg-name" in params and params['tvg-name'].strip() != "": self.name = params['tvg-name'].strip() if "tvg-logo" in params and params['tvg-logo'].strip() != "": diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 859924fe..7f34007d 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -561,6 +561,9 @@ def show_channels(self, channels, favorites=False): self.download_channel_logos(logos_to_refresh) else: self.sidebar.hide() + + self.update_hchannels() + self.chan_num_buf = 0 def show_vod(self, items): logos_to_refresh = [] @@ -732,6 +735,9 @@ def on_search_bar(self, widget): self.latest_search_bar_text = search_bar_text self.search_bar.set_sensitive(False) GLib.timeout_add_seconds(0.1, self.on_search) + + def update_hchannels(self): + self.hchannels = [c for c in self.channels_listbox.get_children() if unidecode(self.search_bar.get_text()).lower() in c.channel.name.lower()] def on_search(self): self.visible_search_results = 0 @@ -747,6 +753,7 @@ def on_search(self): self.search_bar.set_sensitive(True) self.search_bar.grab_focus_without_selecting() self.navigate_to("channels_page") + self.update_hchannels() def init_channels_listbox(self): self.latest_search_bar_text = None @@ -883,13 +890,23 @@ def on_channel_activated(self, box, widget): def on_prev_channel(self): if self.stack.get_visible_child_name() == "channels_page": - self.channels_listbox.do_move_cursor(self.channels_listbox, Gtk.MovementStep.DISPLAY_LINES, -1) - self.channels_listbox.do_activate_cursor_row(self.channels_listbox) + idx = self.channels_listbox_selected_index() + step = -1 if idx != 0 else (len(self.hchannels) - 1) + self.channels_listbox_activate_row(step) def on_next_channel(self): if self.stack.get_visible_child_name() == "channels_page": - self.channels_listbox.do_move_cursor(self.channels_listbox, Gtk.MovementStep.DISPLAY_LINES, 1) - self.channels_listbox.do_activate_cursor_row(self.channels_listbox) + idx = self.channels_listbox_selected_index() + step = 1 if idx != (len(self.hchannels) - 1) else (1 - len(self.hchannels)) + self.channels_listbox_activate_row(step) + + def channels_listbox_selected_index(self): + channel = [c for c in self.hchannels if c.channel.name == self.active_channel.name][0] + return self.hchannels.index(channel) + + def channels_listbox_activate_row(self, step): + self.channels_listbox.do_move_cursor(self.channels_listbox, Gtk.MovementStep.DISPLAY_LINES, step) + self.channels_listbox.do_activate_cursor_row(self.channels_listbox) @async_function def play_async(self, channel): @@ -1516,6 +1533,7 @@ def on_menu_quit(self, widget): self.application.quit() def on_key_press_event(self, widget, event): + channel_focused = self.fullscreen or "ChannelWidget" in widget.get_focus().get_name() # Get any active, but not pressed modifiers, like CapsLock and NumLock persistant_modifiers = Gtk.accelerator_get_default_mod_mask() @@ -1548,6 +1566,24 @@ def on_key_press_event(self, widget, event): self.on_prev_channel() elif event.keyval == Gdk.KEY_Right: self.on_next_channel() + return True + elif event.keyval == Gdk.KEY_Return: + if channel_focused: + try: + chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_num_buf)][0] + idx = self.channels_listbox_selected_index() + step = self.hchannels.index(chan) - idx + self.channels_listbox_activate_row(step) + finally: + self.chan_num_buf = 0 + return True + else: + self.chan_num_buf = 0 + else: + try: + self.chan_num_buf = self.chan_num_buf * 10 + int(chr(event.keyval)) + except: + self.chan_num_buf = 0 # elif event.keyval == Gdk.KEY_Up: # # Up of in the list # pass From 43997e1a02602b9492471f657bb3d81b87c2864a Mon Sep 17 00:00:00 2001 From: egon984 Date: Thu, 4 Sep 2025 22:26:43 +0200 Subject: [PATCH 02/11] renamed chan_num_buf as chan_lcn_buf --- usr/lib/hypnotix/hypnotix.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 7f34007d..6dbb0c06 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -563,7 +563,7 @@ def show_channels(self, channels, favorites=False): self.sidebar.hide() self.update_hchannels() - self.chan_num_buf = 0 + self.chan_lcn_buf = 0 def show_vod(self, items): logos_to_refresh = [] @@ -1570,20 +1570,20 @@ def on_key_press_event(self, widget, event): elif event.keyval == Gdk.KEY_Return: if channel_focused: try: - chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_num_buf)][0] + chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_lcn_buf)][0] idx = self.channels_listbox_selected_index() step = self.hchannels.index(chan) - idx self.channels_listbox_activate_row(step) finally: - self.chan_num_buf = 0 + self.chan_lcn_buf = 0 return True else: - self.chan_num_buf = 0 + self.chan_lcn_buf = 0 else: try: - self.chan_num_buf = self.chan_num_buf * 10 + int(chr(event.keyval)) + self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) except: - self.chan_num_buf = 0 + self.chan_lcn_buf = 0 # elif event.keyval == Gdk.KEY_Up: # # Up of in the list # pass From 2297897c55b91bb5013c7820203086e74ffec834 Mon Sep 17 00:00:00 2001 From: egon984 Date: Fri, 5 Sep 2025 16:41:12 +0200 Subject: [PATCH 03/11] added OSD while typing LCN --- usr/lib/hypnotix/hypnotix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index aace2206..b50ccb80 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -1515,6 +1515,7 @@ def on_key_press_event(self, widget, event): else: try: self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) + self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", 1500) except: self.chan_lcn_buf = 0 # elif event.keyval == Gdk.KEY_Up: From ceb1bf02832e7c866255443158e3e7c1afb12678 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 00:13:06 +0200 Subject: [PATCH 04/11] chan_lcn_buf resets after a timeout --- usr/lib/hypnotix/hypnotix.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index b50ccb80..fd6543d1 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -1515,7 +1515,11 @@ def on_key_press_event(self, widget, event): else: try: self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) - self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", 1500) + timeout = 1500 + self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", timeout) + def reset_chan_lcn_buf(): + self.chan_lcn_buf = 0 + GLib.timeout_add(timeout, reset_chan_lcn_buf) except: self.chan_lcn_buf = 0 # elif event.keyval == Gdk.KEY_Up: From 2f52959136f3401891f150d94447c3ae61613703 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 00:31:00 +0200 Subject: [PATCH 05/11] bugfix --- usr/lib/hypnotix/hypnotix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index fd6543d1..3a178534 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -1519,6 +1519,7 @@ def on_key_press_event(self, widget, event): self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", timeout) def reset_chan_lcn_buf(): self.chan_lcn_buf = 0 + return False GLib.timeout_add(timeout, reset_chan_lcn_buf) except: self.chan_lcn_buf = 0 From 99315dfb5547a3a6a9c3930a81af8f6927ab67d8 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 09:39:55 +0200 Subject: [PATCH 06/11] minor bugfixes --- usr/lib/hypnotix/hypnotix.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 3a178534..bbaaa47c 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -545,6 +545,7 @@ def show_channels(self, channels, favorites=False): self.update_hchannels() self.chan_lcn_buf = 0 + self.active_channel = self.hchannels[0].channel def show_vod(self, items): logos_to_refresh = [] @@ -1513,16 +1514,17 @@ def on_key_press_event(self, widget, event): else: self.chan_lcn_buf = 0 else: - try: - self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) - timeout = 1500 - self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", timeout) - def reset_chan_lcn_buf(): + if channel_focused: + try: + self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) + timeout = 1500 + self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", timeout) + def reset_chan_lcn_buf(): + self.chan_lcn_buf = 0 + return False + GLib.timeout_add(timeout, reset_chan_lcn_buf) + except: self.chan_lcn_buf = 0 - return False - GLib.timeout_add(timeout, reset_chan_lcn_buf) - except: - self.chan_lcn_buf = 0 # elif event.keyval == Gdk.KEY_Up: # # Up of in the list # pass From bc89d35e3450b0956c93f9ec3de1b78fbbcc61b1 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 16:50:06 +0200 Subject: [PATCH 07/11] reset timeout when typing LCN --- usr/lib/hypnotix/hypnotix.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index bbaaa47c..54bc243a 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -545,6 +545,7 @@ def show_channels(self, channels, favorites=False): self.update_hchannels() self.chan_lcn_buf = 0 + self.timeoutID = 0 self.active_channel = self.hchannels[0].channel def show_vod(self, items): @@ -1519,10 +1520,16 @@ def on_key_press_event(self, widget, event): self.chan_lcn_buf = self.chan_lcn_buf * 10 + int(chr(event.keyval)) timeout = 1500 self.mpv.command("show-text", str(self.chan_lcn_buf) + "-", timeout) + def reset_chan_lcn_buf(): self.chan_lcn_buf = 0 return False - GLib.timeout_add(timeout, reset_chan_lcn_buf) + + if (self.timeoutID != 0): + GLib.source_remove(self.timeoutID) + self.timeoutID = 0 + + self.timeoutID = GLib.timeout_add(timeout, reset_chan_lcn_buf) except: self.chan_lcn_buf = 0 # elif event.keyval == Gdk.KEY_Up: From f5d66999a01516e4a5c07059568b8a3ea79749c5 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 17:46:02 +0200 Subject: [PATCH 08/11] press the Home key to go back --- usr/lib/hypnotix/hypnotix.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 54bc243a..e090d424 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -547,6 +547,7 @@ def show_channels(self, channels, favorites=False): self.chan_lcn_buf = 0 self.timeoutID = 0 self.active_channel = self.hchannels[0].channel + self.prev_channel = None def show_vod(self, items): logos_to_refresh = [] @@ -868,6 +869,7 @@ def on_favorite_button_toggled(self, widget): self.manager.save_favorites(self.favorite_data) def on_channel_activated(self, box, widget): + self.prev_channel = self.active_channel self.active_channel = widget.channel self.play_async(self.active_channel) @@ -1477,6 +1479,11 @@ def on_key_press_event(self, widget, event): # Bool of Control or Shift modifier states ctrl = modifier == Gdk.ModifierType.CONTROL_MASK shift = modifier == Gdk.ModifierType.SHIFT_MASK + + def activate_hchannel(chan): + idx = self.channels_listbox_selected_index() + step = self.hchannels.index(chan) - idx + self.channels_listbox_activate_row(step) if ctrl and event.keyval == Gdk.KEY_r: self.reload(page=None, refresh=True) @@ -1502,13 +1509,16 @@ def on_key_press_event(self, widget, event): elif event.keyval == Gdk.KEY_Right: self.on_next_channel() return True + elif event.keyval == Gdk.KEY_Home: + if channel_focused: + chan = [c for c in self.hchannels if c.channel == self.prev_channel][0] + activate_hchannel(chan) + return True elif event.keyval == Gdk.KEY_Return: if channel_focused: try: chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_lcn_buf)][0] - idx = self.channels_listbox_selected_index() - step = self.hchannels.index(chan) - idx - self.channels_listbox_activate_row(step) + activate_hchannel(chan) finally: self.chan_lcn_buf = 0 return True From 0b3eea7451750f2e0adcff86366775429be081f3 Mon Sep 17 00:00:00 2001 From: egon984 Date: Sat, 6 Sep 2025 22:34:31 +0200 Subject: [PATCH 09/11] bugfix fixed misdetection of focused widget --- usr/lib/hypnotix/hypnotix.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index e090d424..2e1ba930 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -1470,7 +1470,11 @@ def on_menu_quit(self, widget): self.application.quit() def on_key_press_event(self, widget, event): - channel_focused = self.fullscreen or "ChannelWidget" in widget.get_focus().get_name() + try: + widgetType = widget.get_focus().get_name() + except: # we can pretend the channel widget is focused + widgetType = "ChannelWidget" + channel_focused = self.fullscreen or "ChannelWidget" in widgetType # Get any active, but not pressed modifiers, like CapsLock and NumLock persistant_modifiers = Gtk.accelerator_get_default_mod_mask() From 0a9c9a8f470512166c3a00d0eb4e7a205fc73516 Mon Sep 17 00:00:00 2001 From: Frederic Bontemps <35567681+egon984@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:11:27 +0200 Subject: [PATCH 10/11] bugfix --- usr/lib/hypnotix/hypnotix.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 2e1ba930..5fda871f 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -546,7 +546,10 @@ def show_channels(self, channels, favorites=False): self.update_hchannels() self.chan_lcn_buf = 0 self.timeoutID = 0 - self.active_channel = self.hchannels[0].channel + try: + self.active_channel = self.hchannels[0].channel + except: + pass self.prev_channel = None def show_vod(self, items): From 2e672ae3f4133ab84a21b1ecac9543ad029bd87e Mon Sep 17 00:00:00 2001 From: Frederic Bontemps <35567681+egon984@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:02:37 +0200 Subject: [PATCH 11/11] example: epg + lcn example of navigation across channels via epg numbers --- usr/lib/hypnotix/hypnotix.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/usr/lib/hypnotix/hypnotix.py b/usr/lib/hypnotix/hypnotix.py index 5fda871f..a07776b9 100755 --- a/usr/lib/hypnotix/hypnotix.py +++ b/usr/lib/hypnotix/hypnotix.py @@ -1524,8 +1524,21 @@ def activate_hchannel(chan): elif event.keyval == Gdk.KEY_Return: if channel_focused: try: - chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_lcn_buf)][0] - activate_hchannel(chan) + chan = [c for c in self.hchannels if c.channel.lcn == str(self.chan_lcn_buf)] + + """ + + If this branch will be merged with "epg", it's very easy to jump across channels using + their EPG numbers (without needing a lcn tag) just by adding the lines here below: + + + if (len(chan) == 0 and self.epg is not None): + epg_channel = [p.attrib["id"] for p in self.epg.findall("channel") if p.attrib["id"].split()[-1] == str(self.chan_lcn_buf)][0] + chan = [c for c in self.hchannels if chan_match(c.channel.name, epg_channel)] + + """ + + activate_hchannel(chan[0]) finally: self.chan_lcn_buf = 0 return True