Revert "First attempt at server select dialog"
This reverts commit 59040f3b3e
.
This commit is contained in:
parent
59040f3b3e
commit
5cdda0e334
6 changed files with 72 additions and 599 deletions
|
@ -11,8 +11,9 @@ from threading import Thread
|
|||
from xbmc import sleep
|
||||
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from .plexapi.base import PlexServer, Connection
|
||||
from . import utils, plex_tv, variables as v
|
||||
from . import utils
|
||||
from . import plex_tv
|
||||
from . import variables as v
|
||||
|
||||
###############################################################################
|
||||
LOG = getLogger('PLEX.plex_functions')
|
||||
|
@ -192,7 +193,7 @@ def discover_pms(token=None):
|
|||
"""
|
||||
LOG.info('Start discovery of Plex Media Servers')
|
||||
# Look first for local PMS in the LAN
|
||||
local_pms_list = plex_gdm()
|
||||
local_pms_list = _plex_gdm()
|
||||
LOG.debug('PMS found in the local LAN using Plex GDM: %s', local_pms_list)
|
||||
# Get PMS from plex.tv
|
||||
if token:
|
||||
|
@ -235,7 +236,7 @@ def _log_pms(pms_list):
|
|||
LOG.debug('Found the following PMS: %s', log_list)
|
||||
|
||||
|
||||
def plex_gdm():
|
||||
def _plex_gdm():
|
||||
"""
|
||||
PlexGDM - looks for PMS in the local LAN and returns a list of the PMS found
|
||||
"""
|
||||
|
@ -277,54 +278,44 @@ def plex_gdm():
|
|||
# Check if we had a positive HTTP response
|
||||
if '200 OK' not in response['data']:
|
||||
continue
|
||||
connection = Connection(local=True)
|
||||
# Local LAN IP from GDM
|
||||
connection.address = response['from'][0]
|
||||
pms = PlexServer()
|
||||
pms.presence = True
|
||||
pms.connections.append(connection)
|
||||
pms = {
|
||||
'ip': response['from'][0],
|
||||
'scheme': None,
|
||||
'local': True, # Since we found it using GDM
|
||||
'product': None,
|
||||
'baseURL': None,
|
||||
'name': None,
|
||||
'version': None,
|
||||
'token': None,
|
||||
'ownername': None,
|
||||
'device': None,
|
||||
'platform': None,
|
||||
'owned': None,
|
||||
'relay': None,
|
||||
'presence': True, # Since we're talking to the PMS
|
||||
'httpsRequired': None,
|
||||
}
|
||||
for line in response['data'].split('\n'):
|
||||
try:
|
||||
kind, info = line.split(':', 1)
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
kind, info = kind.strip(), info.strip()
|
||||
if kind == 'Name':
|
||||
pms.name = info
|
||||
elif kind == 'Resource-Identifier':
|
||||
pms.clientIdentifier = info
|
||||
elif kind == 'Content-Type':
|
||||
pms.product = info
|
||||
elif kind == 'Version':
|
||||
pms.productVersion = info
|
||||
elif kind == 'Updated-At':
|
||||
pms.lastSeenAt = int(info)
|
||||
elif kind == 'Host':
|
||||
# Example: "Host: <....sfe...>.plex.direct"
|
||||
connection.uri = info
|
||||
elif kind == 'Port':
|
||||
connection.port = int(info)
|
||||
# Assume https
|
||||
connection.protocol = 'https'
|
||||
if connection.uri != connection.address:
|
||||
# The PMS might return both local IP and plex.direct address
|
||||
alt_connection = deepcopy(connection)
|
||||
alt_connection.uri = 'https://%s:%s' % (connection.address,
|
||||
connection.port)
|
||||
pms.connections.append(alt_connection)
|
||||
connection.uri = 'https://%s:%s' % (connection.uri,
|
||||
connection.port)
|
||||
if 'Content-Type:' in line:
|
||||
pms['product'] = utils.try_decode(line.split(':')[1].strip())
|
||||
elif 'Host:' in line:
|
||||
pms['baseURL'] = line.split(':')[1].strip()
|
||||
elif 'Name:' in line:
|
||||
pms['name'] = utils.try_decode(line.split(':')[1].strip())
|
||||
elif 'Port:' in line:
|
||||
pms['port'] = line.split(':')[1].strip()
|
||||
elif 'Resource-Identifier:' in line:
|
||||
pms['machineIdentifier'] = line.split(':')[1].strip()
|
||||
elif 'Version:' in line:
|
||||
pms['version'] = line.split(':')[1].strip()
|
||||
pms_list.append(pms)
|
||||
LOG.debug('Found PMS in the LAN: %s: %s', pms, pms.connections)
|
||||
return pms_list
|
||||
|
||||
|
||||
def pms_from_plex_tv(token):
|
||||
def _pms_list_from_plex_tv(token):
|
||||
"""
|
||||
get Plex media Server List from plex.tv/pms/resources
|
||||
"""
|
||||
pms_list = []
|
||||
xml = DU().downloadUrl('https://plex.tv/api/resources',
|
||||
authenticate=False,
|
||||
parameters={'includeHttps': 1},
|
||||
|
@ -333,20 +324,49 @@ def pms_from_plex_tv(token):
|
|||
xml.attrib
|
||||
except AttributeError:
|
||||
LOG.error('Could not get list of PMS from plex.tv')
|
||||
return pms_list
|
||||
return []
|
||||
|
||||
from Queue import Queue
|
||||
queue = Queue()
|
||||
thread_queue = []
|
||||
|
||||
max_age_in_seconds = 2 * 60 * 60 * 24
|
||||
for device in xml.findall('Device'):
|
||||
if 'server' not in device.get('provides', ''):
|
||||
if 'server' not in device.get('provides'):
|
||||
# No PMS - skip
|
||||
continue
|
||||
if device.find('Connection') is None:
|
||||
# no valid connection - skip
|
||||
continue
|
||||
pms = PlexServer(xml=device)
|
||||
pms_list.append(pms)
|
||||
return pms_list
|
||||
|
||||
|
||||
|
||||
# check MyPlex data age - skip if >2 days
|
||||
info_age = time() - int(device.get('lastSeenAt'))
|
||||
if info_age > max_age_in_seconds:
|
||||
LOG.debug("Skip server %s not seen for 2 days", device.get('name'))
|
||||
continue
|
||||
pms = {
|
||||
'machineIdentifier': device.get('clientIdentifier'),
|
||||
'name': device.get('name'),
|
||||
'token': device.get('accessToken'),
|
||||
'ownername': device.get('sourceTitle'),
|
||||
'product': device.get('product'), # e.g. 'Plex Media Server'
|
||||
'version': device.get('productVersion'), # e.g. '1.11.2.4772-3e..'
|
||||
'device': device.get('device'), # e.g. 'PC' or 'Windows'
|
||||
'platform': device.get('platform'), # e.g. 'Windows', 'Android'
|
||||
'local': device.get('publicAddressMatches') == '1',
|
||||
'owned': device.get('owned') == '1',
|
||||
'relay': device.get('relay') == '1',
|
||||
'presence': device.get('presence') == '1',
|
||||
'httpsRequired': device.get('httpsRequired') == '1',
|
||||
'connections': []
|
||||
}
|
||||
# Try a local connection first, no matter what plex.tv tells us
|
||||
for connection in device.findall('Connection'):
|
||||
if connection.get('local') == '1':
|
||||
pms['connections'].append(connection)
|
||||
# Then try non-local
|
||||
for connection in device.findall('Connection'):
|
||||
if connection.get('local') != '1':
|
||||
pms['connections'].append(connection)
|
||||
# Spawn threads to ping each PMS simultaneously
|
||||
thread = Thread(target=_poke_pms, args=(pms, queue))
|
||||
thread_queue.append(thread)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# Dummy file to make this directory a package.
|
|
@ -1,126 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
"""
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
|
||||
from ..utils import cast, to_list
|
||||
|
||||
LOG = getLogger('PLEX.' + __name__)
|
||||
|
||||
|
||||
class Connection(object):
|
||||
def __init__(self, xml=None, **kwargs):
|
||||
self.protocol = None
|
||||
self.address = None
|
||||
self.port = None
|
||||
self.uri = None
|
||||
self.local = None
|
||||
if xml:
|
||||
self.load_from_xml(xml)
|
||||
# Set all remaining attributes set on Class instantiation
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def __unicode__(self):
|
||||
return '<Connection {self.uri}>'.format(self=self)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__unicode__().encode('utf-8')
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.uri == other.uri
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.uri != other.uri
|
||||
|
||||
def load_from_xml(self, xml):
|
||||
"""
|
||||
Throw in an etree xml-element to load PMS settings from it
|
||||
"""
|
||||
if xml.tag != 'Connection':
|
||||
raise RuntimeError('Did not receive Connection xml but %s'
|
||||
% xml.tag)
|
||||
self.protocol = cast(unicode, xml.get('protocol'))
|
||||
self.address = cast(unicode, xml.get('address'))
|
||||
self.port = cast(int, xml.get('port'))
|
||||
self.uri = cast(unicode, xml.get('uri'))
|
||||
self.local = cast(bool, xml.get('local'))
|
||||
|
||||
|
||||
class PlexServer(object):
|
||||
def __init__(self, xml=None, **kwargs):
|
||||
# Information from plex.tv
|
||||
self.name = None
|
||||
self.clientIdentifier = None
|
||||
self.provides = set()
|
||||
self.owned = None
|
||||
self.home = None
|
||||
self.httpsRequired = None
|
||||
self.synced = None
|
||||
self.relay = None
|
||||
self.publicAddressMatches = None
|
||||
self.presence = None
|
||||
self.accessToken = None
|
||||
|
||||
self.product = None # Usually "Plex Media Server"
|
||||
self.ownerId = None # User id of the owner of this PMS
|
||||
self.owner = None # User name of the (foreign!) owner
|
||||
self.productVersion = None
|
||||
self.platform = None
|
||||
self.platformVersion = None
|
||||
self.device = None
|
||||
self.createdAt = None
|
||||
self.lastSeenAt = None
|
||||
|
||||
# Connection info
|
||||
self.connections = []
|
||||
if xml:
|
||||
self.load_from_xml(xml)
|
||||
# Set all remaining attributes set on Class instantiation
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def load_from_xml(self, xml):
|
||||
"""
|
||||
Throw in an etree xml-element to load PMS settings from it
|
||||
"""
|
||||
if xml.tag != 'Device':
|
||||
raise RuntimeError('Did not receive Device xml but %s' % xml.tag)
|
||||
self.name = cast(unicode, xml.get('name'))
|
||||
self.clientIdentifier = cast(unicode, xml.get('clientIdentifier'))
|
||||
self.provides = set(to_list(cast(unicode, xml.get('provides'))))
|
||||
self.owned = cast(bool, xml.get('owned'))
|
||||
self.home = cast(bool, xml.get('home'))
|
||||
self.httpsRequired = cast(bool, xml.get('httpsRequired'))
|
||||
self.synced = cast(bool, xml.get('synced'))
|
||||
self.relay = cast(bool, xml.get('relay'))
|
||||
self.publicAddressMatches = cast(bool,
|
||||
xml.get('publicAddressMatches'))
|
||||
self.presence = cast(bool, xml.get('presence'))
|
||||
self.accessToken = cast(unicode, xml.get('accessToken'))
|
||||
self.product = cast(unicode, xml.get('product'))
|
||||
self.ownerId = cast(int, xml.get('ownerId'))
|
||||
self.owner = cast(unicode, xml.get('sourceTitle'))
|
||||
self.productVersion = cast(unicode, xml.get('productVersion'))
|
||||
self.platform = cast(unicode, xml.get('platform'))
|
||||
self.platformVersion = cast(unicode, xml.get('platformVersion'))
|
||||
self.device = cast(unicode, xml.get('device'))
|
||||
self.createdAt = cast(int, xml.get('createdAt'))
|
||||
self.lastSeenAt = cast(int, xml.get('lastSeenAt'))
|
||||
|
||||
for connection in xml.findall('Connection'):
|
||||
self.connections.append(Connection(xml=connection))
|
||||
|
||||
def __unicode__(self):
|
||||
return '<PlexServer {self.name}:{self.clientIdentifier}>'.format(self=self)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__unicode__().encode('utf-8')
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.clientIdentifier == other.clientIdentifier
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.clientIdentifier != other.clientIdentifier
|
|
@ -249,46 +249,6 @@ def ERROR(txt='', hide_tb=False, notify=False):
|
|||
return short
|
||||
|
||||
|
||||
def to_list(value, itemcast=None, delim=','):
|
||||
"""
|
||||
Returns a list of unicodes from the specified value [unicode].
|
||||
|
||||
Parameters:
|
||||
value [unicode]: (comma) delimited string to convert to list.
|
||||
itemcast (func): Function to cast each list item to (default unicode).
|
||||
delim (str): string delimiter (optional; default ',').
|
||||
"""
|
||||
value = value or ''
|
||||
itemcast = itemcast or unicode
|
||||
return [itemcast(item) for item in value.split(delim) if item != '']
|
||||
|
||||
|
||||
def cast(func, value):
|
||||
"""
|
||||
Cast the specified value to the specified type (returned by func).
|
||||
Currently supports int, float, bool, unicode (if str supplied), str
|
||||
Will return None if value=None
|
||||
|
||||
Parameters:
|
||||
func (func): Calback function to used cast to type
|
||||
value (any): value to be cast and returned.
|
||||
"""
|
||||
if value is None:
|
||||
return value
|
||||
if func == bool:
|
||||
return bool(int(value))
|
||||
elif func in (int, float):
|
||||
try:
|
||||
return func(value)
|
||||
except ValueError:
|
||||
return float('nan')
|
||||
elif func == unicode:
|
||||
return value.decode('utf-8')
|
||||
elif func == str:
|
||||
return value.encode('utf-8')
|
||||
return func(value)
|
||||
|
||||
|
||||
class AttributeDict(dict):
|
||||
"""
|
||||
Turns an etree xml response's xml.attrib into an object with attributes
|
||||
|
|
|
@ -1,224 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
:module: plexkodiconnect.userselect
|
||||
:synopsis: This module shows a dialog to let one choose between different Plex
|
||||
(home) users
|
||||
"""
|
||||
from __future__ import absolute_import, division, unicode_literals
|
||||
from logging import getLogger
|
||||
import xbmc
|
||||
import xbmcgui
|
||||
|
||||
from . import kodigui
|
||||
from .connection import plexapp
|
||||
from .. import backgroundthread, utils, plex_functions as PF, variables as v
|
||||
|
||||
LOG = getLogger('PLEX.' + __name__)
|
||||
|
||||
|
||||
class UserThumbTask(backgroundthread.Task):
|
||||
def setup(self, users, callback):
|
||||
self.users = users
|
||||
self.callback = callback
|
||||
return self
|
||||
|
||||
def run(self):
|
||||
for user in self.users:
|
||||
if self.isCanceled():
|
||||
return
|
||||
thumb, back = user.thumb, ''
|
||||
self.callback(user, thumb, back)
|
||||
|
||||
|
||||
class ServerListItem(kodigui.ManagedListItem):
|
||||
def init(self):
|
||||
self.dataSource.on('completed:reachability', self.onUpdate)
|
||||
self.dataSource.on('started:reachability', self.onUpdate)
|
||||
return self
|
||||
|
||||
def safeSetProperty(self, key, value):
|
||||
# For if we catch the item in the middle of being removed
|
||||
try:
|
||||
self.setProperty(key, value)
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def safeSetLabel(self, value):
|
||||
# For if we catch the item in the middle of being removed
|
||||
try:
|
||||
self.setLabel(value)
|
||||
return True
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def onUpdate(self, **kwargs):
|
||||
if not self.listItem: # ex. can happen on Kodi shutdown
|
||||
return
|
||||
|
||||
if not self.dataSource.isSupported or not self.dataSource.isReachable():
|
||||
if self.dataSource.pendingReachabilityRequests > 0:
|
||||
self.safeSetProperty('status', 'refreshing.gif')
|
||||
else:
|
||||
self.safeSetProperty('status', 'unreachable.png')
|
||||
else:
|
||||
self.safeSetProperty('status', self.dataSource.isSecure and 'secure.png' or '')
|
||||
|
||||
self.safeSetProperty('current', plexapp.SERVERMANAGER.selectedServer == self.dataSource and '1' or '')
|
||||
self.safeSetLabel(self.dataSource.name)
|
||||
|
||||
def onDestroy(self):
|
||||
self.dataSource.off('completed:reachability', self.onUpdate)
|
||||
self.dataSource.off('started:reachability', self.onUpdate)
|
||||
|
||||
|
||||
class ServerSelectWindow(kodigui.BaseWindow):
|
||||
xmlFile = 'script-plex-server_select.xml'
|
||||
path = v.ADDON_PATH
|
||||
theme = 'Main'
|
||||
res = '1080i'
|
||||
width = 1920
|
||||
height = 1080
|
||||
|
||||
USER_LIST_ID = 101
|
||||
PIN_ENTRY_GROUP_ID = 400
|
||||
HOME_BUTTON_ID = 500
|
||||
SERVER_LIST_ID = 260
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.tasks = None
|
||||
self.server = None
|
||||
self.aborted = False
|
||||
self.serverList = None
|
||||
kodigui.BaseWindow.__init__(self, *args, **kwargs)
|
||||
|
||||
def onFirstInit(self):
|
||||
self.serverList = kodigui.ManagedControlList(self,
|
||||
self.SERVER_LIST_ID,
|
||||
10)
|
||||
self.start()
|
||||
|
||||
def onAction(self, action):
|
||||
try:
|
||||
ID = action.getId()
|
||||
if 57 < ID < 68:
|
||||
if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)):
|
||||
item = self.userList.getSelectedItem()
|
||||
if not item.dataSource.isProtected:
|
||||
return
|
||||
self.setFocusId(self.PIN_ENTRY_GROUP_ID)
|
||||
self.pinEntryClicked(ID + 142)
|
||||
return
|
||||
elif 142 <= ID <= 149: # JumpSMS action
|
||||
if not xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)):
|
||||
item = self.userList.getSelectedItem()
|
||||
if not item.dataSource.isProtected:
|
||||
return
|
||||
self.setFocusId(self.PIN_ENTRY_GROUP_ID)
|
||||
self.pinEntryClicked(ID + 60)
|
||||
return
|
||||
elif ID in (xbmcgui.ACTION_NAV_BACK, xbmcgui.ACTION_BACKSPACE):
|
||||
if xbmc.getCondVisibility('ControlGroup({0}).HasFocus(0)'.format(self.PIN_ENTRY_GROUP_ID)):
|
||||
self.pinEntryClicked(211)
|
||||
return
|
||||
except:
|
||||
utils.ERROR()
|
||||
|
||||
kodigui.BaseWindow.onAction(self, action)
|
||||
|
||||
def onClick(self, controlID):
|
||||
if controlID == self.USER_LIST_ID:
|
||||
item = self.userList.getSelectedItem()
|
||||
if item.dataSource.isProtected:
|
||||
self.setFocusId(self.PIN_ENTRY_GROUP_ID)
|
||||
else:
|
||||
self.userSelected(item)
|
||||
elif 200 < controlID < 212:
|
||||
self.pinEntryClicked(controlID)
|
||||
elif controlID == self.HOME_BUTTON_ID:
|
||||
self.home_button_clicked()
|
||||
|
||||
def onFocus(self, controlID):
|
||||
if controlID == self.USER_LIST_ID:
|
||||
item = self.userList.getSelectedItem()
|
||||
item.setProperty('editing.pin', '')
|
||||
|
||||
def showServers(self, from_refresh=False, mouse=False):
|
||||
selection = None
|
||||
if from_refresh:
|
||||
mli = self.serverList.getSelectedItem()
|
||||
if mli:
|
||||
selection = mli.dataSource
|
||||
else:
|
||||
plexapp.refreshResources()
|
||||
|
||||
servers = sorted(
|
||||
plexapp.SERVERMANAGER.getServers(),
|
||||
key=lambda x: (x.owned and '0' or '1') + x.name.lower()
|
||||
)
|
||||
servers = PF.plex_gdm()
|
||||
|
||||
items = []
|
||||
for s in servers:
|
||||
item = ServerListItem(s.name,
|
||||
not s.owned and s.owner or '',
|
||||
data_source=s).init()
|
||||
item.onUpdate()
|
||||
item.setProperty(
|
||||
'current',
|
||||
plexapp.SERVERMANAGER.selectedServer == s and '1' or '')
|
||||
items.append(item)
|
||||
|
||||
if len(items) > 1:
|
||||
items[0].setProperty('first', '1')
|
||||
items[-1].setProperty('last', '1')
|
||||
elif items:
|
||||
items[0].setProperty('only', '1')
|
||||
|
||||
self.serverList.replaceItems(items)
|
||||
|
||||
self.getControl(800).setHeight((min(len(items), 9) * 100) + 80)
|
||||
|
||||
if selection:
|
||||
for mli in self.serverList:
|
||||
if mli.dataSource == selection:
|
||||
self.serverList.selectItem(mli.pos())
|
||||
if not from_refresh and items and not mouse:
|
||||
self.setFocusId(self.SERVER_LIST_ID)
|
||||
|
||||
def start(self):
|
||||
self.setProperty('busy', '1')
|
||||
self.showServers()
|
||||
self.setProperty('initialized', '1')
|
||||
self.setProperty('busy', '')
|
||||
|
||||
def home_button_clicked(self):
|
||||
"""
|
||||
Action taken if user clicked the home button
|
||||
"""
|
||||
self.server = None
|
||||
self.aborted = True
|
||||
self.doClose()
|
||||
|
||||
def finished(self):
|
||||
for task in self.tasks:
|
||||
task.cancel()
|
||||
|
||||
|
||||
def start():
|
||||
"""
|
||||
Hit this function to open a dialog to choose the Plex user
|
||||
|
||||
Returns
|
||||
=======
|
||||
tuple (server, aborted)
|
||||
server : PlexServer
|
||||
Or None if server switch failed or aborted by the server
|
||||
aborted : bool
|
||||
True if the server cancelled the dialog
|
||||
"""
|
||||
w = ServerSelectWindow.open()
|
||||
server, aborted = w.server, w.aborted
|
||||
del w
|
||||
return server, aborted
|
|
@ -1,156 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<window>
|
||||
<defaultcontrol>100</defaultcontrol>
|
||||
<coordinates>
|
||||
<system>1</system>
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
</coordinates>
|
||||
<backgroundcolor>0xff111111</backgroundcolor>
|
||||
<controls>
|
||||
<!-- Background -->
|
||||
<control type="group">
|
||||
<control type="image">
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<texture colordiffuse="80FFFFFF">script.plex/home/background-fallback.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<fadetime>1000</fadetime>
|
||||
<texture background="true">$INFO[Window.Property(background)]</texture>
|
||||
</control>
|
||||
</control>
|
||||
|
||||
<!-- Top bar -->
|
||||
<control type="group">
|
||||
<defaultcontrol always="true">201</defaultcontrol>
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>1920</width>
|
||||
<height>135</height>
|
||||
<control type="image">
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>1920</width>
|
||||
<height>135</height>
|
||||
<texture>plugin.video.plexkodiconnect/white-square.png</texture>
|
||||
<colordiffuse>19000000</colordiffuse>
|
||||
</control>
|
||||
|
||||
<control type="grouplist">
|
||||
<posx>20</posx>
|
||||
<posy>-5.5</posy>
|
||||
<width>1040</width>
|
||||
<height>170</height>
|
||||
<align>left</align>
|
||||
<itemgap>60</itemgap>
|
||||
<orientation>horizontal</orientation>
|
||||
<ondown>101</ondown>
|
||||
<usecontrolcoords>true</usecontrolcoords>
|
||||
<control type="group">
|
||||
<posx>40</posx>
|
||||
<posy>34.5</posy>
|
||||
<width>124</width>
|
||||
<height>66</height>
|
||||
<control type="button" id="500">
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>124</width>
|
||||
<height>66</height>
|
||||
<ondown>101</ondown>
|
||||
<onright>101</onright>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<texturefocus>-</texturefocus>
|
||||
<texturenofocus>-</texturenofocus>
|
||||
<label> </label>
|
||||
</control>
|
||||
<control type="image">
|
||||
<visible>!String.IsEmpty(Window.Property(dropdown))</visible>
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>124</width>
|
||||
<height>66</height>
|
||||
<texture colordiffuse="FFCC7B19" border="10">plugin.video.plexkodiconnect/white-square-rounded.png</texture>
|
||||
</control>
|
||||
<control type="group">
|
||||
<posx>27</posx>
|
||||
<posy>13</posy>
|
||||
<control type="image">
|
||||
<visible>!Control.HasFocus(500) + String.IsEmpty(Window.Property(dropdown))</visible>
|
||||
<posx>0</posx>
|
||||
<posy>0</posy>
|
||||
<width>90</width>
|
||||
<height>90</height>
|
||||
<texture>plugin.video.plexkodiconnect/home/type/home.png</texture>
|
||||
</control>
|
||||
<control type="image">
|
||||
<visible>Control.HasFocus(500) | !String.IsEmpty(Window.Property(dropdown))</visible>
|
||||
<posx>-40</posx>
|
||||
<posy>-40</posy>
|
||||
<width>170</width>
|
||||
<height>170</height>
|
||||
<texture>plugin.video.plexkodiconnect/home/type/home-selected.png</texture>
|
||||
</control>
|
||||
</control>
|
||||
</control>
|
||||
<control type="label">
|
||||
<posx>13</posx>
|
||||
<posy>50</posy>
|
||||
<width max="500">auto</width>
|
||||
<height>66</height>
|
||||
<font>font12</font>
|
||||
<align>left</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<label>[UPPERCASE]$ADDON[plugin.video.plexkodiconnect 33000][/UPPERCASE]</label>
|
||||
</control>
|
||||
</control>
|
||||
|
||||
<control type="label">
|
||||
<right>213</right>
|
||||
<posy>35</posy>
|
||||
<width>200</width>
|
||||
<height>65</height>
|
||||
<font>font12</font>
|
||||
<align>right</align>
|
||||
<aligny>center</aligny>
|
||||
<textcolor>FFFFFFFF</textcolor>
|
||||
<label>$INFO[System.Time]</label>
|
||||
</control>
|
||||
<control type="image">
|
||||
<posx>153r</posx>
|
||||
<posy>54</posy>
|
||||
<width>93</width>
|
||||
<height>30</height>
|
||||
<texture>plugin.video.plexkodiconnect/home/plex.png</texture>
|
||||
</control>
|
||||
</control>
|
||||
|
||||
|
||||
<control type="group">
|
||||
<visible>!String.IsEmpty(Window.Property(busy))</visible>
|
||||
<control type="image">
|
||||
<posx>840</posx>
|
||||
<posy>465</posy>
|
||||
<width>240</width>
|
||||
<height>150</height>
|
||||
<texture>script.plex/busy-back.png</texture>
|
||||
<colordiffuse>A0FFFFFF</colordiffuse>
|
||||
</control>
|
||||
<control type="image">
|
||||
<posx>915</posx>
|
||||
<posy>521</posy>
|
||||
<width>90</width>
|
||||
<height>38</height>
|
||||
<texture diffuse="script.plex/busy-diffuse.png">script.plex/busy.gif</texture>
|
||||
</control>
|
||||
</control>
|
||||
</controls>
|
||||
</window>
|
Loading…
Reference in a new issue