Source code for fsl.utils.platform

#
# platform.py - Platform information
#
# Author: Paul McCarthy <pauldmccarthy@gmail.com>
#
"""This module provides the :class:`Platform` class, which is a container
of information about the current platform we are running on. A single
``Platform`` instance is created when this module is first imported, and
is available as a module attribute called :attr:`platform`.
"""


import logging

import os
import os.path as op
import sys
import importlib

import fsl.utils.notifier as notifier

# An annoying consequence of using
# a system-module name for our own
# module is that we can't import
# it directly (as it will attempt
# to import itself, i.e. this module).
#
# This is only necessary in Python 2.x
# (as python 3 disallows relative
# imports).
builtin_platform = importlib.import_module('platform')


log = logging.getLogger(__name__)


WX_UNKNOWN = 0
"""Identifier for the :attr:`Platform.wxFlavour` and
:attr:`Platform.wxPlatform` properties indicating an unknown/undetermined
flavour/platform.
"""


WX_PYTHON  = 1
"""Identifier for the :attr:`Platform.wxFlavour` property, indicating that
we are running standard wx Python.
"""


WX_PHOENIX = 2
"""Identifier for the :attr:`Platform.wxFlavour` property, indicating that we
are running wx Python/Phoenix.
"""


WX_MAC_COCOA = 1
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
are running the OSX cocoa wx build.
"""


WX_MAC_CARBON = 2
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
are running the OSX carbon wx build.
"""


WX_GTK = 3
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
are running the Linux/GTK wx build.
"""


[docs]class Platform(notifier.Notifier): """The ``Platform`` class contains a handful of properties which contain information about the platform we are running on. .. note:: The values of the :attr:`glVersion` and :attr:`glRenderer` properties are not automatically set - they will only contain a value if one is assigned to them. *FSLeyes* does this during startup, in the :func:`fsleyes.gl.bootstrap` function. .. autosummary:: os frozen fsldir fsldevdir haveGui canHaveGui inSSHSession wxPlatform wxFlavour glVersion glRenderer glIsSoftwareRenderer """
[docs] def __init__(self): """Create a ``Platform`` instance. """ # For things which 'from fsl.utils.platform import platform', # these identifiers are available on the platform instance self.WX_UNKNOWN = WX_UNKNOWN self.WX_PYTHON = WX_PYTHON self.WX_PHOENIX = WX_PHOENIX self.WX_MAC_COCOA = WX_MAC_COCOA self.WX_MAC_CARBON = WX_MAC_CARBON self.WX_GTK = WX_GTK self.__inSSHSession = False self.__inVNCSession = False self.__glVersion = None self.__glRenderer = None self.__glIsSoftware = None self.__fslVersion = None # initialise fsldir - see fsldir.setter self.fsldir = self.fsldir # Determine if a display is available. We do # this once at init (instead of on-demand in # the canHaveGui method) because calling the # IsDisplayAvailable function will cause the # application to steal focus under OSX! try: import wx self.__canHaveGui = wx.App.IsDisplayAvailable() except ImportError: self.__canHaveGui = False # If one of the SSH_/VNC environment # variables is set, then we're probably # running over SSH/VNC. sshVars = ['SSH_CLIENT', 'SSH_TTY'] vncVars = ['VNCDESKTOP', 'X2GO_SESSION', 'NXSESSIONID'] self.__inSSHSession = any(s in os.environ for s in sshVars) self.__inVNCSession = any(v in os.environ for v in vncVars)
@property def os(self): """The operating system name. Whatever is returned by the built-in ``platform.system`` function. """ return builtin_platform.system() @property def frozen(self): """``True`` if we are running in a compiled/frozen application, ``False`` otherwise. """ return getattr(sys, 'frozen', False) @property def haveGui(self): """``True`` if we are running with a GUI, ``False`` otherwise. This currently equates to testing whether a display is available (see :meth:`canHaveGui`) and whether a ``wx.App`` exists. It previously also tested whether an event loop was running, but this is not compatible with execution from IPython/Jupyter notebook, where the event loop is called periodically, and so is not always running. """ try: import wx app = wx.GetApp() # TODO Previously this conditional # also used app.IsMainLoopRunning() # to check that the wx main loop # was running. But this doesn't # suit situations where a non-main # event loop is running, or where # the mainloop is periodically # started and stopped (e.g. when # the event loop is being run by # IPython). # # In c++ wx, there is the # wx.App.UsesEventLoop method, but # this is not presently exposed to # Python code (and wouldn't help # to detect the loop start/stop # scenario). # # So this constraint has been # (hopefully) temporarily relaxed # until I can think of a better # solution. return (self.canHaveGui and app is not None) except ImportError: return False @property def canHaveGui(self): """``True`` if it is possible to create a GUI, ``False`` otherwise. """ return self.__canHaveGui @property def inSSHSession(self): """``True`` if this application is running over an SSH session, ``False`` otherwise. """ return self.__inSSHSession @property def inVNCSession(self): """``True`` if this application is running over a VNC (or similar) session, ``False`` otherwise. Currently, the following remote desktop environments are detected: - VNC - x2go - NoMachine """ return self.__inVNCSession @property def wxPlatform(self): """One of :data:`WX_UNKNOWN`, :data:`WX_MAC_COCOA`, :data:`WX_MAC_CARBON`, or :data:`WX_GTK`, indicating the wx platform. """ if not self.canHaveGui: return WX_UNKNOWN import wx pi = [t.lower() for t in wx.PlatformInfo] if any(['cocoa' in p for p in pi]): plat = WX_MAC_COCOA elif any(['carbon' in p for p in pi]): plat = WX_MAC_CARBON elif any(['gtk' in p for p in pi]): plat = WX_GTK else: plat = WX_UNKNOWN if plat is WX_UNKNOWN: log.warning('Could not determine wx platform from ' 'information: {}'.format(pi)) return plat @property def wxFlavour(self): """One of :data:`WX_UNKNOWN`, :data:`WX_PYTHON` or :data:`WX_PHOENIX`, indicating the wx flavour. """ if not self.canHaveGui: return WX_UNKNOWN import wx pi = [t.lower() for t in wx.PlatformInfo] isPhoenix = False for tag in pi: if 'phoenix' in tag: isPhoenix = True break if isPhoenix: return WX_PHOENIX else: return WX_PYTHON @property def fsldir(self): """The FSL installation location. .. note:: The ``fsldir`` property can be updated - when it is changed, any registered listeners are notified via the :class:`.Notifier` interface. """ return os.environ.get('FSLDIR', None) @property def fsldevdir(self): """The FSL development directory location. """ return os.environ.get('FSLDEVDIR', None) @property def fslwsl(self): """Boolean flag indicating whether FSL is installed in Windows Subsystem for Linux """ return self.fsldir is not None and self.fsldir.startswith("\\\\wsl$") @fsldir.setter def fsldir(self, value): """Changes the value of the :attr:`fsldir` property, and notifies any registered listeners. """ if value is not None: value = value.strip() if value is None: pass elif value == '': value = None elif not op.exists(value): value = None elif not op.isdir(value): value = None if value is None: os.environ.pop('FSLDIR', None) else: os.environ['FSLDIR'] = value # Set the FSL version field if we can versionFile = op.join(value, 'etc', 'fslversion') if op.exists(versionFile): with open(versionFile, 'rt') as f: # split string at colon for new hash style versions # first object in list is the non-hashed version string (e.g. 6.0.2) # if no ":hash:" then standard FSL version string is still returned self.__fslVersion = f.read().strip().split(":")[0] self.notify(value=value) @fsldevdir.setter def fsldevdir(self, value): """Changes the value of the :attr:`fsldevdir` property, and notifies any registered listeners. """ if value is not None: value = value.strip() if value is None: pass elif value == '': value = None elif not op.exists(value): value = None elif not op.isdir(value): value = None if value is None: os.environ.pop('FSLDEVDIR', None) else: os.environ['FSLDEVDIR'] = value @property def fslVersion(self): """Returns the FSL version as a string, e.g. ``'5.0.9'``. Returns ``None`` if a FSL installation could not be found. """ return self.__fslVersion @property def glVersion(self): """Returns the available OpenGL version, or ``None`` if it has not been set. """ return self.__glVersion @glVersion.setter def glVersion(self, value): """Set the available OpenGL version. """ self.__glVersion = value @property def glRenderer(self): """Returns the available OpenGL renderer, or ``None`` if it has not been set. """ return self.__glRenderer @glRenderer.setter def glRenderer(self, value): """Set the available OpenGL renderer. """ self.__glRenderer = value value = value.lower() # There doesn't seem to be any quantitative # method for determining whether we are using # software-based rendering, so a hack is # necessary. self.__glIsSoftware = any(( 'software' in value, 'chromium' in value, )) @property def glIsSoftwareRenderer(self): """Returns ``True`` if the OpenGL renderer is software based, ``False`` otherwise, or ``None`` if the renderer has not yet been set. .. note:: This check is based on heuristics, ans is not guaranteed to be correct. """ return self.__glIsSoftware
platform = Platform() """An instance of the :class:`Platform` class. Feel free to create your own instance, but be aware that if you do so you will not be updated of changes to the :attr:`Platform.fsldir` property. """