From e2776261c38865edb95453f33c8c7a0e2c31869e Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 22 Oct 2016 17:15:10 +0200 Subject: [PATCH] Initial Kodi context menu commit --- addon.xml | 7 + contextmenu.py | 187 ++---------- resources/language/English/strings.xml | 10 +- resources/language/German/strings.xml | 19 ++ resources/lib/PlexFunctions.py | 19 ++ resources/lib/context_entry.py | 194 ++++++++++++ resources/lib/dialog/__init__.py | 1 - resources/lib/dialog/loginconnect.py | 76 ----- resources/lib/dialogs/__init__.py | 6 + resources/lib/dialogs/context.py | 93 ++++++ resources/lib/dialogs/loginconnect.py | 136 +++++++++ resources/lib/dialogs/loginmanual.py | 145 +++++++++ resources/lib/dialogs/serverconnect.py | 145 +++++++++ resources/lib/dialogs/servermanual.py | 145 +++++++++ resources/lib/dialogs/usersconnect.py | 104 +++++++ resources/lib/kodimonitor.py | 24 +- resources/lib/utils.py | 23 ++ resources/settings.xml | 3 +- .../script-emby-connect-login-manual.xml | 145 +++++++++ .../1080i/script-emby-connect-login.xml | 84 ++++-- .../script-emby-connect-server-manual.xml | 154 ++++++++++ .../1080i/script-emby-connect-server.xml | 280 ++++++++++++++++++ .../1080i/script-emby-connect-users.xml | 198 +++++++++++++ .../default/1080i/script-emby-context.xml | 109 +++++++ .../script-emby-kodi-UserPreferences.xml | 129 -------- resources/skins/default/media/emby-icon.png | Bin 0 -> 55512 bytes .../{separator.png => emby-separator.png} | Bin .../skins/default/media/fading_circle.png | Bin 0 -> 1906 bytes resources/skins/default/media/network.png | Bin 0 -> 727 bytes resources/skins/default/media/user_image.png | Bin 0 -> 662 bytes .../skins/default/media/userflyoutdefault.png | Bin 0 -> 1338 bytes .../default/media/userflyoutdefault2.png | Bin 0 -> 1700 bytes resources/skins/default/media/wifi.png | Bin 0 -> 1095 bytes service.py | 4 +- 34 files changed, 2029 insertions(+), 411 deletions(-) create mode 100644 resources/lib/context_entry.py delete mode 100644 resources/lib/dialog/__init__.py delete mode 100644 resources/lib/dialog/loginconnect.py create mode 100644 resources/lib/dialogs/__init__.py create mode 100644 resources/lib/dialogs/context.py create mode 100644 resources/lib/dialogs/loginconnect.py create mode 100644 resources/lib/dialogs/loginmanual.py create mode 100644 resources/lib/dialogs/serverconnect.py create mode 100644 resources/lib/dialogs/servermanual.py create mode 100644 resources/lib/dialogs/usersconnect.py create mode 100644 resources/skins/default/1080i/script-emby-connect-login-manual.xml create mode 100644 resources/skins/default/1080i/script-emby-connect-server-manual.xml create mode 100644 resources/skins/default/1080i/script-emby-connect-server.xml create mode 100644 resources/skins/default/1080i/script-emby-connect-users.xml create mode 100644 resources/skins/default/1080i/script-emby-context.xml delete mode 100644 resources/skins/default/1080i/script-emby-kodi-UserPreferences.xml create mode 100644 resources/skins/default/media/emby-icon.png rename resources/skins/default/media/{separator.png => emby-separator.png} (100%) create mode 100644 resources/skins/default/media/fading_circle.png create mode 100644 resources/skins/default/media/network.png create mode 100644 resources/skins/default/media/user_image.png create mode 100644 resources/skins/default/media/userflyoutdefault.png create mode 100644 resources/skins/default/media/userflyoutdefault2.png create mode 100644 resources/skins/default/media/wifi.png diff --git a/addon.xml b/addon.xml index 8d848b4a..ab62d8ca 100644 --- a/addon.xml +++ b/addon.xml @@ -15,6 +15,13 @@ + + + + Settings for the Plex Server + [!IsEmpty(ListItem.DBID) + !StringCompare(ListItem.DBID,-1) | !IsEmpty(ListItem.Property(plexid))] + !IsEmpty(Window(10000).Property(plex_context)) + + all en diff --git a/contextmenu.py b/contextmenu.py index d4549829..df6de3fd 100644 --- a/contextmenu.py +++ b/contextmenu.py @@ -1,175 +1,52 @@ # -*- coding: utf-8 -*- -################################################################################################# +############################################################################### import logging import os import sys -import urlparse import xbmc import xbmcaddon -import xbmcgui -################################################################################################# +############################################################################### -_addon = xbmcaddon.Addon(id='plugin.video.emby') -_addon_path = _addon.getAddonInfo('path').decode('utf-8') -_base_resource = xbmc.translatePath(os.path.join(_addon_path, 'resources', 'lib')).decode('utf-8') +_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect') +try: + _addon_path = _addon.getAddonInfo('path').decode('utf-8') +except TypeError: + _addon_path = _addon.getAddonInfo('path').decode() +try: + _base_resource = xbmc.translatePath(os.path.join( + _addon_path, + 'resources', + 'lib')).decode('utf-8') +except TypeError: + _base_resource = xbmc.translatePath(os.path.join( + _addon_path, + 'resources', + 'lib')).decode() sys.path.append(_base_resource) -################################################################################################# - -import api -import artwork -import downloadutils -import librarysync -import read_embyserver as embyserver -import embydb_functions as embydb -import kodidb_functions as kodidb -import musicutils as musicutils -from utils import settings, language as lang, kodiSQL - -################################################################################################# +############################################################################### import loghandler +from context_entry import ContextMenu + +############################################################################### loghandler.config() -log = logging.getLogger("EMBY.contextmenu") +log = logging.getLogger("PLEX.contextmenu") -################################################################################################# +############################################################################### -# Kodi contextmenu item to configure the emby settings -if __name__ == '__main__': +if __name__ == "__main__": - kodiId = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8') - itemType = xbmc.getInfoLabel('ListItem.DBTYPE').decode('utf-8') - itemId = "" - - if not itemType: - - if xbmc.getCondVisibility("Container.Content(albums)"): - itemType = "album" - elif xbmc.getCondVisibility("Container.Content(artists)"): - itemType = "artist" - elif xbmc.getCondVisibility("Container.Content(songs)"): - itemType = "song" - elif xbmc.getCondVisibility("Container.Content(pictures)"): - itemType = "picture" - else: - log.info("ItemType is unknown.") - - if (not kodiId or kodiId == "-1") and xbmc.getInfoLabel("ListItem.Property(embyid)"): - itemId = xbmc.getInfoLabel("ListItem.Property(embyid)") - - elif kodiId and itemType: - embyconn = kodiSQL('emby') - embycursor = embyconn.cursor() - emby_db = embydb.Embydb_Functions(embycursor) - item = emby_db.getItem_byKodiId(kodiId, itemType) - embycursor.close() - try: - itemId = item[0] - except TypeError: - pass - - - log.info("Found ItemId: %s ItemType: %s" % (itemId, itemType)) - if itemId: - - dialog = xbmcgui.Dialog() - - emby = embyserver.Read_EmbyServer() - item = emby.getItem(itemId) - API = api.API(item) - userdata = API.getUserData() - likes = userdata['Likes'] - favourite = userdata['Favorite'] - - options = [] - - if favourite: - # Remove from emby favourites - options.append(lang(30406)) - else: - # Add to emby favourites - options.append(lang(30405)) - - if itemType == "song": - # Set custom song rating - options.append(lang(30407)) - - # Refresh item - options.append(lang(30410)) - # Delete item - options.append(lang(30409)) - # Addon settings - options.append(lang(30408)) - - # Display select dialog and process results - resp = xbmcgui.Dialog().select(lang(30401), options) - if resp > -1: - selected = options[resp] - - if selected == lang(30410): - # Refresh item - emby.refreshItem(itemId) - elif selected == lang(30405): - # Add favourite - emby.updateUserRating(itemId, favourite=True) - elif selected == lang(30406): - # Delete favourite - emby.updateUserRating(itemId, favourite=False) - elif selected == lang(30407): - # Update song rating - kodiconn = kodiSQL('music') - kodicursor = kodiconn.cursor() - query = "SELECT rating FROM song WHERE idSong = ?" - kodicursor.execute(query, (kodiId,)) - try: - value = kodicursor.fetchone()[0] - current_value = int(round(float(value),0)) - except TypeError: - pass - else: - new_value = dialog.numeric(0, lang(30411), str(current_value)) - if new_value > -1: - - new_value = int(new_value) - if new_value > 5: - new_value = 5 - - if settings('enableUpdateSongRating') == "true": - musicutils.updateRatingToFile(new_value, API.getFilePath()) - - query = "UPDATE song SET rating = ? WHERE idSong = ?" - kodicursor.execute(query, (new_value, kodiId,)) - kodiconn.commit() - - '''if settings('enableExportSongRating') == "true": - like, favourite, deletelike = musicutils.getEmbyRatingFromKodiRating(new_value) - emby.updateUserRating(itemId, like, favourite, deletelike)''' - finally: - kodicursor.close() - - elif selected == lang(30408): - # Open addon settings - xbmc.executebuiltin("Addon.OpenSettings(plugin.video.emby)") - - elif selected == lang(30409): - # delete item from the server - delete = True - if settings('skipContextMenu') != "true": - resp = dialog.yesno( - heading=lang(29999), - line1=lang(33041)) - if not resp: - log.info("User skipped deletion for: %s." % itemId) - delete = False - - if delete: - log.info("Deleting request: %s" % itemId) - emby.deleteItem(itemId) - - xbmc.sleep(500) - xbmc.executebuiltin('Container.Refresh') \ No newline at end of file + try: + # Start the context menu + ContextMenu() + except Exception as error: + log.exception(error) + import traceback + log.exception("Traceback:\n%s" % traceback.format_exc()) + raise diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 913a3512..4a35817e 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -1,6 +1,7 @@  + PlexKodiConnect Server Address (IP) Preferred playback method Log level @@ -260,7 +261,11 @@ Remove from Plex favorites Set custom song rating Plex addon settings - Delete item from the server + Delete item from server + Refresh this item + Set custom song rating (0-5) + Force transcode + Enable Plex context menu in Kodi Verify Host SSL Certificate (more secure) @@ -283,7 +288,7 @@ [COLOR yellow]Enter network credentials[/COLOR] Enable Plex Trailers (Plexpass is needed) Ask to play trailers - Skip Emby delete confirmation for the context menu (use at your own risk) + Skip Plex delete confirmation for the context menu (use at your own risk) Jump back on resume (in seconds) Force transcode H265 Music metadata options (not compatible with direct stream) @@ -344,6 +349,7 @@ Comparing: Failed to generate a new device Id. See your logs for more information. Kodi will now restart to apply the changes. + Delete file(s) from Plex Server? This will also delete the file(s) from disk! - Number of trailers to play before a movie diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index 789debcf..6263abb0 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -1,5 +1,6 @@ + PlexKodiConnect IP-Adresse des Servers Automatisches Öffnen von Ordnern mit einem Eintrag Gewünschte Wiedergabe-Methode @@ -21,6 +22,7 @@ Netzwerk Credentials eingeben PlexKodiConnect Start Verzögerung (in Sekunden) Extras ignorieren, wenn Nächste Episode gespielt wird + Sicherheitsabfrage bei Löschen des Plex Elements deaktivieren Bei Wiederaufnahme zurückspulen (in Sekunden) [COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR] @@ -282,7 +284,24 @@ Musikstücke Kanäle + + Plex Optionen + Clear like for this item + Like this item + Dislike this item + Add to Plex favorites + Remove from Plex favorites + Set custom song rating + Plex Addon Optionen + Element in Plex Server löschen + Element neu laden + Set custom song rating (0-5) + Transkodieren erzwingen + Plex Kontextmenu in Kodi aktivieren + + Kodi wird jetzt neu gestartet um die Änderungen anzuwenden. + Eintrag vom Plex Server löschen? Dies wird die Dateien auch endgültig von der Festplatte löschen! - Anzahl abzuspielender Trailer vor einem Film diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index ba42e254..1d008e54 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -526,3 +526,22 @@ def scrobble(ratingKey, state): return downloadutils.DownloadUtils().downloadUrl(url) log.info("Toggled watched state for Plex item %s" % ratingKey) + + +def delete_item_from_pms(plexid): + """ + Deletes the item plexid from the Plex Media Server (and the harddrive!). + Do make sure that the currently logged in user has the credentials + + Returns True if successful, False otherwise + """ + xml = downloadutils.DownloadUtils().downloadUrl( + '{server}/library/metadata/%s' % plexid, + action_type="DELETE") + try: + xml.attrib + except AttributeError: + log.error('Could not delete Plex id %s' % plexid) + return False + log.info(xml.dump) + return True diff --git a/resources/lib/context_entry.py b/resources/lib/context_entry.py new file mode 100644 index 00000000..65c386ee --- /dev/null +++ b/resources/lib/context_entry.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- + +############################################################################### + +import logging + +import xbmc +import xbmcaddon + +import PlexAPI +from PlexFunctions import GetPlexMetadata, delete_item_from_pms +import embydb_functions as embydb +from utils import settings, dialog, language as lang, kodiSQL +from dialogs import context + +############################################################################### + +log = logging.getLogger("PLEX."+__name__) +addonName = 'PlexKodiConnect' + +OPTIONS = { + 'Refresh': lang(30410), + 'Delete': lang(30409), + 'Addon': lang(30408), + # 'AddFav': lang(30405), + # 'RemoveFav': lang(30406), + # 'RateSong': lang(30407), + 'Transcode': lang(30412) +} + +############################################################################### + + +class ContextMenu(object): + + _selected_option = None + + def __init__(self): + self.kodi_id = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8') + self.item_type = self._get_item_type() + self.item_id = self._get_item_id(self.kodi_id, self.item_type) + + log.info("Found item_id: %s item_type: %s" + % (self.item_id, self.item_type)) + + if not self.item_id: + return + + self.item = GetPlexMetadata(self.item_id) + self.api = PlexAPI.API(self.item) + + if self._select_menu(): + self._action_menu() + + if self._selected_option in (OPTIONS['Delete'], + OPTIONS['Refresh']): + log.info("refreshing container") + xbmc.sleep(500) + xbmc.executebuiltin('Container.Refresh') + + @classmethod + def _get_item_type(cls): + item_type = xbmc.getInfoLabel('ListItem.DBTYPE').decode('utf-8') + + if not item_type: + if xbmc.getCondVisibility('Container.Content(albums)'): + item_type = "album" + elif xbmc.getCondVisibility('Container.Content(artists)'): + item_type = "artist" + elif xbmc.getCondVisibility('Container.Content(songs)'): + item_type = "song" + elif xbmc.getCondVisibility('Container.Content(pictures)'): + item_type = "picture" + else: + log.info("item_type is unknown") + + return item_type + + @classmethod + def _get_item_id(cls, kodi_id, item_type): + item_id = xbmc.getInfoLabel('ListItem.Property(plexid)') + if not item_id and kodi_id and item_type: + with embydb.GetEmbyDB() as emby_db: + item = emby_db.getItem_byKodiId(kodi_id, item_type) + try: + item_id = item[0] + except TypeError: + log.error('Could not get the Plex id for context menu') + return item_id + + def _select_menu(self): + # Display select dialog + options = [] + + if self.item_type in ("movie", "episode", "song"): + options.append(OPTIONS['Transcode']) + + # userdata = self.api.getUserData() + # if userdata['Favorite']: + # # Remove from emby favourites + # options.append(OPTIONS['RemoveFav']) + # else: + # # Add to emby favourites + # options.append(OPTIONS['AddFav']) + + # if self.item_type == "song": + # # Set custom song rating + # options.append(OPTIONS['RateSong']) + + # Refresh item + options.append(OPTIONS['Refresh']) + # Delete item, only if the Plex Home main user is logged in + if settings('plex_restricteduser') != 'true': + options.append(OPTIONS['Delete']) + # Addon settings + options.append(OPTIONS['Addon']) + + addon = xbmcaddon.Addon('plugin.video.plexkodiconnect') + context_menu = context.ContextMenu("script-emby-context.xml", + addon.getAddonInfo('path'), + "default", "1080i") + context_menu.set_options(options) + context_menu.doModal() + + if context_menu.is_selected(): + self._selected_option = context_menu.get_selected() + + return self._selected_option + + def _action_menu(self): + + selected = self._selected_option + + if selected == OPTIONS['Transcode']: + pass + + elif selected == OPTIONS['Refresh']: + self.emby.refreshItem(self.item_id) + + # elif selected == OPTIONS['AddFav']: + # self.emby.updateUserRating(self.item_id, favourite=True) + + # elif selected == OPTIONS['RemoveFav']: + # self.emby.updateUserRating(self.item_id, favourite=False) + + # elif selected == OPTIONS['RateSong']: + # self._rate_song() + + elif selected == OPTIONS['Addon']: + xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') + + elif selected == OPTIONS['Delete']: + self._delete_item() + + def _rate_song(self): + + conn = kodiSQL('music') + cursor = conn.cursor() + query = "SELECT rating FROM song WHERE idSong = ?" + cursor.execute(query, (self.kodi_id,)) + try: + value = cursor.fetchone()[0] + current_value = int(round(float(value), 0)) + except TypeError: + pass + else: + new_value = dialog("numeric", 0, lang(30411), str(current_value)) + if new_value > -1: + + new_value = int(new_value) + if new_value > 5: + new_value = 5 + + if settings('enableUpdateSongRating') == "true": + musicutils.updateRatingToFile(new_value, self.api.get_file_path()) + + query = "UPDATE song SET rating = ? WHERE idSong = ?" + cursor.execute(query, (new_value, self.kodi_id,)) + conn.commit() + finally: + cursor.close() + + def _delete_item(self): + + delete = True + if settings('skipContextMenu') != "true": + + if not dialog(type_="yesno", heading=addonName, line1=lang(33041)): + log.info("User skipped deletion for: %s", self.item_id) + delete = False + + if delete: + log.info("Deleting Plex item with id %s", self.item_id) + delete_item_from_pms(self.item_id) diff --git a/resources/lib/dialog/__init__.py b/resources/lib/dialog/__init__.py deleted file mode 100644 index b93054b3..00000000 --- a/resources/lib/dialog/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Dummy file to make this directory a package. diff --git a/resources/lib/dialog/loginconnect.py b/resources/lib/dialog/loginconnect.py deleted file mode 100644 index f12eab0f..00000000 --- a/resources/lib/dialog/loginconnect.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################## - -import os - -import xbmcgui -import xbmcaddon - -################################################################################################## - -addon = xbmcaddon.Addon('plugin.video.plexkodiconnect') - -ACTION_BACK = 92 -SIGN_IN = 200 -REMIND_LATER = 201 - - -class LoginConnect(xbmcgui.WindowXMLDialog): - - - def __init__(self, *args, **kwargs): - - xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) - - def __add_editcontrol(self, x, y, height, width, password=0): - - media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') - control = xbmcgui.ControlEdit(0,0,0,0, - label="User", - font="font10", - textColor="ff464646", - focusTexture=os.path.join(media, "button-focus.png"), - noFocusTexture=os.path.join(media, "button-focus.png"), - isPassword=password) - - control.setPosition(x,y) - control.setHeight(height) - control.setWidth(width) - - self.addControl(control) - return control - - def onInit(self): - - self.user_field = self.__add_editcontrol(685,385,40,500) - self.setFocus(self.user_field) - self.password_field = self.__add_editcontrol(685,470,40,500, password=1) - self.signin_button = self.getControl(SIGN_IN) - self.remind_button = self.getControl(REMIND_LATER) - - self.user_field.controlUp(self.remind_button) - self.user_field.controlDown(self.password_field) - self.password_field.controlUp(self.user_field) - self.password_field.controlDown(self.signin_button) - self.signin_button.controlUp(self.password_field) - self.remind_button.controlDown(self.user_field) - - def onClick(self, control): - - if control == SIGN_IN: - # Sign in to emby connect - self.user = self.user_field.getText() - __password = self.password_field.getText() - - ### REVIEW ONCE CONNECT MODULE IS MADE - self.close() - - elif control == REMIND_LATER: - # Remind me later - self.close() - - def onAction(self, action): - - if action == ACTION_BACK: - self.close() \ No newline at end of file diff --git a/resources/lib/dialogs/__init__.py b/resources/lib/dialogs/__init__.py new file mode 100644 index 00000000..92c6f42a --- /dev/null +++ b/resources/lib/dialogs/__init__.py @@ -0,0 +1,6 @@ +# Dummy file to make this directory a package. +# from serverconnect import ServerConnect +# from usersconnect import UsersConnect +# from loginconnect import LoginConnect +# from loginmanual import LoginManual +# from servermanual import ServerManual diff --git a/resources/lib/dialogs/context.py b/resources/lib/dialogs/context.py new file mode 100644 index 00000000..4ee9f038 --- /dev/null +++ b/resources/lib/dialogs/context.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +############################################################################### + +import logging +import os + +import xbmcgui +import xbmcaddon + +from utils import window + +############################################################################### + +log = logging.getLogger("PLEX."+__name__) +addon = xbmcaddon.Addon('plugin.video.plexkodiconnect') + +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +ACTION_SELECT_ITEM = 7 +ACTION_MOUSE_LEFT_CLICK = 100 +LIST = 155 +USER_IMAGE = 150 + +############################################################################### + + +class ContextMenu(xbmcgui.WindowXMLDialog): + + _options = [] + selected_option = None + + + def __init__(self, *args, **kwargs): + + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def set_options(self, options=[]): + self._options = options + + def is_selected(self): + return True if self.selected_option else False + + def get_selected(self): + return self.selected_option + + def onInit(self): + + if window('PlexUserImage'): + self.getControl(USER_IMAGE).setImage(window('PlexUserImage')) + + height = 479 + (len(self._options) * 55) + log.info("options: %s", self._options) + self.list_ = self.getControl(LIST) + + for option in self._options: + self.list_.addItem(self._add_listitem(option)) + + self.background = self._add_editcontrol(730, height, 30, 450) + self.setFocus(self.list_) + + def onAction(self, action): + + if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU): + self.close() + + if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK): + + if self.getFocusId() == LIST: + option = self.list_.getSelectedItem() + self.selected_option = option.getLabel() + log.info('option selected: %s', self.selected_option) + + self.close() + + def _add_editcontrol(self, x, y, height, width, password=0): + + media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlImage(0, 0, 0, 0, + filename=os.path.join(media, "white.png"), + aspectRatio=0, + colorDiffuse="ff111111") + control.setPosition(x, y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + + @classmethod + def _add_listitem(cls, label): + return xbmcgui.ListItem(label) diff --git a/resources/lib/dialogs/loginconnect.py b/resources/lib/dialogs/loginconnect.py new file mode 100644 index 00000000..db7c39cc --- /dev/null +++ b/resources/lib/dialogs/loginconnect.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import os + +import xbmcgui +import xbmcaddon + +from utils import language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) +addon = xbmcaddon.Addon('plugin.video.emby') + +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +SIGN_IN = 200 +CANCEL = 201 +ERROR_TOGGLE = 202 +ERROR_MSG = 203 +ERROR = { + 'Invalid': 1, + 'Empty': 2 +} + +################################################################################################## + + +class LoginConnect(xbmcgui.WindowXMLDialog): + + _user = None + error = None + + + def __init__(self, *args, **kwargs): + + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def set_connect_manager(self, connect_manager): + self.connect_manager = connect_manager + + def is_logged_in(self): + return True if self._user else False + + def get_user(self): + return self._user + + + def onInit(self): + + self.user_field = self._add_editcontrol(725, 385, 40, 500) + self.setFocus(self.user_field) + self.password_field = self._add_editcontrol(725, 470, 40, 500, password=1) + self.signin_button = self.getControl(SIGN_IN) + self.remind_button = self.getControl(CANCEL) + self.error_toggle = self.getControl(ERROR_TOGGLE) + self.error_msg = self.getControl(ERROR_MSG) + + self.user_field.controlUp(self.remind_button) + self.user_field.controlDown(self.password_field) + self.password_field.controlUp(self.user_field) + self.password_field.controlDown(self.signin_button) + self.signin_button.controlUp(self.password_field) + self.remind_button.controlDown(self.user_field) + + def onClick(self, control): + + if control == SIGN_IN: + # Sign in to emby connect + self._disable_error() + + user = self.user_field.getText() + password = self.password_field.getText() + + if not user or not password: + # Display error + self._error(ERROR['Empty'], lang(30608)) + log.error("Username or password cannot be null") + + elif self._login(user, password): + self.close() + + elif control == CANCEL: + # Remind me later + self.close() + + def onAction(self, action): + + if (self.error == ERROR['Empty'] + and self.user_field.getText() and self.password_field.getText()): + self._disable_error() + + if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU): + self.close() + + def _add_editcontrol(self, x, y, height, width, password=0): + + media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlEdit(0, 0, 0, 0, + label="User", + font="font10", + textColor="ff525252", + focusTexture=os.path.join(media, "button-focus.png"), + noFocusTexture=os.path.join(media, "button-focus.png"), + isPassword=password) + control.setPosition(x, y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + + def _login(self, username, password): + + result = self.connect_manager.loginToConnect(username, password) + if result is False: + self._error(ERROR['Invalid'], lang(33009)) + return False + else: + self._user = result + return True + + def _error(self, state, message): + + self.error = state + self.error_msg.setLabel(message) + self.error_toggle.setVisibleCondition('True') + + def _disable_error(self): + + self.error = None + self.error_toggle.setVisibleCondition('False') diff --git a/resources/lib/dialogs/loginmanual.py b/resources/lib/dialogs/loginmanual.py new file mode 100644 index 00000000..7db1adcf --- /dev/null +++ b/resources/lib/dialogs/loginmanual.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import os + +import xbmcgui +import xbmcaddon + +import read_embyserver as embyserver +from utils import language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) +addon = xbmcaddon.Addon('plugin.video.emby') + +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +SIGN_IN = 200 +CANCEL = 201 +ERROR_TOGGLE = 202 +ERROR_MSG = 203 +ERROR = { + 'Invalid': 1, + 'Empty': 2 +} + +################################################################################################## + + +class LoginManual(xbmcgui.WindowXMLDialog): + + _user = None + error = None + username = None + + + def __init__(self, *args, **kwargs): + + self.emby = embyserver.Read_EmbyServer() + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def is_logged_in(self): + return True if self._user else False + + def set_server(self, server): + self.server = server + + def set_user(self, user): + self.username = user or {} + + def get_user(self): + return self._user + + def onInit(self): + + self.signin_button = self.getControl(SIGN_IN) + self.cancel_button = self.getControl(CANCEL) + self.error_toggle = self.getControl(ERROR_TOGGLE) + self.error_msg = self.getControl(ERROR_MSG) + self.user_field = self._add_editcontrol(725, 400, 40, 500) + self.password_field = self._add_editcontrol(725, 475, 40, 500, password=1) + + if self.username: + self.user_field.setText(self.username) + self.setFocus(self.password_field) + else: + self.setFocus(self.user_field) + + self.user_field.controlUp(self.cancel_button) + self.user_field.controlDown(self.password_field) + self.password_field.controlUp(self.user_field) + self.password_field.controlDown(self.signin_button) + self.signin_button.controlUp(self.password_field) + self.cancel_button.controlDown(self.user_field) + + def onClick(self, control): + + if control == SIGN_IN: + # Sign in to emby connect + self._disable_error() + + user = self.user_field.getText() + password = self.password_field.getText() + + if not user: + # Display error + self._error(ERROR['Empty'], lang(30613)) + log.error("Username cannot be null") + + elif self._login(user, password): + self.close() + + elif control == CANCEL: + # Remind me later + self.close() + + def onAction(self, action): + + if self.error == ERROR['Empty'] and self.user_field.getText(): + self._disable_error() + + if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU): + self.close() + + def _add_editcontrol(self, x, y, height, width, password=0): + + media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlEdit(0, 0, 0, 0, + label="User", + font="font10", + textColor="ff525252", + focusTexture=os.path.join(media, "button-focus.png"), + noFocusTexture=os.path.join(media, "button-focus.png"), + isPassword=password) + control.setPosition(x, y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + + def _login(self, username, password): + + result = self.emby.loginUser(self.server, username, password) + if not result: + self._error(ERROR['Invalid'], lang(33009)) + return False + else: + self._user = result + return True + + def _error(self, state, message): + + self.error = state + self.error_msg.setLabel(message) + self.error_toggle.setVisibleCondition('True') + + def _disable_error(self): + + self.error = None + self.error_toggle.setVisibleCondition('False') diff --git a/resources/lib/dialogs/serverconnect.py b/resources/lib/dialogs/serverconnect.py new file mode 100644 index 00000000..541ca6f9 --- /dev/null +++ b/resources/lib/dialogs/serverconnect.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import xbmc +import xbmcgui + +import connect.connectionmanager as connectionmanager +from utils import language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +CONN_STATE = connectionmanager.ConnectionState +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +ACTION_SELECT_ITEM = 7 +ACTION_MOUSE_LEFT_CLICK = 100 +USER_IMAGE = 150 +USER_NAME = 151 +LIST = 155 +CANCEL = 201 +MESSAGE_BOX = 202 +MESSAGE = 203 +BUSY = 204 +EMBY_CONNECT = 205 +MANUAL_SERVER = 206 + +################################################################################################## + + +class ServerConnect(xbmcgui.WindowXMLDialog): + + username = "" + user_image = None + servers = [] + + _selected_server = None + _connect_login = False + _manual_server = False + + + def __init__(self, *args, **kwargs): + + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def set_args(self, **kwargs): + # connect_manager, username, user_image, servers, emby_connect + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def is_server_selected(self): + return True if self._selected_server else False + + def get_server(self): + return self._selected_server + + def is_connect_login(self): + return self._connect_login + + def is_manual_server(self): + return self._manual_server + + + def onInit(self): + + self.message = self.getControl(MESSAGE) + self.message_box = self.getControl(MESSAGE_BOX) + self.busy = self.getControl(BUSY) + self.list_ = self.getControl(LIST) + + for server in self.servers: + server_type = "wifi" if server.get('ExchangeToken') else "network" + self.list_.addItem(self._add_listitem(server['Name'], server['Id'], server_type)) + + self.getControl(USER_NAME).setLabel("%s %s" % (lang(33000), self.username.decode('utf-8'))) + + if self.user_image is not None: + self.getControl(USER_IMAGE).setImage(self.user_image) + + if not self.emby_connect: # Change connect user + self.getControl(EMBY_CONNECT).setLabel("[UPPERCASE][B]"+lang(30618)+"[/B][/UPPERCASE]") + + if self.servers: + self.setFocus(self.list_) + + @classmethod + def _add_listitem(cls, label, server_id, server_type): + + item = xbmcgui.ListItem(label) + item.setProperty('id', server_id) + item.setProperty('server_type', server_type) + + return item + + def onAction(self, action): + + if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR): + self.close() + + if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK): + + if self.getFocusId() == LIST: + server = self.list_.getSelectedItem() + selected_id = server.getProperty('id') + log.info('Server Id selected: %s', selected_id) + + if self._connect_server(selected_id): + self.message_box.setVisibleCondition('False') + self.close() + + def onClick(self, control): + + if control == EMBY_CONNECT: + self.connect_manager.clearData() + self._connect_login = True + self.close() + + elif control == MANUAL_SERVER: + self._manual_server = True + self.close() + + elif control == CANCEL: + self.close() + + def _connect_server(self, server_id): + + server = self.connect_manager.getServerInfo(server_id) + self.message.setLabel("%s %s..." % (lang(30610), server['Name'])) + self.message_box.setVisibleCondition('True') + self.busy.setVisibleCondition('True') + result = self.connect_manager.connectToServer(server) + + if result['State'] == CONN_STATE['Unavailable']: + self.busy.setVisibleCondition('False') + self.message.setLabel(lang(30609)) + return False + else: + xbmc.sleep(1000) + self._selected_server = result['Servers'][0] + return True diff --git a/resources/lib/dialogs/servermanual.py b/resources/lib/dialogs/servermanual.py new file mode 100644 index 00000000..d54199eb --- /dev/null +++ b/resources/lib/dialogs/servermanual.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging +import os + +import xbmcgui +import xbmcaddon + +import connect.connectionmanager as connectionmanager +from utils import language as lang + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) +addon = xbmcaddon.Addon('plugin.video.emby') + +CONN_STATE = connectionmanager.ConnectionState +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +CONNECT = 200 +CANCEL = 201 +ERROR_TOGGLE = 202 +ERROR_MSG = 203 +ERROR = { + 'Invalid': 1, + 'Empty': 2 +} + +################################################################################################## + + +class ServerManual(xbmcgui.WindowXMLDialog): + + _server = None + error = None + + + def __init__(self, *args, **kwargs): + + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def set_connect_manager(self, connect_manager): + self.connect_manager = connect_manager + + def is_connected(self): + return True if self._server else False + + def get_server(self): + return self._server + + def onInit(self): + + self.connect_button = self.getControl(CONNECT) + self.cancel_button = self.getControl(CANCEL) + self.error_toggle = self.getControl(ERROR_TOGGLE) + self.error_msg = self.getControl(ERROR_MSG) + self.host_field = self._add_editcontrol(725, 400, 40, 500) + self.port_field = self._add_editcontrol(725, 525, 40, 500) + + self.port_field.setText('8096') + self.setFocus(self.host_field) + + self.host_field.controlUp(self.cancel_button) + self.host_field.controlDown(self.port_field) + self.port_field.controlUp(self.host_field) + self.port_field.controlDown(self.connect_button) + self.connect_button.controlUp(self.port_field) + self.cancel_button.controlDown(self.host_field) + + def onClick(self, control): + + if control == CONNECT: + # Sign in to emby connect + self._disable_error() + + server = self.host_field.getText() + port = self.port_field.getText() + + if not server or not port: + # Display error + self._error(ERROR['Empty'], lang(30617)) + log.error("Server or port cannot be null") + + elif self._connect_to_server(server, port): + self.close() + + elif control == CANCEL: + # Remind me later + self.close() + + def onAction(self, action): + + if self.error == ERROR['Empty'] and self.host_field.getText() and self.port_field.getText(): + self._disable_error() + + if action in (ACTION_BACK, ACTION_PARENT_DIR, ACTION_PREVIOUS_MENU): + self.close() + + def _add_editcontrol(self, x, y, height, width): + + media = os.path.join(addon.getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + control = xbmcgui.ControlEdit(0, 0, 0, 0, + label="User", + font="font10", + textColor="ffc2c2c2", + focusTexture=os.path.join(media, "button-focus.png"), + noFocusTexture=os.path.join(media, "button-focus.png")) + control.setPosition(x, y) + control.setHeight(height) + control.setWidth(width) + + self.addControl(control) + return control + + def _connect_to_server(self, server, port): + + server_address = "%s:%s" % (server, port) + self._message("%s %s..." % (lang(30610), server_address)) + result = self.connect_manager.connectToAddress(server_address) + + if result['State'] == CONN_STATE['Unavailable']: + self._message(lang(30609)) + return False + else: + self._server = result['Servers'][0] + return True + + def _message(self, message): + + self.error_msg.setLabel(message) + self.error_toggle.setVisibleCondition('True') + + def _error(self, state, message): + + self.error = state + self.error_msg.setLabel(message) + self.error_toggle.setVisibleCondition('True') + + def _disable_error(self): + + self.error = None + self.error_toggle.setVisibleCondition('False') diff --git a/resources/lib/dialogs/usersconnect.py b/resources/lib/dialogs/usersconnect.py new file mode 100644 index 00000000..770b0a2c --- /dev/null +++ b/resources/lib/dialogs/usersconnect.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import logging + +import xbmc +import xbmcgui + +################################################################################################## + +log = logging.getLogger("EMBY."+__name__) + +ACTION_PARENT_DIR = 9 +ACTION_PREVIOUS_MENU = 10 +ACTION_BACK = 92 +ACTION_SELECT_ITEM = 7 +ACTION_MOUSE_LEFT_CLICK = 100 +LIST = 155 +MANUAL = 200 +CANCEL = 201 + +################################################################################################## + + +class UsersConnect(xbmcgui.WindowXMLDialog): + + _user = None + _manual_login = False + + + def __init__(self, *args, **kwargs): + + self.kodi_version = int(xbmc.getInfoLabel('System.BuildVersion')[:2]) + xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) + + def set_server(self, server): + self.server = server + + def set_users(self, users): + self.users = users + + def is_user_selected(self): + return True if self._user else False + + def get_user(self): + return self._user + + def is_manual_login(self): + return self._manual_login + + + def onInit(self): + + self.list_ = self.getControl(LIST) + for user in self.users: + user_image = ("userflyoutdefault2.png" if 'PrimaryImageTag' not in user + else self._get_user_artwork(user['Id'], 'Primary')) + self.list_.addItem(self._add_listitem(user['Name'], user['Id'], user_image)) + + self.setFocus(self.list_) + + def _add_listitem(self, label, user_id, user_image): + + item = xbmcgui.ListItem(label) + item.setProperty('id', user_id) + if self.kodi_version > 15: + item.setArt({'Icon': user_image}) + else: + item.setIconImage(user_image) + + return item + + def onAction(self, action): + + if action in (ACTION_BACK, ACTION_PREVIOUS_MENU, ACTION_PARENT_DIR): + self.close() + + if action in (ACTION_SELECT_ITEM, ACTION_MOUSE_LEFT_CLICK): + + if self.getFocusId() == LIST: + user = self.list_.getSelectedItem() + selected_id = user.getProperty('id') + log.info('User Id selected: %s', selected_id) + + for user in self.users: + if user['Id'] == selected_id: + self._user = user + break + + self.close() + + def onClick(self, control): + + if control == MANUAL: + self._manual_login = True + self.close() + + elif control == CANCEL: + self.close() + + def _get_user_artwork(self, user_id, item_type): + # Load user information set by UserClient + return "%s/emby/Users/%s/Images/%s?Format=original" % (self.server, user_id, item_type) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 5cac1315..589115d6 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -42,28 +42,18 @@ class KodiMonitor(xbmc.Monitor): window('plex_kodiScan', clear=True) def onSettingsChanged(self): - # Monitor emby settings - # Review reset setting at a later time, need to be adjusted to account for initial setup - # changes. - '''currentPath = settings('useDirectPaths') - if window('plex_pluginpath') != currentPath: - # Plugin path value changed. Offer to reset - log.info("Changed to playback mode detected") - window('plex_pluginpath', value=currentPath) - resp = xbmcgui.Dialog().yesno( - heading="Playback mode change detected", - line1=( - "Detected the playback mode has changed. The database " - "needs to be recreated for the change to be applied. " - "Proceed?")) - if resp: - utils.reset()''' - + """ + Monitor the PKC settings for changes made by the user + """ currentLog = settings('logLevel') if window('plex_logLevel') != currentLog: # The log level changed, set new prop log.debug("New log level: %s" % currentLog) window('plex_logLevel', value=currentLog) + current_context = "true" if settings('enableContext') == "true" else "" + if window('plex_context') != current_context: + log.info("New context setting: %s", current_context) + window('plex_context', value=current_context) @CatchExceptions(warnuser=False) def onNotification(self, sender, method, data): diff --git a/resources/lib/utils.py b/resources/lib/utils.py index ceae438a..61666041 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -73,6 +73,29 @@ def language(stringid): return ADDON.getLocalizedString(stringid) +def dialog(type_, *args, **kwargs): + + d = xbmcgui.Dialog() + + if "icon" in kwargs: + kwargs['icon'] = kwargs['icon'].replace( + "{plex}", + "special://home/addons/plugin.video.plexkodiconnect/icon.png") + if "heading" in kwargs: + kwargs['heading'] = kwargs['heading'].replace("{plex}", + language(29999)) + + types = { + 'yesno': d.yesno, + 'ok': d.ok, + 'notification': d.notification, + 'input': d.input, + 'select': d.select, + 'numeric': d.numeric + } + return types[type_](*args, **kwargs) + + def tryEncode(uniString, encoding='utf-8'): """ Will try to encode uniString (in unicode) to encoding. This possibly diff --git a/resources/settings.xml b/resources/settings.xml index 9c18a978..bcaca8be 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -38,6 +38,8 @@ + + @@ -117,7 +119,6 @@ - - 320 - 30 - left - 40 - 100% - font16 - ffeeeeee - - - - separator - -10 - 103 - 101% - 1 - separator.png - - - 50 - 600 - vertical - left - 20 - - - 8 - 8012 - 8012 - 100 - 785 - left - 70 - radio-on.png - radio-off.png - white.png - 750 - 32 - 32 - 30 - B3dddddd - - - - - - - separator - 70 - -10 - 101% - 1 - separator.png - - - Save - 150 - 20 - 230 - 8011 - 8013 - 8013 - font14 - - FFededed - B3dddddd - FF000000 - 40 - ff333333 - box.png - box.png - - - Cancel - 430 - 20 - 230 - 8011 - 8012 - 8012 - font14 - - FFededed - B3dddddd - FF000000 - 40 - ff333333 - box.png - box.png - - - - - \ No newline at end of file diff --git a/resources/skins/default/media/emby-icon.png b/resources/skins/default/media/emby-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..52a30539cf81188198bb9eec3ea4970bdcec8823 GIT binary patch literal 55512 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 UZgcxoV*mgE07*qoM6N<$f)(+}tN;K2 literal 0 HcmV?d00001 diff --git a/resources/skins/default/media/separator.png b/resources/skins/default/media/emby-separator.png similarity index 100% rename from resources/skins/default/media/separator.png rename to resources/skins/default/media/emby-separator.png diff --git a/resources/skins/default/media/fading_circle.png b/resources/skins/default/media/fading_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..56aea399872fab57cd01439a481a8b9672bbba09 GIT binary patch literal 1906 zcmd6o`#;l-0>=?6*BZ}uW+}&YZFXE99-(r-%zeX_6^`4;otknBX|5Y`Np5wX2s2-c zF)ebP%WJS=&R)T8747w7YOeLnABKd;X(pKPp)ovajGN<>6N)*fT+cFdCh z`P7NylwQ2|`WTg1Cl8xre*b?}j}JNGxSs*p&Ba|rT=oEb`6QfV*sli3)f+#rWrg^3 zd)EhhRL^S-vvZ8zsxHe|Ccb@+Z%_O9xTq^pD!E`c6*?m#0>ar_TX@9HZoS3bDc6M! z3K4o@mP&NM1lQm}!UF-{j}{|_&*pb-fN_NjyVB8rnYo(RO>K}(9=m)}y(Td_c(XY-#4>YTnJih)rnBaOYq6OkB;6NqfXA?K-?e5A<1)_Z{ zh+`oiLOnmj_mX3>L;mjD-sAO9c=xEPw9#~Qj8M$Gju6rJ?P?Ql{7CoGiB?!2T8ce0 zEJY7Ah?510We`VGom-Mxo}y+qHAYjINlDHRUlac1g1F>c#pcC}ZI-$k{h^0$ z?T$7-NG??T*yg(w+0iVPDE ze$~NRFJ9v++7iK$*Rdt$vNOcskuOUVn2M#g2R%>0^`nQ2UGrNUNh?3tkvVJ&j!vee zRf(U;#(vH!d$91cbJ~>L9)ib++<{)UAaAZ*cvTW0Fmh69@Y!RfDN3?=*~TNY}Wht6od46_7JE++d=$^rAP3O?|k& zXuW}L*SeiLh5L{vy2bbHzYQ-nsXl4B_N)p}K7o#w_oI|*=D!>8wYVTYMKlfKxbqaD zm_afDG}cLdVLG@D>ZP5zCEm$wpEn8D4$AnVC*7@h_4L_2h`#zd_@bMCfT-H}pb_Ne zvcG$tziq}#H}}|A`1c+?n5>sShPLS)Own3&ij2ZZSK1~$;=AsMfpduG-nVZ;`lz;4 zh0rtfbyHA)o!ja6Sw&cgwD6Z?bqPcHI(6_K zZ-#l-oKpgHv}9-h6Rd%=@|GYVv_|~T`(O=M`Q;ivPSje{s)YA+E-{3L zr&lsst=a-T0-(}O^212Se@5XCRtW6v_ZpXh!A)`@xN~M~py&q>{+qx}o7aCnpk(l0 z(q~y;qHow=bpX(OO+n_7^&4YZ-J(aI>62)%e{6C>9qOizymC>}@U=(=yaKWvQ(l2TLEMFgFgc}>v{Lq6LNxjI>v4p9cKF=IL z3rnwma>dG;So}|uwR)(Ba&2qW`x{j#zL2xHm*^sRWjK+6R5Pr?5LQzHaUWp&TGV{M z$ESR$2)X=jS1Rk2Gs@Y^6OzL`Qllmj*dZ&uCMG5AdyiME7xCWp{>ncK5Re9wiA|Hq qecG%l$$rcK#oYGoj`_%ohmvs9fz?W#<+_*uO6M7H(C<@r09&c7wR#u*o?vo@l7O*Bul6@NAu*(1$0A#zEhQ#)Up~oh1 z40!?ZfFkD?!ax*Y(ijR|LpZjOiC{a|@dB9)US=uwkRY&ULiK$i(^s5de+!ubY>-1{ zVu@H05hK=IUwuAz`AmjTVA4Jpk_mhN6+(hnweIQ;K43;7VDyRbAY~@BBqpvT3zM`>?MA4_YrY99Dvk5dgcTNy z2dFN42BX?J83Cb^a%@zFN-QtY!)4-1OhA2XtID}^RXrP3)!DhKYLygLH{+#C){BUo zy3tkS+9mdd-0`lX7Ps!ErM} zOo@KD<0Uran0kKv+jelOKXrq%vP-mh-I~W6$@Tx|=)>`0x&L$vy3rL9i9)>u@gP^P zb3EqvFQ&1s*a{FD(0wQ!Y?sl%iPDPFIe>B;exN=0Z%K_#re(Xd<}=?$CsvxUQDcP(BpwW}uALYKpack=?EI~mV;x;Tbp+t zyY2G^!S%)Fx1y8Tt0uG8{JzopfA7ClN^jJnDpRj>v4vbWl@e!FzLFaGIPB1bPunW` zgpN&_&RVSz;c)iW(X|P!S?7)`MJUbcbc}eM8ZxgY;;{W*gN`=WFIyt5B9_iRBQLk+ zk?+&#Qqxwd7wN7sK6PSmQ)!5P{e+K!CM*}~9|Y0+>`pF?^UX|CSMN8t{6dg4?HIB+I z^+^@EGi5#VQ{f{&&IQ^n>(Mt`8tIT(Z6IaQ6DG2B<*^L@AgQ@M({vaAJd?3Fn2o9D z;<5kynQwY#_ZeCk%QMQQ&&V{HW_zol^8JcG9nSyXzUFq1I}))!=o8;*!|rUm_ggo7 cd3cV$IEZ%^lVPScFbObty85}Sb4q9e0Pz`5JOBUy literal 0 HcmV?d00001 diff --git a/resources/skins/default/media/userflyoutdefault.png b/resources/skins/default/media/userflyoutdefault.png new file mode 100644 index 0000000000000000000000000000000000000000..046676e597da515a8969467dad0fe42877b092fb GIT binary patch literal 1338 zcmai!do&XY0LGWRTB(ub6`Q7vD6utnbnk3zLndLSxYmk1Vj;#DceFKA&MCvCbW4__ zsd*Q&d1W3cbXVq;R}spMn0t+@|L-~Xcg}ad?|k3?AJ@whrv)?w0ssIl0v_uP0Bk$* zLz>&S8qZ=TZp#9^JbYZZT<%shFfafDfe8r-85tQ+C{#^NEhs3+-rk-@qY;TjU0q!# zCnpAj;p^)g9v)7mQn6Sp2m~S!2sSo0mX?-radBWUI4vy=jYfxthLXu-BodkCCgK19 zs)YnB+J`QmAK6hm0S2jgndCcMaeJH0QASra5Q*Z2Qs}s;ReOWGq|ySY!oL20Z18j5 zE!oyw9+HL0eTMd6T5AI{8MTAv>@Rl=ITUgQe3?)*VQ|*Ak=fwiIDud{S)Eao#$p%> z%k%e)hh-by(YDW-e?=YgJRm?~$uaPJK{l;=CEpTWF-WPe;&#g^ zZ0YB6lh|i`E}!Zn&4nZ;k9*1_OIYg-9XD89E|S3BI(Zp@)gU2HRGzZ|B{I zn~C%Tie#Ff!gC&jdiyNi9lYa_XhhRmOr3e`jMpfZYeO803ou?Kr{^^1 z3iX8dLCk`&-^WwPs)9ACA6ue(?^WQWhHP;%i98>6MZ}VuRri)&ZMJembT;1-+55ae zUSNF+PPw6qp{vvKF~Nt6N!gOD^J*&nO|TINcmX$&povd7Wk~28_nK&YDoND!t!`3G%P?%7*+fL9gR4#B6eS_SYeUCGuB8V48;D&(?Qr#6 zvDHWJTmfGtmOP?lzt-U;9$$Fe^15r+!0f1vv1r@_>D-(FKXloavWBY3Kg9dE!6VAx z0;#U47jvSxgKpEb`GMG}P5rY%7^Bj|Rm!pRS+@P}!`Y}jy{6;s8h9u36AHpXPf{u$ z*BC+-K2ZS390ZnwvVMG4-A{4GZ$vh#)$;S}NWUC``@PyRQRTFW&=BRVSg464t~WFicZ+)OJM zwmjXvsz3XI-w?xLbcTCZ=vOzK8%v9cz^-06j`eqFax{%{TG(or@o@9RI86=9Q(MSInGi6IzjRF zlPb$=`*W8;y}N5Dp>rmC(z7Xb(}DG@yY=1uu1Dt{7*%I>LWW9aZ}go-@K;{Wh-Yxb t_I&jyX1Bjc{UKX^>6>h@b~RsfW%+QqE;k7W`c_+_K9;a>t8_ab^`za literal 0 HcmV?d00001 diff --git a/resources/skins/default/media/userflyoutdefault2.png b/resources/skins/default/media/userflyoutdefault2.png new file mode 100644 index 0000000000000000000000000000000000000000..24d771eb7e9f8c1d50ce033f37c57e25892432d4 GIT binary patch literal 1700 zcmb`Hdo8`l$IUqth0aYKYPwT=Q;1^dB4y1InVpo%VW@8l@-(# z007EVHzzLuAbAM^0$Psf>;6nRPBCaqXF2ER=jZ0;7EgKcuOaU)#$xyH+X9#TONJ$J zNm-g&GA|X29v+W(u=Z4){54w+dC|QAW|kaNo;lrJT`>A8UY*bqM%kpZ`pcWkM5T$N z%?&KZ$H%^Kue%j5DbUH{#`YlquotOL4$Or9@z-Oq{S4xXVULy0Q-k-gw1K+=^Pijz z=t`-V9lNUPCT6PhC(R?o-B9J^%^?yj^Bz(XfrSLv&c7%EjQ>Z{jsp{umGuP}wx2{2 zp4Ki=7FAdzdN-EP3JaB3}tmgO}9N|BXN_wMSj+5orDa;j< zhIqV+`pMjIa4AQKF-E2B28VaTWW+-u5XA==BViywgCdbT|hoR=+4xMOnScNgf$`Vpq30?}a6ya4gY$(#@bQ2Lsib;5DQ~)s)y2g}aRf<;& zWSo?^&)EqZ59tGD{#`s~KQ=GZ%){w3wkz!WdBRf#S?kmvuzaOQX_d{w>Q4XBt+E(k zV9wSX_fqUym8j2kMYegraLNk+GsWU34=3=9pKnuA-(#f~BkPy#MQ7GwC_m_AxJ-u& z1wxrOF_fedlAtG32&&L&eM&y~nR8Kycy?HGu2h3H7@FNSL$?IDkk%)c-q}h3)lP@B zqK5wqppk^G<7Jv6!_!u{N6P{II62nRie?XA=cJ#pKgiSsU2&w!*3DnPbW(SwsIJWjVMhU!GW1bS<*+pLSt7TO?o!abGrGrxwn_}5rOB#a370u zg8Ad4pz=qgDZol z4sY)bFj=1ZRSU9d$IicRoXZ!n7@TeGCIoM^%w}KTvXq8o_v*Hwiq{k6P-G+JP5oAP zi^R}nFdi|NNUnvMJYMFNScPWIi9SC6p4KImmE1myLlXH}0YUmw%LMR* zzm@#Tf9NxUrX>d6e_wuSPm$Eq2Ik!em|9nOz~6S7nsos2ylW{ zSU(C}&*i+TG%{gkDKWxpZ?~W6g-kUlcjAMBt4!2bEr&;Jy`X+Y7iyPU8Ar)fawH%< z6g5RMa%fo{qgWF@m6{YkqX@T|L*086G2DXmqMSx|9ptL3_)5Jho*NIPH7TTmY|X5U z;2?G6$*k|i$EXC!S4<3k2k^Ndny9}F!hKqIK57v+LuSc88SM6^0=st`L+-N(+F@>P z<(AUJ{AU~tiV~~k~J9Z*hFTw=D^h&sM1K2{cB@4xt&{cA9cDK? z%=fJvWuF8e&hD6wA^<75ee@-oxz^I*!Y~mQHFS)yYw`Z3F~SKnNAk;jzRazjGbXCR!nq+@yk%-dWS1YmGcP5cd=vIfx7V&fV}u-*sy!z_Aw6 zfV)T>f{OSLlB{CA4G{q3sP>fOmtKuWYaCIk8({=M|A5HYSWCvHTYppK_7%uKctCZg KJ5@Qdj{OO+*$OQH literal 0 HcmV?d00001 diff --git a/resources/skins/default/media/wifi.png b/resources/skins/default/media/wifi.png new file mode 100644 index 0000000000000000000000000000000000000000..2a646c5599262ee182823c73a615d5210f617895 GIT binary patch literal 1095 zcmV-N1i1T&P)h`|Vkz8*+h1fk~|PiG8uq!4(g4q}HIS#=ADzy+Av2bkIlhrbk4 zZxTme8%0?ZK2a54fEZVE5P7H&S$Y^?fevh!4QrMIsr3K<{{yr4BB}J(;rOWW{`dU< z*Yf=XsrUE)|Df^y|NQ;;_5APj{MX+1GO6_6`TmgV{W8w`1G)G@v-Icu|G@J8o9g|D zu<8837i z_lV2)BEI*i%l44O^?AAUX}R>*`Twx={(0v8-|_qd!TPw~`AN(9-{kmd!S-3R^pu)n z{{R309duGoQvk!4OnHZg5Vas|YD_MNr=^E3AP^8fE^IzLTwDqywQ&Fd0@O)FK~!ko z?U`v)+E5gRbFzSFSQHVpyN*+qGH{xOZWBv|CDwZ( zmY18vNJ&Xak&(??mhG5Crej-HJ{!Mb(@JxzI#m_t!~RLhCShu|vOLj)(yVUx+@GMHiCzrQ|3c3FW3pN+Kpo z5%;iub>8r5k9glMZoAP2%o1U|dAC!WpmX$AgkjB`@O%5U5Dq?p%vY_}4J)7q*K18C zsqeOG(|NHaldOeEY+0q=*Zg>1;~ytp;D@h1ib-tsYMr%weg5%VJTd&)_f10kKD1sq zQks4KZ;!f@6W1I6`dBqusuS(l9xLvJjNw@?yfHbnB|%C1kpIHOScFO}C}9itRnA_I z9SV|ykXo+0+=an;W5@G4jf)Sw>)%i1Q*1*CTYhaleHl!a&Ug~)U!}vKP!7M(k9|u4`vZA~R#VZn