platform.py 10.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
#
# 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
17
import os.path as op
Paul McCarthy's avatar
Paul McCarthy committed
18
import sys
19
import importlib
20
import deprecation
21
22
23

import fsl.utils.notifier as notifier

24
25
26
27
28
29
30
31
32
33
34
# 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')

35
36
37
38

log = logging.getLogger(__name__)


39
40
41
WX_UNKNOWN = 0
"""Identifier for the :attr:`Platform.wxFlavour` and
:attr:`Platform.wxPlatform` properties indicating an unknown/undetermined
Paul McCarthy's avatar
Paul McCarthy committed
42
flavour/platform.
43
44
45
"""


46
47
48
49
50
51
52
WX_PYTHON  = 1
"""Identifier for the :attr:`Platform.wxFlavour` property, indicating that
we are running standard wx Python.
"""


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


WX_MAC_COCOA = 1
Paul McCarthy's avatar
Paul McCarthy committed
59
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
60
61
62
63
64
are running the OSX cocoa wx build.
"""


WX_MAC_CARBON = 2
Paul McCarthy's avatar
Paul McCarthy committed
65
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
66
67
68
69
70
are running the OSX carbon wx build.
"""


WX_GTK = 3
Paul McCarthy's avatar
Paul McCarthy committed
71
"""Identifier for the :attr:`Platform.wxPlatform` property, indicating that we
72
73
74
75
are running the Linux/GTK wx build.
"""


76
77
78
@deprecation.deprecated(deprecated_in='1.2.2',
                        removed_in='2.0.0',
                        details='Use fsleyes_widgets.isalive instead')
79
80
81
82
def isWidgetAlive(widget):
    """Returns ``True`` if the given ``wx.Window`` object is "alive" (i.e.
    has not been destroyed), ``False`` otherwise. Works in both wxPython
    and wxPython/Phoenix.
83
84
85
86

    .. warning:: Don't try to test whether a ``wx.MenuItem`` has been
                 destroyed, as it will probably result in segmentation
                 faults. Check the parent ``wx.Menu`` instead.
87
88
89
90
    """

    import wx

91
92
93
94
95
96
97
98
99
100
101
102

    if platform.wxFlavour == platform.WX_PHOENIX:
        excType = RuntimeError
    elif platform.wxFlavour == platform.WX_PYTHON:
        excType = wx.PyDeadObjectError

    try:
        widget.GetParent()
        return True

    except excType:
        return False
103
104


105
106
107
108
class Platform(notifier.Notifier):
    """The ``Platform`` class contains a handful of properties which contain
    information about the platform we are running on.

109
110
    .. note:: The values of the :attr:`glVersion` and :attr:`glRenderer`
              properties are not automatically set - they will only contain
111
112
              a value if one is assigned to them. *FSLeyes* does this during
              startup, in the :func:`fsleyes.gl.bootstrap` function.
113

114
115
    .. autosummary::

Paul McCarthy's avatar
Paul McCarthy committed
116
117
       os
       frozen
118
119
       fsldir
       haveGui
120
       canHaveGui
121
       inSSHSession
122
       wxPlatform
123
       wxFlavour
124
125
       glVersion
       glRenderer
126
       glIsSoftwareRenderer
127
128
    """

129

130
131
    def __init__(self):
        """Create a ``Platform`` instance. """
132
133
134

        # For things which 'from fsl.utils.platform import platform',
        # these identifiers are available on the platform instance
135
        self.WX_UNKNOWN    = WX_UNKNOWN
136
137
138
139
140
        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
141
        self.isWidgetAlive = isWidgetAlive
142

143
        self.__inSSHSession = False
144
        self.__inVNCSession = False
145
146
        self.__glVersion    = None
        self.__glRenderer   = None
147
        self.__glIsSoftware = None
148
        self.__fslVersion   = None
Paul McCarthy's avatar
Paul McCarthy committed
149
        self.__fsldir       = None
150
        self.fsldir         = os.environ.get('FSLDIR', None)
151

152
153
154
155
156
157
158
159
160
161
162
        # 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

163
164
165
166
167
        # 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']
168

169
170
        self.__inSSHSession = any(s in os.environ for s in sshVars)
        self.__inVNCSession = any(v in os.environ for v in vncVars)
171

172

Paul McCarthy's avatar
Paul McCarthy committed
173
174
175
176
177
178
179
    @property
    def os(self):
        """The operating system name. Whatever is returned by the built-in
        ``platform.system`` function.
        """
        return builtin_platform.system()

180

Paul McCarthy's avatar
Paul McCarthy committed
181
182
183
184
185
186
187
    @property
    def frozen(self):
        """``True`` if we are running in a compiled/frozen application,
        ``False`` otherwise.
        """
        return getattr(sys, 'frozen', False)

188
189
190
191

    @property
    def haveGui(self):
        """``True`` if we are running with a GUI, ``False`` otherwise. """
192
193
        try:
            import wx
194
195
196
197
            app = wx.GetApp()
            return (self.canHaveGui and
                    app is not None and
                    app.IsMainLoopRunning())
198

199
200
        except ImportError:
            return False
201

202

203
204
205
    @property
    def canHaveGui(self):
        """``True`` if it is possible to create a GUI, ``False`` otherwise. """
206
        return self.__canHaveGui
207
208


209
210
211
212
213
214
215
    @property
    def inSSHSession(self):
        """``True`` if this application is running over an SSH session,
        ``False`` otherwise.
        """
        return self.__inSSHSession

216

217
218
219
220
221
222
223
224
225
226
227
228
229
    @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


230
    @property
231
    def wxPlatform(self):
232
233
        """One of :data:`WX_UNKNOWN`, :data:`WX_MAC_COCOA`,
        :data:`WX_MAC_CARBON`, or :data:`WX_GTK`, indicating the wx platform.
234
        """
235

236
        if not self.canHaveGui:
237
238
239
240
241
242
            return WX_UNKNOWN

        import wx

        pi = [t.lower() for t in wx.PlatformInfo]

Paul McCarthy's avatar
Paul McCarthy committed
243
244
245
246
        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
247

248
        if plat is WX_UNKNOWN:
Paul McCarthy's avatar
Paul McCarthy committed
249
250
            log.warning('Could not determine wx platform from '
                        'information: {}'.format(pi))
251

Paul McCarthy's avatar
Paul McCarthy committed
252
        return plat
253

254

255
256
    @property
    def wxFlavour(self):
257
258
        """One of :data:`WX_UNKNOWN`, :data:`WX_PYTHON` or :data:`WX_PHOENIX`,
        indicating the wx flavour.
259
        """
260

261
        if not self.canHaveGui:
262
263
264
265
266
267
268
269
            return WX_UNKNOWN

        import wx

        pi        = [t.lower() for t in wx.PlatformInfo]
        isPhoenix = False

        for tag in pi:
270
            if 'phoenix' in tag:
271
272
273
274
275
                isPhoenix = True
                break

        if isPhoenix: return WX_PHOENIX
        else:         return WX_PYTHON
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293


    @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 self.__fsldir


    @fsldir.setter
    def fsldir(self, value):
        """Changes the value of the :attr:`fsldir` property, and notifies any
        registered listeners.
        """
294
295
296
297
298
299

        if value is not None:
            value = value.strip()

        if   value is None:        pass
        elif value == '':          value = None
300
301
        elif not op.exists(value): value = None
        elif not op.isdir(value):  value = None
302

303
        self.__fsldir = value
304
305
306

        if value is not None:
            os.environ['FSLDIR'] = value
307

Paul McCarthy's avatar
Paul McCarthy committed
308
309
            # Set the FSL version field if we can
            versionFile = op.join(value, 'etc', 'fslversion')
310

Paul McCarthy's avatar
Paul McCarthy committed
311
312
313
            if op.exists(versionFile):
                with open(versionFile, 'rt') as f:
                    self.__fslVersion = f.read().strip()
314
315

        self.notify(value=value)
316

317
318
319
320
321
322
323
324

    @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

325

326
327
328
329
330
331
332
    @property
    def glVersion(self):
        """Returns the available OpenGL version, or ``None`` if it has not
        been set.
        """
        return self.__glVersion

333

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
    @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

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
        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

375
376
377
378
379
380

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.
"""