From 984fb7e7b9521ec34165c624b28dcefa729b10c7 Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 17 Feb 2016 05:05:53 -0200 Subject: [PATCH] Using ctypes instead popen, Fix pairing issue, add thumbnails to game list. --- script.moonlight/addon.py | 145 +++++++++++------------- script.moonlight/addon.xml | 4 +- script.moonlight/resources/__init__.py | 0 script.moonlight/resources/moonlight.py | 111 ++++++++++++++++++ 4 files changed, 180 insertions(+), 80 deletions(-) create mode 100644 script.moonlight/resources/__init__.py create mode 100644 script.moonlight/resources/moonlight.py diff --git a/script.moonlight/addon.py b/script.moonlight/addon.py index 197f388..9f460ac 100644 --- a/script.moonlight/addon.py +++ b/script.moonlight/addon.py @@ -1,91 +1,80 @@ -import os -import sys -import urllib -import urlparse -import xbmcgui import xbmcplugin import xbmcaddon -import re -import subprocess +import xbmcgui +import xbmc -my_addon = xbmcaddon.Addon() -my_env = os.environ.copy() -my_env["LD_LIBRARY_PATH"] = my_addon.getAddonInfo("path")+"/libs:" + my_env["LD_LIBRARY_PATH"] +import random +import urllib +import urlparse +import sys +import os + +from resources.moonlight import LibGameStream base_url = sys.argv[0] addon_handle = int(sys.argv[1]) args = urlparse.parse_qs(sys.argv[2][1:]) - -xbmcplugin.setContent(addon_handle, 'files') +addon = xbmcaddon.Addon() +xbmcplugin.setContent(addon_handle, "files") 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: - xbmcplugin.addDirectoryItem(handle=addon_handle, - 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) + index() elif mode[0] == "stream": - app = args.get('app', None) - with open(my_addon.getAddonInfo("path") + '/start_moonlight.tmp', 'w') as f: - if app is None: - f.write("") - else: - f.write(app[0]) + app = args.get("app", None) + stream(app) diff --git a/script.moonlight/addon.xml b/script.moonlight/addon.xml index 9a0541b..179b06d 100644 --- a/script.moonlight/addon.xml +++ b/script.moonlight/addon.xml @@ -1,5 +1,5 @@  - + @@ -16,4 +16,4 @@ gustavobenn@hotmail.com - + \ No newline at end of file diff --git a/script.moonlight/resources/__init__.py b/script.moonlight/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/script.moonlight/resources/moonlight.py b/script.moonlight/resources/moonlight.py new file mode 100644 index 0000000..0308a61 --- /dev/null +++ b/script.moonlight/resources/moonlight.py @@ -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 + \ No newline at end of file