1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-12 15:27:14 +03:00
2018-05-27 15:46:56 +01:00

372 lines
13 KiB
Python

"""
Plugin hooks for EDMC - Ian Norton, Jonathan Harris
"""
import os
import imp
import sys
import operator
import threading # We don't use it, but plugins might
from traceback import print_exc
import Tkinter as tk
import myNotebook as nb
from config import config
# Dashboard Flags constants
FlagsDocked = 1<<0 # on a landing pad
FlagsLanded = 1<<1 # on planet surface
FlagsLandingGearDown = 1<<2
FlagsShieldsUp = 1<<3
FlagsSupercruise = 1<<4
FlagsFlightAssistOff = 1<<5
FlagsHardpointsDeployed = 1<<6
FlagsInWing = 1<<7
FlagsLightsOn = 1<<8
FlagsCargoScoopDeployed = 1<<9
FlagsSilentRunning = 1<<10
FlagsScoopingFuel = 1<<11
FlagsSrvHandbrake = 1<<12
FlagsSrvTurret = 1<<13
FlagsSrvUnderShip = 1<<14
FlagsSrvDriveAssist = 1<<15
FlagsFsdMassLocked = 1<<16
FlagsFsdCharging = 1<<17
FlagsFsdCooldown = 1<<18
FlagsLowFuel = 1<<19 # <25%
FlagsOverHeating = 1<<20 # > 100%
FlagsHasLatLong = 1<<21
FlagsIsInDanger = 1<<22
FlagsBeingInterdicted = 1<<23
FlagsInMainShip = 1<<24
FlagsInFighter = 1<<25
FlagsInSRV = 1<<26
# Dashboard GuiFocus constants
GuiFocusNoFocus = 0
GuiFocusInternalPanel = 1 # right hand side
GuiFocusExternalPanel = 2 # left hand side
GuiFocusCommsPanel = 3 # top
GuiFocusRolePanel = 4 # bottom
GuiFocusStationServices = 5
GuiFocusGalaxyMap = 6
GuiFocusSystemMap = 7
# List of loaded Plugins
PLUGINS = []
# For asynchronous error display
last_error = {
'msg': None,
'root': None,
}
class Plugin(object):
def __init__(self, name, loadfile):
"""
Load a single plugin
:param name: module name
:param loadfile: the main .py file
:raises Exception: Typically ImportError or OSError
"""
self.name = name # Display name.
self.folder = name # basename of plugin folder. None for internal plugins.
self.module = None # None for disabled plugins.
if loadfile:
sys.stdout.write(('loading plugin %s from "%s"\n' % (name.replace('.', '_'), loadfile)).encode('utf-8'))
with open(loadfile, 'rb') as plugfile:
module = imp.load_module('plugin_%s' % name.encode('ascii', 'replace').replace('.', '_'), plugfile, loadfile.encode(sys.getfilesystemencoding()),
('.py', 'r', imp.PY_SOURCE))
newname = module.plugin_start()
self.name = newname and unicode(newname) or name
self.module = module
else:
sys.stdout.write('plugin %s disabled\n' % name)
def _get_func(self, funcname):
"""
Get a function from a plugin
:param funcname:
:returns: The function, or None if it isn't implemented.
"""
return getattr(self.module, funcname, None)
def get_app(self, parent):
"""
If the plugin provides mainwindow content create and return it.
:param parent: the parent frame for this entry.
:returns: None, a tk Widget, or a pair of tk.Widgets
"""
plugin_app = self._get_func('plugin_app')
if plugin_app:
try:
appitem = plugin_app(parent)
if appitem is None:
return None
elif isinstance(appitem, tuple):
if len(appitem) != 2 or not isinstance(appitem[0], tk.Widget) or not isinstance(appitem[1], tk.Widget):
raise AssertionError
elif not isinstance(appitem, tk.Widget):
raise AssertionError
return appitem
except:
print_exc()
return None
def get_prefs(self, parent, cmdr, is_beta):
"""
If the plugin provides a prefs frame, create and return it.
:param parent: the parent frame for this preference tab.
:param cmdr: current Cmdr name (or None). Relevant if you want to have
different settings for different user accounts.
:param is_beta: whether the player is in a Beta universe.
:returns: a myNotebook Frame
"""
plugin_prefs = self._get_func('plugin_prefs')
if plugin_prefs:
try:
if plugin_prefs.func_code.co_argcount == 1:
frame = plugin_prefs(parent)
else:
frame = plugin_prefs(parent, cmdr, is_beta)
if not isinstance(frame, nb.Frame):
raise AssertionError
return frame
except:
print_exc()
return None
def load_plugins(master):
"""
Find and load all plugins
"""
last_error['root'] = master
imp.acquire_lock()
internal = []
for name in os.listdir(config.internal_plugin_dir):
if name.endswith('.py') and not name[0] in ['.', '_']:
try:
plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir, name))
plugin.folder = None # Suppress listing in Plugins prefs tab
internal.append(plugin)
except:
print_exc()
PLUGINS.extend(sorted(internal, key = lambda p: operator.attrgetter('name')(p).lower()))
# Add plugin folder to load path so packages can be loaded from plugin folder
sys.path.append(config.plugin_dir)
found = []
# Load any plugins that are also packages first
for name in sorted(os.listdir(config.plugin_dir),
key = lambda n: (not os.path.isfile(os.path.join(config.plugin_dir, n, '__init__.py')), n.lower())):
if not os.path.isdir(os.path.join(config.plugin_dir, name)) or name[0] in ['.', '_']:
pass
elif name.endswith('.disabled'):
name, discard = name.rsplit('.', 1)
found.append(Plugin(name, None))
else:
try:
# Add plugin's folder to load path in case plugin has internal package dependencies
sys.path.append(os.path.join(config.plugin_dir, name))
found.append(Plugin(name, os.path.join(config.plugin_dir, name, 'load.py')))
except:
print_exc()
PLUGINS.extend(sorted(found, key = lambda p: operator.attrgetter('name')(p).lower()))
imp.release_lock()
def provides(fn_name):
"""
Find plugins that provide a function
:param fn_name:
:returns: list of names of plugins that provide this function
.. versionadded:: 3.0.2
"""
return [p.name for p in PLUGINS if p._get_func(fn_name)]
def invoke(plugin_name, fallback, fn_name, *args):
"""
Invoke a function on a named plugin
:param plugin_name: preferred plugin on which to invoke the function
:param fallback: fallback plugin on which to invoke the function, or None
:param fn_name:
:param *args: arguments passed to the function
:returns: return value from the function, or None if the function was not found
.. versionadded:: 3.0.2
"""
for plugin in PLUGINS:
if plugin.name == plugin_name and plugin._get_func(fn_name):
return plugin._get_func(fn_name)(*args)
for plugin in PLUGINS:
if plugin.name == fallback:
assert plugin._get_func(fn_name), plugin.name # fallback plugin should provide the function
return plugin._get_func(fn_name)(*args)
def notify_stop():
"""
Notify each plugin that the program is closing.
If your plugin uses threads then stop and join() them before returning.
.. versionadded:: 2.3.7
"""
error = None
for plugin in PLUGINS:
plugin_stop = plugin._get_func('plugin_stop')
if plugin_stop:
try:
newerror = plugin_stop()
error = error or newerror
except:
print_exc()
return error
def notify_prefs_cmdr_changed(cmdr, is_beta):
"""
Notify each plugin that the Cmdr has been changed while the settings dialog is open.
Relevant if you want to have different settings for different user accounts.
:param cmdr: current Cmdr name (or None).
:param is_beta: whether the player is in a Beta universe.
"""
for plugin in PLUGINS:
prefs_cmdr_changed = plugin._get_func('prefs_cmdr_changed')
if prefs_cmdr_changed:
try:
prefs_cmdr_changed(cmdr, is_beta)
except:
print_exc()
def notify_prefs_changed(cmdr, is_beta):
"""
Notify each plugin that the settings dialog has been closed.
The prefs frame and any widgets you created in your `get_prefs()` callback
will be destroyed on return from this function, so take a copy of any
values that you want to save.
:param cmdr: current Cmdr name (or None).
:param is_beta: whether the player is in a Beta universe.
"""
for plugin in PLUGINS:
prefs_changed = plugin._get_func('prefs_changed')
if prefs_changed:
try:
if prefs_changed.func_code.co_argcount == 0:
prefs_changed()
else:
prefs_changed(cmdr, is_beta)
except:
print_exc()
def notify_journal_entry(cmdr, is_beta, system, station, entry, state):
"""
Send a journal entry to each plugin.
:param cmdr: The Cmdr name, or None if not yet known
:param system: The current system, or None if not yet known
:param station: The current station, or None if not docked or not yet known
:param entry: The journal entry as a dictionary
:param state: A dictionary containing info about the Cmdr, current ship and cargo
:param is_beta: whether the player is in a Beta universe.
:returns: Error message from the first plugin that returns one (if any)
"""
error = None
for plugin in PLUGINS:
journal_entry = plugin._get_func('journal_entry')
if journal_entry:
try:
# Pass a copy of the journal entry in case the callee modifies it
if journal_entry.func_code.co_argcount == 4:
newerror = journal_entry(cmdr, system, station, dict(entry))
elif journal_entry.func_code.co_argcount == 5:
newerror = journal_entry(cmdr, system, station, dict(entry), dict(state))
else:
newerror = journal_entry(cmdr, is_beta, system, station, dict(entry), dict(state))
error = error or newerror
except:
print_exc()
return error
def notify_dashboard_entry(cmdr, is_beta, entry):
"""
Send a status entry to each plugin.
:param cmdr: The piloting Cmdr name
:param is_beta: whether the player is in a Beta universe.
:param entry: The status entry as a dictionary
:returns: Error message from the first plugin that returns one (if any)
"""
error = None
for plugin in PLUGINS:
status = plugin._get_func('dashboard_entry')
if status:
try:
# Pass a copy of the status entry in case the callee modifies it
newerror = status(cmdr, is_beta, dict(entry))
error = error or newerror
except:
print_exc()
return error
def notify_system_changed(timestamp, system, coordinates):
"""
Send notification data to each plugin when we arrive at a new system.
:param timestamp:
:param system:
.. deprecated:: 2.2
Use :func:`journal_entry` with the 'FSDJump' event.
"""
for plugin in PLUGINS:
system_changed = plugin._get_func('system_changed')
if system_changed:
try:
if system_changed.func_code.co_argcount == 2:
system_changed(timestamp, system)
else:
system_changed(timestamp, system, coordinates)
except:
print_exc()
def notify_newdata(data, is_beta):
"""
Send the latest EDMC data from the FD servers to each plugin
:param data:
:param is_beta: whether the player is in a Beta universe.
:returns: Error message from the first plugin that returns one (if any)
"""
error = None
for plugin in PLUGINS:
cmdr_data = plugin._get_func('cmdr_data')
if cmdr_data:
try:
if cmdr_data.func_code.co_argcount == 1:
newerror = cmdr_data(data)
else:
newerror = cmdr_data(data, is_beta)
error = error or newerror
except:
print_exc()
return error
def show_error(err):
"""
Display an error message in the status line of the main window.
:param err:
.. versionadded:: 2.3.7
"""
if err and last_error['root']:
last_error['msg'] = unicode(err)
last_error['root'].event_generate('<<PluginError>>', when="tail")