Hello World

Moderators: Gully, peteru

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Jul 30, 2020 15:00

Found some explanation
https://www.linuxsat-support.com/thread/4957-how-to-create-ipk-file-on-enigma2-based-receivers/?postID=519252#post519252 wrote:Unfortunately, in most Enigma firmware, the "ar" tool is included in Busybox only as a very simple tool (with missing features). For example, in OpenPLi-7, Busybox is currently in v1.24.1. The lightweight version of "ar" in this Busybox does not support the file combining argument (ar -r as for example) .

Newer Busybox v1.29.2, which is already in OpenATV 6.3 firmware, already supports this argument (ar -r). However, it is still a lightweight "ar" version.

For this purpose it is possible to copy the alternative binary "ar" or the alternative complete newer Busybox (also as a binary, for a specific chipset). The busybox is located in the "/bin" directory and the "ar" tool is located in the "/usr/bin" directory. In both cases, as usually the sym-link link is used in most Enigma (the "ar" tool is called from Busybox and not directly as a binary file). I don't recommend overwriting "/bin/busybox*" files due to possible compatibility issues in some firmwares. Conversely, you can override the "/usr/bin/ar" tool (if you have the right binary file for your chipset!)

Also found out the hard way that control files need to have a linefeed at the end of each line, not carriage return + line feed (as all Windows text editors use). Otherwise the control file won't work and you'll get an error message "Error: Package name contains illegal characters".

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Jul 30, 2020 15:05

Regarding package files, is this actually the best way to deploy plugins, or is there a better way? It seems that ipk files are just archives and don't seem to provide any "uninstall" feature - or does enigma handle that? It would be nice to allow the user an easy way to uninstall the plugin, such as through the Plugins menu on the Beyonwiz. I want to make it as easy for the user as possible to install and uninstall my plugin.

User avatar
peteru
Uber Wizard
Posts: 9735
Joined: Tue Jun 12, 2007 23:06
Location: Sydney, Australia
Contact:

Re: Hello World

Post by peteru » Thu Jul 30, 2020 15:17

root@beyonwizt4:~# opkg install opkg-doc opkg-utils binutils should give you a few of the things you need. You can use opkg files binutils to find out where to look for the relevant binaries.

And yes, opkg has the means to uninstall.

"Beauty lies in the hands of the beer holder."
Blog.

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Thu Jul 30, 2020 16:08

sonicblue wrote:
Thu Jul 30, 2020 13:58
The problem is I can't find a ipkg-build that works on the V2.

Here's the one I use.
Attachments
ipkg-build.zip
(1.66 KiB) Downloaded 64 times
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Jul 30, 2020 16:29

Thanks, I've got it working now.

The version of opkg-tools in post#1 here is also compatible with V2.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Jul 30, 2020 16:53

[deleted]

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Fri Jul 31, 2020 14:09

I'm trying to detect the system events when the user either plays, pauses, or seeks in the current video.

Inside iService.h there aren't any such events to monitor. I've tried monitoring all of them, and none of them occur when performing those actions.

A third party source describes another version of enigma having such iPlayableService events:
https://dream.reichholf.net/pydoc/html/d1/d90/classenigma_1_1iPlayableService__ENUMS.html wrote:evPause = _enigma.iPlayableService_ENUMS_evPause
evPlay = _enigma.iPlayableService_ENUMS_evPlay
evSeek = _enigma.iPlayableService_ENUMS_evSeek
statePlay = _enigma.iPlayableService_ENUMS_statePlay
statePause = _enigma.iPlayableService_ENUMS_statePause

None of the above work on our version of enigma (crashes due to no such attributes in iPlayableService).

The closest I could find in the Beyonwiz codebase is this in DVD.py:

Code: Select all

self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
{
	iPlayableService.evUser: self.__timeUpdated,
	iPlayableService.evUser+1: self.__statePlay,
	iPlayableService.evUser+2: self.__statePause,
However those evUser events never occur.

In decoder.h:

Code: Select all

/*
	The following states exist:

	 - stop: data source closed, no playback
	 - pause: data source active, decoder paused
	 - play: data source active, decoder consuming
	 - decoder fast forward: data source linear, decoder drops frames
	 - trickmode, highspeed reverse: data source fast forwards / reverses, decoder just displays frames as fast as it can
	 - slow motion: decoder displays frames multiple times
	*/
enum {
	stateStop,
	statePause,
	statePlay,
	stateDecoderFastForward,
	stateTrickmode,
	stateSlowMotion
};
I can't find any code examples which use this though.

Also looked at InfoBarGenerics.py to see if there's a way to set up a callback by appending my own function to an "onSeek" type list in one of the classes, but can't really see anything.

There's also a class in it called InfoBarNotifications, not sure if that can be used somehow.

Surely this has got to be possible? I considered detecting key presses but the user could press direction arrow left/right while in the GUI menus which doesn't correspond to a seek.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Fri Jul 31, 2020 14:46

sonicblue wrote:
Fri Jul 31, 2020 14:09
Also looked at InfoBarGenerics.py to see if there's a way to set up a callback by appending my own function to an "onSeek" type list in one of the classes, but can't really see anything.

It seems InfoBarSeek.setSeekState() performs

Code: Select all

for c in self.onPlayStateChanged:
			c(self.seekstate)
Perhaps then we could append our own function into the onPlayStateChanged array?

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Fri Jul 31, 2020 15:28

sonicblue wrote:
Fri Jul 31, 2020 14:46
Perhaps then we could append our own function into the onPlayStateChanged array?

So this works, but only for play/pause, not seek, and is a huge pain to implement because every infobar that inherits InfoBarSeek needs to have my callback func appended to its self.onPlayStateChange. That makes a lot of assumptions about the version of Enigma as well.

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Fri Jul 31, 2020 15:50

Of the events that you're interested in, eDVBServicePlay only sends evStart and evStopped. The only user event it sends is evUser+1 for "timeshift file changed".

An eDVBServicePlay's m_cue sends an eCueSheet.evtSeek on seeks, but that doesn't seem to be propagated through the eDVBServicePlay.

The relevant source files are service/iservice.h, service/servicedvb.{h,cpp} dvb/dvb.{h,cpp} & dvb/idvb.h.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sat Aug 01, 2020 20:34

prl wrote:
Fri Jul 31, 2020 15:50
Of the events that you're interested in, eDVBServicePlay only sends evStart and evStopped. The only user event it sends is evUser+1 for "timeshift file changed".

An eDVBServicePlay's m_cue sends an eCueSheet.evtSeek on seeks, but that doesn't seem to be propagated through the eDVBServicePlay.

The relevant source files are service/iservice.h, service/servicedvb.{h,cpp} dvb/dvb.{h,cpp} & dvb/idvb.h.
Thanks.

Unfortunately I couldn't find anything explicit to detect whether user is currently playing or paused.

It seems we would have to be a bit creative; one idea was to check to see what the current video bit rate is, and if it's 0, then that could be an indicator the video steam is paused.

Another option might be to look at the frame counter in proc/vfmw/pts_info and if it increased then the decoder is decoding frames and therefore playing.

If anyone else knows of a way to detect play/pause state without having to rewrite InfoBar[Generics].py, I'd love to hear it :)

edit: proc/hisi/msp/sync00 appears to contain the play/pause state. I will use that for now, but it feels a bit wrong - I'm sure there has got to be some enigma value that can tell us the play/pause state. Surely?

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sun Aug 09, 2020 08:24

I'm trying to implement my plugin's startup routine inside a try/except block to avoid the possibility of it going into a crash reboot loop on startup which would lock the user out of their box and would be quite disastrous.

I can't seem to get it to work though. It appears to work fine when launched from WHERE_SESSIONSTART, but not from WHERE_PLUGINMENU or WHERE_PLUGINMENU.

Here's a working example

Code: Select all

import os
from Tools import Notifications
from Screens.Screen import Screen
from Screens.MessageBox import MessageBox
from Plugins.Plugin import PluginDescriptor


class Daemon(Screen):
	def __init__(self, session):	
		try:
			Screen.__init__(self, session)
			var+=1 # raise exception
		except Exception as e:
			Notifications.AddPopup(text=_("Unable to launch Daemon.\nReason: %s" %e),
			type=MessageBox.TYPE_ERROR, timeout=20)
			
			# this appears to work fine, although Screen.__init__() must come first, otherwise
			# enigma says "RuntimeError: modal open are allowed only from a screen which is modal!"
			

class GUI(Screen):
	def __init__(self, session):	
		try:
			Screen.__init__(self, session)
			var+=1
		except Exception as e:
			Notifications.AddPopup(text=_("Unable to launch GUI.\nReason: %s" %e),
			type=MessageBox.TYPE_ERROR, timeout=20)
			
			# execution reaches here, and there are no errors in the log, but the notification
			# doesn't appear on screen and the user is locked out of the screen (every button 
			# press just shows the "not allowed" button press icon, as if there is some invisible
			# screen in front of everything)
			
			# also tried
			Notifications.AddNotification(MessageBox, _("Unable to launch GUI.\nReason: %s" %e),
			type=MessageBox.TYPE_ERROR, timeout=20)

			
def startDaemon(session, **kwargs):
	session.open(Daemon)
	
	
def startGUI(session, **kwargs):
	session.open(GUI)
	
	
def Plugins(**kwargs):			
	DescriptorList = []   
	DescriptorList.append(
		PluginDescriptor(
			name="MyPlugin",		
			where = PluginDescriptor.WHERE_SESSIONSTART,		
			description=_("MyPlugin"),
			fnc=startDaemon
		)	
	)	
	DescriptorList.append(
        PluginDescriptor(
			name="MyPlugin",		
			where = [
					PluginDescriptor.WHERE_PLUGINMENU,
					PluginDescriptor.WHERE_EXTENSIONSMENU
					],
            description=_("MyPlugin"),
            fnc=startGUI
		)		
	)	
	return DescriptorList

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sun Aug 09, 2020 09:12

It seems calling self.close() in the GUI screen's exception handler solves the issue.

Strange that we don't need to do it for the Daemon GUI.

Should I be doing it anyway, or is Enigma deliberately designed to not display any screens which are launched from WHERE_SESSIONSTART? Is this a safe assumption for all versions of Enigma or will some boxes behave differently and end up locking the user out of their box with an invisible Daemon screen on top of everything with no action map and no way of closing it (assuming that's what's happening with the GUI screen).

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Sun Aug 09, 2020 14:04

sonicblue wrote:
Sun Aug 09, 2020 09:12
It seems calling self.close() in the GUI screen's exception handler solves the issue.

Strange that we don't need to do it for the Daemon GUI.

Should I be doing it anyway,

At that point, you shouldn't be doing it.

The reason is that the screen is not fully constructed at the time when Screen.__init__() returns. It's not fully constructed when the user screen's __init__() returns (in this case when Daemon.__init__() returns). I suspect that close() shouldn't be called on a screen until at least after the time that onLayoutFinish callbacks are called (e.g. from methods called from the screen's ActionMaps).

I doubt that Screen.__init__() will throw an exception in that case anyway. All it does at that point is allocate (but not, for example) skin, all its attributes and key-value pairs. Skinning happens after Daemon.__init__() returns, initiated by the class's session. If there are exceptions during skinning, they will normally be caught in the skin.

BTW, if you haven't found it yet, class Session is defined in mytest.py (don't ask :roll: ) in the top directory of the repository.
sonicblue wrote:
Sun Aug 09, 2020 09:12
or is Enigma deliberately designed to not display any screens which are launched from WHERE_SESSIONSTART?

There is no problem with screens launched by WHERE_SESSIONSTART classes. They display just fine. Both the IceTV and Series2Folder plugins do it. I'm sure others do, too.

I don't recall seeing an instance where a WHERE_SESSIONSTART daemon was a Screen, though.

Why is it necessary for it to be a Screen?

Is the user intended to interact with the Daemon screen?
sonicblue wrote:
Sun Aug 09, 2020 09:12
Is this a safe assumption for all versions of Enigma or will some boxes behave differently and end up locking the user out of their box with an invisible Daemon screen on top of everything with no action map and no way of closing it (assuming that's what's happening with the GUI screen).

I'd expect all versions of enigma2 to behave in the same way in this respect.

If you want the user to be able to exit from a screen, the screen needs to have an ActionMap that binds a key to self.close. Always, every screen, where the user needs to be able to exit the screen on a remote button action.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

IanSav
Uber Wizard
Posts: 16846
Joined: Tue May 29, 2007 15:00
Location: Melbourne, Australia

Re: Hello World

Post by IanSav » Sun Aug 09, 2020 14:13

Hi Prl,
prl wrote:
Sun Aug 09, 2020 14:04
BTW, if you haven't found it yet, class Session is defined in myclass.py (don't ask :roll: ) in the top directory of the repository.
I think you mean mytest.py. :)

Regards,
Ian.

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Sun Aug 09, 2020 14:26

I do indeed. Fixed. Thanks :oops:
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sun Aug 09, 2020 20:06

prl wrote:
Sun Aug 09, 2020 14:04
Why is it necessary for it to be a Screen?

It uses the ServiceEventTracker class to detect changes to the video content, and that class "tracks service events into a screen" (its __init__ takes a Screen as an argument).

prl wrote:
Sun Aug 09, 2020 14:04
Is the user intended to interact with the Daemon screen?

No. In that case, what should I do? I need it to be a Screen, but I don't want the screen to appear, because if it did, it doesn't have a skin or actionmap, causing the user to be locked out of their box with an invisible screen on top of everything in the case that Enigma did show it. It seems Engima deliberately doesn't display skinless screens if called from WHERE_SESSIONSTART, but I don't like not knowing why my code works. I'll probably just associate an action map with it to be safe, unless you know of a better way? eg. calling self.close() or self.hide() at the end of Daemon's layoutfinished? Or will self.close() somehow invalidate the screen, causing failure of other modules that rely on it being a screen (i.e ServiceEventTracker)?

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sun Aug 09, 2020 22:48

prl wrote:
Sun Aug 09, 2020 14:04
class Session is defined in mytest.py (don't ask :roll: )

I've noticed this mytest.py file - strange name for such an important file :lol:

Is there some backstory to it? Did something get unintentionally hardcoded to the name "mytest" or something?

IanSav
Uber Wizard
Posts: 16846
Joined: Tue May 29, 2007 15:00
Location: Melbourne, Australia

Re: Hello World

Post by IanSav » Sun Aug 09, 2020 23:11

Hi Sonicblue,

The reason for the name appears to be lost to history. There are a few of us pushing to have the name improved.

Regards,
Ian.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Mon Aug 10, 2020 02:55

prl wrote:
Sun Aug 09, 2020 14:04
There is no problem with screens launched by WHERE_SESSIONSTART classes. They display just fine. Both the IceTV and Series2Folder plugins do it. I'm sure others do, too.

After further testing it seems Enigma deliberately doesn't show any Screen if it was launched from WHERE_SESSIONSTART.

i.e SESSIONSTART > Screen.__init__() > layoutFinished() > nothing is shown on screen.

but: PLUGINSMENU > Screen.__init__() > layoutFinished() > GUI is shown on screen

For my plugin this is desirable as my background Daemon should not show anything on screen, but still needs to inherit Screen in order to get access to service data.
sonicblue wrote:
Sun Aug 09, 2020 20:06
calling self.close() or self.hide() at the end of Daemon's layoutfinished?

It seems that self.hide() is not a reliable option, as a Screen even when hidden, can still be "on top" of everything else and block all user input, eg:

Code: Select all

import os
from Tools import Notifications
from Screens.Screen import Screen
from Screens.MessageBox import MessageBox
from Plugins.Plugin import PluginDescriptor
from enigma import eTimer
from Components.ActionMap import ActionMap
from Components.Sources.StaticText import StaticText
	
class GUI(Screen):
	def __init__(self, session):	
		
		Screen.__init__(self, session)	
		self.skinName = ["Setup"]
		self["actions"] = ActionMap(["SetupActions", "ColorActions", "MenuActions"],
		{		
			"red": self.close, "cancel": self.close,		
			"green": self.hide,			
		}, -2)		
		self["key_red"] = StaticText(_("self.close()"))
		self["key_green"] = StaticText(_("self.hide()"))

def startGUI(session, **kwargs):
	session.open(GUI)
	

def Plugins(**kwargs):			
	DescriptorList = []   
	DescriptorList.append(
		PluginDescriptor(
			name="MyPlugin",
			where = [
					# PluginDescriptor.WHERE_SESSIONSTART,  	# doesn't show GUI 
					PluginDescriptor.WHERE_PLUGINMENU,	# shows GUI
					PluginDescriptor.WHERE_EXTENSIONSMENU	# shows GUI
					],
			description=_("MyPlugin"),
			fnc=startGUI
		)	
	)
	return DescriptorList
My guess is it's something in the definition of this particular Setup skin causing it to remain on top of all other GUI screens even when hidden, possibly the zPosition or transparent attributes of the skin.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Mon Aug 10, 2020 05:32

sonicblue wrote:
Mon Aug 10, 2020 02:55
My guess is it's something in the definition of this particular Setup skin causing it to remain on top of all other GUI screens even when hidden, possibly the zPosition or transparent attributes of the skin.

After more testing, it doesn't seem to matter what skin attributes I use: if the screen is opened using session.open(GUI), it will always be active and "on top", and blocks all other screens, until such time GUI.close() is called, at which point anything which relies on that screen will no longer work, such as the ServiceEventTracker I'm using. So I cannot close my Daemon screen, it must always be open, but invisible and not interfering with any other screens.

It seems if I want to avoid blocking other screens I must use dlg = session.instantiateDialog(GUI). This appears to execute GUI.__init__(), but doesn't show it on screen until manually calling dlg.show() or session.execDialog(dlg).

https://dreambox.de/board/index.php?thr ... og-dialog/

However this still leaves the mystery about why enigma doesn't show GUI's on WHERE_SESSIONSTART. It's desirable to me that it doesn't, but I still don't like not knowing why my code works.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Mon Aug 10, 2020 07:32

sonicblue wrote:
Mon Aug 10, 2020 05:32
It seems if I want to avoid blocking other screens I must use dlg = session.instantiateDialog(GUI). This appears to execute GUI.__init__(), but doesn't show it on screen until manually calling dlg.show() or session.execDialog(dlg).

It should be noted that timers belonging to the GUI class won't fire unless we call execDialog.
Last edited by sonicblue on Mon Aug 10, 2020 10:29, edited 2 times in total.

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Mon Aug 10, 2020 10:24

sonicblue wrote:
Mon Aug 10, 2020 07:32
It should be noted that timers belonging to the GUI class won't fire unless we call execDialog.
sonicblue wrote:
Mon Aug 10, 2020 07:32

Code: Select all

def startDaemon(session, **kwargs):
	daemon = session.instantiateDialog(Daemon)

It seems the issue is that daemon is a local variable pointing to the Screen object, so enigma must be losing a reference to it when the function ends. If I make daemon a global, the problem goes away and everything works as expected, except if I call session.execDialog(daemon) , then daemon.show() no longer works.

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Mon Aug 10, 2020 16:19

sonicblue wrote:
Sun Aug 09, 2020 20:06
prl wrote:
Sun Aug 09, 2020 14:04
Why is it necessary for it to be a Screen?

It uses the ServiceEventTracker class to detect changes to the video content, and that class "tracks service events into a screen" (its __init__ takes a Screen as an argument).

If that's the only mechanism, then it doesn't say that Daemon has to be a Screen, only that it has to have an instance of one.

Have you checked whether you can get the relevant events if you add a method hook into session.nav.event? That's where ServiceEventTracker gets its events from.
sonicblue wrote:
Sun Aug 09, 2020 20:06
prl wrote:
Sun Aug 09, 2020 14:04
Is the user intended to interact with the Daemon screen?

No. In that case, what should I do? I need it to be a Screen, but I don't want the screen to appear, because if it did, it doesn't have a skin or actionmap, causing the user to be locked out of their box with an invisible screen on top of everything in the case that Enigma did show it. It seems Engima deliberately doesn't display skinless screens if called from WHERE_SESSIONSTART, but I don't like not knowing why my code works. I'll probably just associate an action map with it to be safe, unless you know of a better way? eg. calling self.close() or self.hide() at the end of Daemon's layoutfinished? Or will self.close() somehow invalidate the screen, causing failure of other modules that rely on it being a screen (i.e ServiceEventTracker)?

If you have to make Daemon be, or have, a screen, you could just instantiate it instead of opening it. Then it defaults to not intercepting user button presses. From what I can see, it should still be able to receive and process ServiceEventTracker) events.

The choice is between not having the built-in dispatch map in ServiceEventTracker, and having to create a pretend Screen that does nothing more than intercept events.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Mon Aug 10, 2020 16:25

sonicblue wrote:
Mon Aug 10, 2020 07:32
sonicblue wrote:
Mon Aug 10, 2020 05:32
It seems if I want to avoid blocking other screens I must use dlg = session.instantiateDialog(GUI). This appears to execute GUI.__init__(), but doesn't show it on screen until manually calling dlg.show() or session.execDialog(dlg).

It should be noted that timers belonging to the GUI class won't fire unless we call execDialog.

Are you sure about that? Can you point me to the code that prevents it?

Timers certainly work in classes that aren't Screens. Both the IceTV and Series2Folder plugins use timers in classes that aren't Screens at.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Mon Aug 10, 2020 16:43

sonicblue wrote:
Mon Aug 10, 2020 10:24
sonicblue wrote:
Mon Aug 10, 2020 07:32

Code: Select all

def startDaemon(session, **kwargs):
	daemon = session.instantiateDialog(Daemon)

It seems the issue is that daemon is a local variable pointing to the Screen object, so enigma must be losing a reference to it when the function ends. If I make daemon a global, the problem goes away and everything works as expected, except ...

That's a common issue whether you use a screen or just an ordinary daemon class. It's the downside of garbage collection: stuff being treated as garbage when it's not.

Starting background plugin class instances generally looks something like this:

Code: Select all

_myPluginMain = None
...
    PluginDescriptor(
        name="MyPlugin",
        where=PluginDescriptor.WHERE_SESSIONSTART,
        description=_("My Great Plugin"),
        needsRestart=False,
        fnc=myPluginMain
    ),
...
def myPluginMain(reason, session, **kwargs):
    global _myPluginMain

    if reason == 0: # Starting
        if not _myPluginMain:
            _myPluginMain = MyPlugin(session)
    elif reason == 1: # Shutting down
        if _myPluginMain:
            _myPluginMain.stopTimers()
            _myPluginMain = None
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Tue Aug 11, 2020 03:38

prl wrote:
Mon Aug 10, 2020 16:19
If that's the only mechanism, then it doesn't say that Daemon has to be a Screen, only that it has to have an instance of one.

Possibly, but the code example I'm following in VideoMode.py is a Screen and sends a copy of itself as the screen parameter.
prl wrote:
Mon Aug 10, 2020 16:19
Have you checked whether you can get the relevant events if you add a method hook into session.nav.event? That's where ServiceEventTracker gets its events from.

I tried that and it does appear to work. I would just need to manually parse out the events I'm interested in.
prl wrote:
Mon Aug 10, 2020 16:43
That's a common issue whether you use a screen or just an ordinary daemon class. It's the downside of garbage collection: stuff being treated as garbage when it's not.

Yep. What made this even more confusing is that ServiceEventTracker keeps its own copy of the screen too, so when I enabled the ServiceEventTracker the garbage collection was no longer occurring and that confused the hell out of me :lol:

The only unresolved mystery I'm trying to solve is why session.execDialog() causes Screen.show() to fail. Here is a code example.

Code: Select all

import os
import traceback
from datetime import datetime
from time import time, strftime
from Tools import Notifications
from Screens.Screen import Screen
from Screens.MessageBox import MessageBox
from Plugins.Plugin import PluginDescriptor
from enigma import eTimer, iPlayableService, iServiceInformation
from enigma import ePoint
from Components.ServiceEventTracker import ServiceEventTracker
from Components.ActionMap import ActionMap
from Components.Sources.StaticText import StaticText

class Daemon(Screen):
	
	instance = None
	
	def __init__(self, session):
		
		Daemon.instance = self  # deleting this causes Daemon.Success()
								# to not arrive due to garbage collection
			
		# just a green square to tell if the screen got shown
		self.skin = """<screen name="Daemon" position="100,100" size="200,200" flags="wfNoBorder" backgroundColor="#c000ff00" zPosition="11" ></screen>"""
			
		Screen.__init__(self, session)
		self.timer = eTimer()
		self.timer.callback.append(self.Success)
		self.timer.start(3000, True)													
		print "Daemon.__init__() finished"
		
	def Success(self):
		print "Daemon.Success()"
		

def startDaemon(session, **kwargs):
	daemon = session.instantiateDialog(Daemon)
	daemon.show()
	session.execDialog(daemon) # removing this causes daemon.show() to work
	daemon.show()
	
	
def Plugins(**kwargs):			
	DescriptorList = []   
	DescriptorList.append(
		PluginDescriptor(
			name="Daemon",		
			where = PluginDescriptor.WHERE_SESSIONSTART,		
			description=_("Daemon"),
			fnc=startDaemon,
			needsRestart=True
		)	
	)	
	return DescriptorList

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Tue Aug 11, 2020 10:48

sonicblue wrote:
Tue Aug 11, 2020 03:38
prl wrote:
Mon Aug 10, 2020 16:19
Have you checked whether you can get the relevant events if you add a method hook into session.nav.event? That's where ServiceEventTracker gets its events from.
I tried that and it does appear to work. I would just need to manually parse out the events I'm interested in.

That's hardly more difficult than using ServiceEventTracker. You just need to make a similar dict to what you already use for ServiceEventTracker, mapping the events you're interested in to functions/methods to call, and call the indexed event method for the events in the map.
sonicblue wrote:
Tue Aug 11, 2020 03:38
The only unresolved mystery I'm trying to solve is why session.execDialog() causes Screen.show() to fail. Here is a code example.

Code: Select all

	session.execDialog(daemon) # removing this causes daemon.show() to work

Why are you calling session.execDialog(daemon)? If you don't want daemon to be interactive, don't do anything more than daemon.show(). If you do want daemon to be interactive, call daemon.execBegin() after the show, as I did in the working example code I posted earlier in the topic.

I still think that you'd be better off having Daemon not be a Screen, and doing the small amount of work needed to have it handle events direct from Navigation. There's an example of handling events in the IceTV plugin (record events in that case, but it's just a matter of which event dispatcher you attach to in Navigation).

I don't think that the small amount of convenience that ServiceEventTracker offers is worth the effort of trying to create Screen with no purpose other than to merely intercept events.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Wed Aug 12, 2020 11:32

prl wrote:
Tue Aug 11, 2020 10:48
That's hardly more difficult than using ServiceEventTracker. You just need to make a similar dict to what you already use for ServiceEventTracker, mapping the events you're interested in to functions/methods to call, and call the indexed event method for the events in the map.

Yep, if I had to write the plugin again from scratch I'd probably do it that way. For now I'll stick to the "don't fix it if it ain't broke" rule as I've had bad luck in the past changing code to do things "the right way" when it wasn't necessary :lol:

For example if you recall earlier I mentioned I was using the ServiceEventTracker object just fine without passing it a Screen, and then mysteriously it started to crash (maybe due to garbage collection again?). So without knowing why it wants a Screen I'm not going to make any assumptions about the way Enigma works under the hood.

Also what if different version enigmas implement the iServiceEvents differently (like how that API mentions evPlay events which our version doesn't have) and the ServiceEventTracker maybe parses the events correctly for that version of enigma? I'm probably wrong, but not willing to take the risk.

prl wrote:
Tue Aug 11, 2020 10:48
Why are you calling session.execDialog(daemon)?

Because there's no documentation and that German forum post was saying to do it. I have no idea what any of those exec functions in mytest.py actually do. I don't know what it means when a screen is "execing". It seems we need it to be execing to enable its Action Map though, as per your example.

prl wrote:
Tue Aug 11, 2020 10:48
If you don't want daemon to be interactive, don't do anything more than daemon.show().

Wait, but I don't want my Daemon screen class to show anything, so I'm not calling show at all. Must I call show?

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Wed Aug 12, 2020 15:11

sonicblue wrote:
Wed Aug 12, 2020 11:32
the ServiceEventTracker maybe parses the events

The added complexity in ServiceEventTracker is all about what service the screen is running. There's no "parsing" of events, just dispatching them to a function.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Wed Aug 12, 2020 17:43

sonicblue wrote:
Wed Aug 12, 2020 11:32
prl wrote:
Tue Aug 11, 2020 10:48
Why are you calling session.execDialog(daemon)?

Because there's no documentation and that German forum post was saying to do it. I have no idea what any of those exec functions in mytest.py actually do. I don't know what it means when a screen is "execing". It seems we need it to be execing to enable its Action Map though, as per your example.

Unless you really know what you're doing you probably shouldn't call any Session methods except open() and instantiateDialog(). I don't think that I've ever called anything else.

"execing" in a screen pretty much means "processing user button presses". I don't think that it implies much else. The implementation of execBegin()/execEnd() is (mostly) adding/removing the button-to-method call mappings in a Screen's ActionMaps, though other Components in the Screen can also ask to receive execBegin()/execEnd() calls by providing an implementation of the methods that does something.
sonicblue wrote:
Wed Aug 12, 2020 11:32
prl wrote:
Tue Aug 11, 2020 10:48
If you don't want daemon to be interactive, don't do anything more than daemon.show().

Wait, but I don't want my Daemon screen class to show anything, so I'm not calling show at all. Must I call show?

I only mentioned calling it because the code you posted calls it. I don't think it needs to be called if you're just using the Screen as a bloated event dispatcher.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Aug 13, 2020 00:31

prl wrote:
Tue Aug 11, 2020 10:48
Why are you calling session.execDialog(daemon)? If you don't want daemon to be interactive, don't do anything more than daemon.show(). If you do want daemon to be interactive, call daemon.execBegin() after the show, as I did in the working example code I posted earlier in the topic.

Sorry, for some reason I had mistakenly thought that all Screens must be instantiated via session.open(Screen) or session.instantiateDialog(Screen). As per your example and VideoMode.py, we can simply create the instance in the typical way, and avoid creating anything that would be rendered or interactive.

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Fri Aug 14, 2020 12:36

Why do you insist on using a Screen as just an event dispatcher?
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sat Aug 15, 2020 05:52

prl wrote:
Fri Aug 14, 2020 12:36
Why do you insist on using a Screen as just an event dispatcher?

Simply because that's how VideoMode.py does it, and all my testing has been done using the same method. I realise it can be done in less lines of code without using the ServiceEventTracker class, but is that enough reason to motivate anyone to submit a pull request for VideoMode.py?

VideoMode.py

Code: Select all

def autostart(session):
	AutoVideoMode(session)
	
class AutoVideoMode(Screen):
	def __init__(self, session):
		Screen.__init__(self, session)
		self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
			{
				iPlayableService.evStart: self.__evStart,
				iPlayableService.evVideoSizeChanged: self.VideoChanged,
				iPlayableService.evVideoProgressiveChanged: self.VideoChanged,
				iPlayableService.evVideoFramerateChanged: self.VideoChanged,
				iPlayableService.evBuffering: self.BufferInfo,
			})

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Sat Aug 15, 2020 06:07

It's not the extra lines of code (the difference would be so small not to really matter, and not using Screen might be a few lines longer), but unnecessarily dragging a parent class into something that doesn't really need it.

Here's what it might look like without using Screen (completely untested):

Code: Select all

class AutoVideoMode(object):
	def __init__(self, session):
		self.eventActions = ServiceEventTracker(screen=self, eventmap=
			{
				iPlayableService.evStart: self.__evStart,
				iPlayableService.evVideoSizeChanged: self.VideoChanged,
				iPlayableService.evVideoProgressiveChanged: self.VideoChanged,
				iPlayableService.evVideoFramerateChanged: self.VideoChanged,
				iPlayableService.evBuffering: self.BufferInfo,
			})
		session.nav.event.append(self.gotEvent)

	def gotEvent(evt):
		if evt in self.eventActions:
			self.eventActions[evt](evt)
You'd need to undo the hook in session.nav.event when the instance was deleted. Perhaps with a __del__() method.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Sat Aug 15, 2020 09:19

prl wrote:
Sat Aug 15, 2020 06:07
Here's what it might look like without using Screen (completely untested):
class AutoVideoMode(object):
    def __init__(self, session):
        self.eventActions = ServiceEventTracker(screen=self

I did actually try it as above, and it worked for a while and then if you recall I mentioned having some crashes until passing it a screen again. I've forgotten what the log said.

prl wrote:
Sat Aug 15, 2020 06:07
You'd need to undo the hook in session.nav.event when the instance was deleted. Perhaps with a __del__() method.

Not sure if this is related:

class ServiceEventTracker wrote: def __del_event(self):
    EventMap = ServiceEventTracker.EventMap.setdefault
        for x in self.__eventmap.iteritems():
            EventMap(x[0], []).remove((self.__passall, self.__screen, x[1]))

prl
Wizard God
Posts: 32702
Joined: Tue Sep 04, 2007 13:49
Location: Canberra; Black Mountain Tower transmitters

Re: Hello World

Post by prl » Sat Aug 15, 2020 12:52

sonicblue wrote:
Sat Aug 15, 2020 09:19
Not sure if this is related:
class ServiceEventTracker wrote: def __del_event(self):
    EventMap = ServiceEventTracker.EventMap.setdefault
        for x in self.__eventmap.iteritems():
            EventMap(x[0], []).remove((self.__passall, self.__screen, x[1]))

Yes, pretty similar:

Code: Select all

class ServiceEventTracker:
	...
	def __init__(self, screen, eventmap):
		...
		screen.onClose.append(self.__del_event)

	def __del_event(self):
Except that a non-screen class doesn't have a close() to run an onClose callback. Normal class instances go away when the garbage collector decides they aren't being referenced. Just before the instance is destroyed, __del__() is called on the instance, a bit like a C++ destructor. You can put any necessary cleanups, like making sure timers are stopped and removing event callbacks, there.

You just override it in the class definition, like
def __del__(self): # do cleanup
Not doing this cleanup is a potential problem.
Peter
T4 HDMI
U4, T4, T3, T2, V2 test/development machines
Sony BDV-9200W HT system
LG OLED55C9PTA 55" OLED TV

sonicblue
Master
Posts: 247
Joined: Wed Oct 25, 2017 14:30

Re: Hello World

Post by sonicblue » Thu Sep 24, 2020 19:02

Many thanks to everyone for your help, namely prl, peteru, adoxa and MrQuade, without whom I probably wouldn't have been able to complete the plugin, which is now available in the plugins subforum!

Post Reply

Return to “Developers Community”