Using ctypes instead popen, Fix pairing issue, add thumbnails to game list.
This commit is contained in:
parent
e878e935dc
commit
984fb7e7b9
4 changed files with 180 additions and 80 deletions
|
@ -1,91 +1,80 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import urllib
|
|
||||||
import urlparse
|
|
||||||
import xbmcgui
|
|
||||||
import xbmcplugin
|
import xbmcplugin
|
||||||
import xbmcaddon
|
import xbmcaddon
|
||||||
import re
|
import xbmcgui
|
||||||
import subprocess
|
import xbmc
|
||||||
|
|
||||||
my_addon = xbmcaddon.Addon()
|
import random
|
||||||
my_env = os.environ.copy()
|
import urllib
|
||||||
my_env["LD_LIBRARY_PATH"] = my_addon.getAddonInfo("path")+"/libs:" + my_env["LD_LIBRARY_PATH"]
|
import urlparse
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
from resources.moonlight import LibGameStream
|
||||||
|
|
||||||
base_url = sys.argv[0]
|
base_url = sys.argv[0]
|
||||||
addon_handle = int(sys.argv[1])
|
addon_handle = int(sys.argv[1])
|
||||||
args = urlparse.parse_qs(sys.argv[2][1:])
|
args = urlparse.parse_qs(sys.argv[2][1:])
|
||||||
|
addon = xbmcaddon.Addon()
|
||||||
xbmcplugin.setContent(addon_handle, 'files')
|
xbmcplugin.setContent(addon_handle, "files")
|
||||||
|
|
||||||
def build_url(query):
|
def build_url(query):
|
||||||
return base_url + '?' + urllib.urlencode(query)
|
return base_url + "?" + urllib.urlencode(query)
|
||||||
|
|
||||||
mode = args.get('mode', None)
|
def index():
|
||||||
|
gs = LibGameStream(addon.getAddonInfo("path") + "/libs")
|
||||||
|
|
||||||
|
address = addon.getSetting("MOON_SERVER_IP")
|
||||||
|
if address == "0.0.0.0":
|
||||||
|
address = gs.discover_server()
|
||||||
|
|
||||||
|
if not gs.connect_server(address):
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
dialog.ok("Error", "Failed connect to server (%s)" % (address))
|
||||||
|
return
|
||||||
|
|
||||||
|
if not gs.isPaired():
|
||||||
|
pin = "%d%d%d%d" % (random.randint(0,9), random.randint(0,9), random.randint(0,9), random.randint(0,9))
|
||||||
|
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
dialog.notification("PIN code", "Insert the pin code in server: %s" % pin, xbmcgui.NOTIFICATION_INFO, 10000)
|
||||||
|
|
||||||
|
if gs.pair(pin):
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
dialog.ok("Paired", "Succesfully paired")
|
||||||
|
else:
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
dialog.ok("Error", "Failed to pair to server, try again")
|
||||||
|
return
|
||||||
|
|
||||||
|
for appId, name in gs.applist():
|
||||||
|
base_path = xbmc.translatePath(addon.getAddonInfo('profile')).decode('utf-8') + "/images"
|
||||||
|
|
||||||
|
if not os.path.exists(base_path):
|
||||||
|
os.makedirs(base_path)
|
||||||
|
|
||||||
|
poster_path = base_path + "/" + str(appId) + ".png"
|
||||||
|
|
||||||
|
if not os.path.isfile(poster_path):
|
||||||
|
gs.poster(appId, base_path)
|
||||||
|
xbmc.sleep(100)
|
||||||
|
|
||||||
|
xbmcplugin.addDirectoryItem(handle=addon_handle,
|
||||||
|
url=build_url({"mode": "stream", "app": name}),
|
||||||
|
listitem=xbmcgui.ListItem(label=name, thumbnailImage=poster_path),
|
||||||
|
isFolder=False)
|
||||||
|
|
||||||
|
xbmcplugin.endOfDirectory(addon_handle)
|
||||||
|
|
||||||
|
def stream(app = None):
|
||||||
|
with open(addon.getAddonInfo("path") + "/start_moonlight.tmp", "w") as f:
|
||||||
|
if app is None:
|
||||||
|
f.write("")
|
||||||
|
else:
|
||||||
|
f.write(app[0])
|
||||||
|
|
||||||
|
mode = args.get("mode", None)
|
||||||
if mode is None:
|
if mode is None:
|
||||||
xbmcplugin.addDirectoryItem(handle=addon_handle,
|
index()
|
||||||
url=build_url({'mode': 'pair'}),
|
|
||||||
listitem=xbmcgui.ListItem('Pair'),
|
|
||||||
isFolder=False)
|
|
||||||
|
|
||||||
xbmcplugin.addDirectoryItem(handle=addon_handle,
|
|
||||||
url=build_url({'mode': 'list'}),
|
|
||||||
listitem=xbmcgui.ListItem('List Games'),
|
|
||||||
isFolder=True)
|
|
||||||
|
|
||||||
xbmcplugin.addDirectoryItem(handle=addon_handle,
|
|
||||||
url=build_url({'mode': 'stream', 'app': ''}),
|
|
||||||
listitem=xbmcgui.ListItem('Stream Default'),
|
|
||||||
isFolder=False)
|
|
||||||
xbmcplugin.endOfDirectory(addon_handle)
|
|
||||||
elif mode[0] == "pair":
|
|
||||||
pair_args = ["pair"]
|
|
||||||
if my_addon.getSetting("MOON_SERVER_IP") != "0.0.0.0":
|
|
||||||
pair_args += [my_addon.getSetting("MOON_SERVER_IP")]
|
|
||||||
|
|
||||||
p = subprocess.Popen([my_addon.getAddonInfo("path") + "/bin/moonlight"] + pair_args, stdout=subprocess.PIPE, env=my_env)
|
|
||||||
pin = ""
|
|
||||||
|
|
||||||
while True:
|
|
||||||
l = p.stdout.readline()
|
|
||||||
if "following PIN" in l:
|
|
||||||
m = re.search(':\s(\d+)', l)
|
|
||||||
pin = m.group(0)
|
|
||||||
break
|
|
||||||
|
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
dialog.ok("PIN code", "Insert the pin code in server: %s" % pin)
|
|
||||||
|
|
||||||
ret = ''.join(p.stdout.readlines())
|
|
||||||
|
|
||||||
if "Succesfully paired" in ret:
|
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
dialog.ok("Paired", "Succesfully paired")
|
|
||||||
else:
|
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
dialog.ok("Error", "Failed to pair to server")
|
|
||||||
elif mode[0] == "list":
|
|
||||||
list_args = ["list"]
|
|
||||||
if my_addon.getSetting("MOON_SERVER_IP") != "0.0.0.0":
|
|
||||||
list_args += [my_addon.getSetting("MOON_SERVER_IP")]
|
|
||||||
p = subprocess.Popen([my_addon.getAddonInfo("path") + "/bin/moonlight"] + list_args, stdout=subprocess.PIPE, env=my_env)
|
|
||||||
|
|
||||||
ret = ''.join(p.stdout.readlines()).strip()
|
|
||||||
m = re.findall(r"\d.\s(.*?)\n|$", ret)
|
|
||||||
|
|
||||||
for game in list(m):
|
|
||||||
name = game.strip()
|
|
||||||
if name != "":
|
|
||||||
xbmcplugin.addDirectoryItem(handle=addon_handle,
|
|
||||||
url=build_url({'mode': 'stream', 'app': name}),
|
|
||||||
listitem=xbmcgui.ListItem(name),
|
|
||||||
isFolder=False)
|
|
||||||
xbmcplugin.endOfDirectory(addon_handle)
|
|
||||||
elif mode[0] == "stream":
|
elif mode[0] == "stream":
|
||||||
app = args.get('app', None)
|
app = args.get("app", None)
|
||||||
with open(my_addon.getAddonInfo("path") + '/start_moonlight.tmp', 'w') as f:
|
stream(app)
|
||||||
if app is None:
|
|
||||||
f.write("")
|
|
||||||
else:
|
|
||||||
f.write(app[0])
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="script.moonlight" name="Moonlight" version="1.0.1" provider-name="dead">
|
<addon id="script.moonlight" name="Moonlight" version="1.0.2" provider-name="dead">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
</requires>
|
</requires>
|
||||||
|
|
0
script.moonlight/resources/__init__.py
Normal file
0
script.moonlight/resources/__init__.py
Normal file
111
script.moonlight/resources/moonlight.py
Normal file
111
script.moonlight/resources/moonlight.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import ctypes
|
||||||
|
import os
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
GS_OK = 0
|
||||||
|
GS_FAILED = -1
|
||||||
|
GS_OUT_OF_MEMORY = -2
|
||||||
|
GS_INVALID = -3
|
||||||
|
GS_WRONG_STATE = -4
|
||||||
|
GS_IO_ERROR = -5
|
||||||
|
GS_NOT_SUPPORTED_4K = -6
|
||||||
|
|
||||||
|
class SERVER_DATA(ctypes.Structure):
|
||||||
|
_fields_ = [("address", ctypes.c_char_p),
|
||||||
|
("paired", ctypes.c_bool),
|
||||||
|
("supports4K", ctypes.c_bool),
|
||||||
|
("currentGame", ctypes.c_int),
|
||||||
|
("serverMajorVersion", ctypes.c_int)]
|
||||||
|
|
||||||
|
|
||||||
|
class APP_LIST(ctypes.Structure):
|
||||||
|
pass
|
||||||
|
|
||||||
|
APP_LIST._fields_ = [("name", ctypes.c_char_p),
|
||||||
|
("id", ctypes.c_int),
|
||||||
|
("next", ctypes.POINTER(APP_LIST))]
|
||||||
|
|
||||||
|
class _HTTP_DATA(ctypes.Structure):
|
||||||
|
_fields_ = [("memory", ctypes.POINTER(ctypes.c_ubyte)),
|
||||||
|
("size", ctypes.c_size_t)]
|
||||||
|
|
||||||
|
class LibGameStream:
|
||||||
|
def __init__(self, libpath = ""):
|
||||||
|
self.commomlib = ctypes.cdll.LoadLibrary(os.path.join(libpath, "libmoonlight-common.so.0"))
|
||||||
|
self.gslib = ctypes.cdll.LoadLibrary(os.path.join(libpath, "libgamestream.so.0"))
|
||||||
|
self.connected = False
|
||||||
|
self.address = ""
|
||||||
|
self.key_dir = ""
|
||||||
|
|
||||||
|
def discover_server(self):
|
||||||
|
addr = ctypes.create_string_buffer('\000' * 40)
|
||||||
|
self.gslib.gs_discover_server(addr)
|
||||||
|
return addr.value
|
||||||
|
|
||||||
|
def connect_server(self, address, key_dir = ""):
|
||||||
|
self.server = ctypes.pointer(SERVER_DATA(address, False, False, 0, 0))
|
||||||
|
self.address = address
|
||||||
|
|
||||||
|
if key_dir == "":
|
||||||
|
if "XDG_CONFIG_DIR" in os.environ:
|
||||||
|
key_dir = os.path.join(os.environ["XDG_CONFIG_DIR"], "moonlight")
|
||||||
|
else:
|
||||||
|
key_dir = os.path.join(os.environ["HOME"], ".cache", "moonlight")
|
||||||
|
|
||||||
|
self.key_dir = key_dir
|
||||||
|
ret = self.gslib.gs_init(self.server, ctypes.c_char_p(key_dir))
|
||||||
|
if ret == GS_OK:
|
||||||
|
self.connected = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def isPaired(self):
|
||||||
|
if not self.connected:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self.server[0].paired
|
||||||
|
|
||||||
|
def applist(self):
|
||||||
|
if not self.connected:
|
||||||
|
return None
|
||||||
|
|
||||||
|
lst = []
|
||||||
|
applst_ptr = ctypes.POINTER(APP_LIST)
|
||||||
|
applst = applst_ptr()
|
||||||
|
|
||||||
|
ret = self.gslib.gs_applist(self.server, ctypes.byref(applst))
|
||||||
|
if ret != GS_OK:
|
||||||
|
return None
|
||||||
|
|
||||||
|
while applst:
|
||||||
|
lst.append((applst[0].id, applst[0].name))
|
||||||
|
applst = applst[0].next
|
||||||
|
|
||||||
|
return lst
|
||||||
|
|
||||||
|
def poster(self, appId, toFolder):
|
||||||
|
unique_id = ""
|
||||||
|
with open(os.path.join(self.key_dir, "uniqueid.dat"), "r") as f:
|
||||||
|
unique_id = f.read()
|
||||||
|
|
||||||
|
uid = uuid4()
|
||||||
|
url = "https://%s:47984/appasset?uniqueid=%s&uuid=%s&appid=%d&AssetType=2&AssetIdx=0" % (self.address, unique_id, str(uid), appId)
|
||||||
|
|
||||||
|
self.gslib.http_create_data.restype = ctypes.POINTER(_HTTP_DATA)
|
||||||
|
data = self.gslib.http_create_data();
|
||||||
|
self.gslib.http_request(ctypes.c_char_p(url), data)
|
||||||
|
|
||||||
|
barray = bytearray(data[0].memory[0:data[0].size])
|
||||||
|
|
||||||
|
with open(os.path.join(toFolder, str(appId) + ".png"), "wb") as f:
|
||||||
|
f.write(barray)
|
||||||
|
|
||||||
|
self.gslib.http_free_data(data);
|
||||||
|
|
||||||
|
def pair(self, pin):
|
||||||
|
if not self.connected:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ret = self.gslib.gs_pair(self.server, ctypes.c_char_p(pin))
|
||||||
|
return ret == GS_OK
|
||||||
|
|
Loading…
Reference in a new issue