~landscape/zope3/newer-from-ztk

« back to all changes in this revision

Viewing changes to src/twisted/internet/gtk2reactor.py

  • Committer: Thomas Hervé
  • Date: 2009-07-08 13:52:04 UTC
  • Revision ID: thomas@canonical.com-20090708135204-df5eesrthifpylf8
Remove twisted copy

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
 
# See LICENSE for details.
3
 
 
4
 
 
5
 
"""
6
 
This module provides support for Twisted to interact with the glib/gtk2 mainloop.
7
 
 
8
 
In order to use this support, simply do the following::
9
 
 
10
 
    |  from twisted.internet import gtk2reactor
11
 
    |  gtk2reactor.install()
12
 
 
13
 
Then use twisted.internet APIs as usual.  The other methods here are not
14
 
intended to be called directly.
15
 
 
16
 
When installing the reactor, you can choose whether to use the glib
17
 
event loop or the GTK+ event loop which is based on it but adds GUI
18
 
integration.
19
 
 
20
 
API Stability: stable
21
 
 
22
 
Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
23
 
"""
24
 
 
25
 
__all__ = ['install']
26
 
 
27
 
# System Imports
28
 
import sys
29
 
from zope.interface import implements
30
 
try:
31
 
    if not hasattr(sys, 'frozen'):
32
 
        # Don't want to check this for py2exe
33
 
        import pygtk
34
 
        pygtk.require('2.0')
35
 
except (ImportError, AttributeError):
36
 
    pass # maybe we're using pygtk before this hack existed.
37
 
import gobject
38
 
if hasattr(gobject, "threads_init"):
39
 
    # recent versions of python-gtk expose this. python-gtk=2.4.1
40
 
    # (wrapping glib-2.4.7) does. python-gtk=2.0.0 (wrapping
41
 
    # glib-2.2.3) does not.
42
 
    gobject.threads_init()
43
 
 
44
 
# Twisted Imports
45
 
from twisted.python import log, threadable, runtime, failure
46
 
from twisted.internet.interfaces import IReactorFDSet
47
 
 
48
 
# Sibling Imports
49
 
from twisted.internet import main, posixbase, error, selectreactor
50
 
 
51
 
reads = {}
52
 
writes = {}
53
 
hasReader = reads.has_key
54
 
hasWriter = writes.has_key
55
 
 
56
 
# the next callback
57
 
_simtag = None
58
 
POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
59
 
 
60
 
# glib's iochannel sources won't tell us about any events that we haven't
61
 
# asked for, even if those events aren't sensible inputs to the poll()
62
 
# call.
63
 
INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
64
 
OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
65
 
 
66
 
def _our_mainquit():
67
 
    # XXX: gtk.main_quit() (which is used for crash()) raises an exception if
68
 
    # gtk.main_level() == 0; however, all the tests freeze if we use this
69
 
    # function to stop the reactor.  what gives?  (I believe this may have been
70
 
    # a stupid mistake where I forgot to import gtk here... I will remove this
71
 
    # comment if the tests pass)
72
 
    import gtk
73
 
    if gtk.main_level():
74
 
        gtk.main_quit()
75
 
 
76
 
class Gtk2Reactor(posixbase.PosixReactorBase):
77
 
    """GTK+-2 event loop reactor.
78
 
    """
79
 
 
80
 
    implements(IReactorFDSet)
81
 
 
82
 
    def __init__(self, useGtk=True):
83
 
        self.context = gobject.main_context_default()
84
 
        self.loop = gobject.MainLoop()
85
 
        posixbase.PosixReactorBase.__init__(self)
86
 
        # pre 2.3.91 the glib iteration and mainloop functions didn't release
87
 
        # global interpreter lock, thus breaking thread and signal support.
88
 
        if (hasattr(gobject, "pygtk_version") and gobject.pygtk_version >= (2, 3, 91)
89
 
            and not useGtk):
90
 
            self.__pending = self.context.pending
91
 
            self.__iteration = self.context.iteration
92
 
            self.__crash = self.loop.quit
93
 
            self.__run = self.loop.run
94
 
        else:
95
 
            import gtk
96
 
            self.__pending = gtk.events_pending
97
 
            self.__iteration = gtk.main_iteration
98
 
            self.__crash = _our_mainquit
99
 
            self.__run = gtk.main
100
 
 
101
 
    # The input_add function in pygtk1 checks for objects with a
102
 
    # 'fileno' method and, if present, uses the result of that method
103
 
    # as the input source. The pygtk2 input_add does not do this. The
104
 
    # function below replicates the pygtk1 functionality.
105
 
 
106
 
    # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
107
 
    # g_io_add_watch() takes different condition bitfields than
108
 
    # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
109
 
    # bug.
110
 
    def input_add(self, source, condition, callback):
111
 
        if hasattr(source, 'fileno'):
112
 
            # handle python objects
113
 
            def wrapper(source, condition, real_s=source, real_cb=callback):
114
 
                return real_cb(real_s, condition)
115
 
            return gobject.io_add_watch(source.fileno(), condition, wrapper)
116
 
        else:
117
 
            return gobject.io_add_watch(source, condition, callback)
118
 
 
119
 
    def addReader(self, reader):
120
 
        if not hasReader(reader):
121
 
            reads[reader] = self.input_add(reader, INFLAGS, self.callback)
122
 
 
123
 
    def addWriter(self, writer):
124
 
        if not hasWriter(writer):
125
 
            writes[writer] = self.input_add(writer, OUTFLAGS, self.callback)
126
 
 
127
 
    def removeAll(self):
128
 
        return self._removeAll(reads, writes)
129
 
    
130
 
    def removeReader(self, reader):
131
 
        if hasReader(reader):
132
 
            gobject.source_remove(reads[reader])
133
 
            del reads[reader]
134
 
 
135
 
    def removeWriter(self, writer):
136
 
        if hasWriter(writer):
137
 
            gobject.source_remove(writes[writer])
138
 
            del writes[writer]
139
 
 
140
 
    doIterationTimer = None
141
 
 
142
 
    def doIterationTimeout(self, *args):
143
 
        self.doIterationTimer = None
144
 
        return 0 # auto-remove
145
 
    
146
 
    def doIteration(self, delay):
147
 
        # flush some pending events, return if there was something to do
148
 
        # don't use the usual "while self.context.pending(): self.context.iteration()"
149
 
        # idiom because lots of IO (in particular test_tcp's
150
 
        # ProperlyCloseFilesTestCase) can keep us from ever exiting.
151
 
        log.msg(channel='system', event='iteration', reactor=self)
152
 
        if self.__pending():
153
 
            self.__iteration(0)
154
 
            return
155
 
        # nothing to do, must delay
156
 
        if delay == 0:
157
 
            return # shouldn't delay, so just return
158
 
        self.doIterationTimer = gobject.timeout_add(int(delay * 1000),
159
 
                                                self.doIterationTimeout)
160
 
        # This will either wake up from IO or from a timeout.
161
 
        self.__iteration(1) # block
162
 
        # note: with the .simulate timer below, delays > 0.1 will always be
163
 
        # woken up by the .simulate timer
164
 
        if self.doIterationTimer:
165
 
            # if woken by IO, need to cancel the timer
166
 
            gobject.source_remove(self.doIterationTimer)
167
 
            self.doIterationTimer = None
168
 
 
169
 
    def crash(self):
170
 
        self.__crash()
171
 
 
172
 
    def run(self, installSignalHandlers=1):
173
 
        self.startRunning(installSignalHandlers=installSignalHandlers)
174
 
        gobject.timeout_add(0, self.simulate)
175
 
        self.__run()
176
 
    
177
 
    def _doReadOrWrite(self, source, condition, faildict={
178
 
        error.ConnectionDone: failure.Failure(error.ConnectionDone()),
179
 
        error.ConnectionLost: failure.Failure(error.ConnectionLost()),
180
 
        }):
181
 
        why = None
182
 
        didRead = None
183
 
        if condition & POLL_DISCONNECTED and \
184
 
               not (condition & gobject.IO_IN):
185
 
            why = main.CONNECTION_LOST
186
 
        else:
187
 
            try:
188
 
                if condition & gobject.IO_IN:
189
 
                    why = source.doRead()
190
 
                    didRead = source.doRead
191
 
                if not why and condition & gobject.IO_OUT:
192
 
                    # if doRead caused connectionLost, don't call doWrite
193
 
                    # if doRead is doWrite, don't call it again.
194
 
                    if not source.disconnected and source.doWrite != didRead:
195
 
                        why = source.doWrite()
196
 
                        didRead = source.doWrite # if failed it was in write
197
 
            except:
198
 
                why = sys.exc_info()[1]
199
 
                log.msg('Error In %s' % source)
200
 
                log.deferr()
201
 
 
202
 
        if why:
203
 
            self._disconnectSelectable(source, why, didRead == source.doRead)
204
 
    
205
 
    def callback(self, source, condition):
206
 
        log.callWithLogger(source, self._doReadOrWrite, source, condition)
207
 
        self.simulate() # fire Twisted timers
208
 
        return 1 # 1=don't auto-remove the source
209
 
 
210
 
    def simulate(self):
211
 
        """Run simulation loops and reschedule callbacks.
212
 
        """
213
 
        global _simtag
214
 
        if _simtag is not None:
215
 
            gobject.source_remove(_simtag)
216
 
        self.runUntilCurrent()
217
 
        timeout = min(self.timeout(), 0.1)
218
 
        if timeout is None:
219
 
            timeout = 0.1
220
 
        # grumble
221
 
        _simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
222
 
 
223
 
 
224
 
class PortableGtkReactor(selectreactor.SelectReactor):
225
 
    """Reactor that works on Windows.
226
 
 
227
 
    input_add is not supported on GTK+ for Win32, apparently.
228
 
    """
229
 
 
230
 
    def crash(self):
231
 
        import gtk
232
 
        # mainquit is deprecated in newer versions
233
 
        if hasattr(gtk, 'main_quit'):
234
 
            gtk.main_quit()
235
 
        else:
236
 
            gtk.mainquit()
237
 
 
238
 
    def run(self, installSignalHandlers=1):
239
 
        import gtk
240
 
        self.startRunning(installSignalHandlers=installSignalHandlers)
241
 
        self.simulate()
242
 
        # mainloop is deprecated in newer versions
243
 
        if hasattr(gtk, 'main'):
244
 
            gtk.main()
245
 
        else:
246
 
            gtk.mainloop()
247
 
 
248
 
    def simulate(self):
249
 
        """Run simulation loops and reschedule callbacks.
250
 
        """
251
 
        global _simtag
252
 
        if _simtag is not None:
253
 
            gobject.source_remove(_simtag)
254
 
        self.iterate()
255
 
        timeout = min(self.timeout(), 0.1)
256
 
        if timeout is None:
257
 
            timeout = 0.1
258
 
        # grumble
259
 
        _simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
260
 
 
261
 
 
262
 
def install(useGtk=True):
263
 
    """Configure the twisted mainloop to be run inside the gtk mainloop.
264
 
 
265
 
    @param useGtk: should glib rather than GTK+ event loop be
266
 
    used (this will be slightly faster but does not support GUI).
267
 
    """
268
 
    reactor = Gtk2Reactor(useGtk)
269
 
    from twisted.internet.main import installReactor
270
 
    installReactor(reactor)
271
 
    return reactor
272
 
 
273
 
def portableInstall(useGtk=True):
274
 
    """Configure the twisted mainloop to be run inside the gtk mainloop.
275
 
    """
276
 
    reactor = PortableGtkReactor()
277
 
    from twisted.internet.main import installReactor
278
 
    installReactor(reactor)
279
 
    return reactor
280
 
 
281
 
if runtime.platform.getType() != 'posix':
282
 
    install = portableInstall