28
28
# ##############
29
29
__author__ = "Devin Bobadilla"
30
30
# YYYY.MM.DD
31
- __date__ = "2025.01.18"
32
- __version__ = (1 , 0 , 5 )
31
+ __date__ = "2025.02.02"
32
+ __version__ = (1 , 0 , 6 )
33
+
34
+ import os
35
+ ENV_DISABLED = os .environ .get ("THREADSAFE_TKINTER_DISABLE" )
33
36
34
37
try :
35
38
from tkinter import *
36
39
except ImportError :
37
40
from Tkinter import *
41
+
38
42
from queue import Queue as _Queue , Empty as _Empty
39
43
try :
40
44
from threading import currentThread as _curr_thread , _DummyThread
41
45
except ImportError :
42
46
from threading import current_thread as _curr_thread , _DummyThread
43
47
44
48
from types import FunctionType
49
+ from traceback import format_exception
45
50
46
51
TKHOOK_UNHOOKING = - 1
47
52
TKHOOK_UNHOOKED = 0
@@ -66,31 +71,35 @@ def create_queue(self, size=0): return _Queue(size)
66
71
67
72
def __getattr__ (self , attr_name ):
68
73
if self .tk_widget is None or self .tk_widget ._tk is None :
69
- raise AttributeError (
70
- "self.tk_widget is None. Not hooked into a Tk instance." )
74
+ raise AttributeError ("Not hooked into a Tk instance." )
71
75
72
76
tk_attr = getattr (self .tk_widget ._tk , attr_name )
73
- return (lambda * a , _f = tk_attr , ** kw :
74
- self .call_tk_attr_threadsafe (_f , * a , ** kw ))
77
+
78
+ return (lambda * a , _f = tk_attr , _s = self , ** kw :
79
+ _s .call_tk_attr_threadsafe (_f , * a , ** kw ))
75
80
76
81
def call_tk_attr_threadsafe (self , tk_attr , * a , ** kw ):
77
82
thread = self .get_curr_thread ()
83
+
78
84
if thread == self .tk_thread or isinstance (thread , _DummyThread ):
79
85
# it is either safe to call from the thread the tkinter widget
80
86
# is running on, or a dummy thread is running which is also safe
81
87
return tk_attr (* a , ** kw )
82
88
83
89
# add a request to the requests queue to call this attribute
84
- response_queue = self .create_queue (1 )
85
- result , raise_result = None , None
90
+ resp_queue , raise_result = self .create_queue (1 ), False
91
+ result = undefined = object ()
86
92
87
- self .request_queue .put ((response_queue , tk_attr , a , kw ))
88
- while result is None and self .tk_widget is not None :
93
+ self .request_queue .put ((resp_queue , tk_attr , a , kw ))
94
+ while result is undefined and self .tk_widget is not None :
89
95
try :
90
- response = response_queue .get (True , 3 )
96
+ response = resp_queue .get (True , 3 )
91
97
result , raise_result = response
92
98
except _Empty :
93
99
pass
100
+ except ValueError as e :
101
+ if result is undefined :
102
+ result , raise_result = e , True
94
103
95
104
if raise_result :
96
105
raise result
@@ -129,26 +138,26 @@ def process_requests(self):
129
138
# will force the loop to finish processing requests before it ends.
130
139
cleanup = True
131
140
while cleanup or not self .request_queue .empty ():
132
- cleanup = False
141
+ cleanup , resp_queue , result = False , None , None
133
142
try :
134
- response_queue = None
135
143
while (self .tk_widget is not None and
136
144
self ._hook_status != TKHOOK_UNHOOKED ) or cleanup :
137
- response_queue = None
145
+ resp_queue = result = None
138
146
# get the response container, function and args, call
139
147
# the function and place the result into the response
140
- item = self .request_queue .get_nowait ()
141
- response_queue , func , a , kw = item
142
- result = func (* a , ** kw )
143
- response_queue .put ((result , False ))
144
-
148
+ resp_queue , func , a , kw = self .request_queue .get_nowait ()
149
+ result = (func (* a , ** kw ), False )
150
+ break
145
151
except _Empty :
146
152
# nothing to process. break out of loop
147
153
pass
148
- except Exception as e :
149
- if response_queue is not None :
150
- response_queue .put ((e , True ))
154
+ except BaseException as e :
155
+ if resp_queue is None :
156
+ print (format_exception (e ))
157
+ else :
158
+ result = (e , True )
151
159
160
+ result and resp_queue .put (result )
152
161
if self ._hook_status == TKHOOK_UNHOOKING :
153
162
self .tk_widget .tk = self .tk_widget ._tk
154
163
self ._hook_status = TKHOOK_UNHOOKED
@@ -181,13 +190,12 @@ def _tk_destroy_override(self, *a, **kw):
181
190
self .tk_widget .after_cancel (cmd )
182
191
183
192
184
-
185
193
# dont hook twice or we'll end up with an infinite loop
186
- if not hasattr (Tk , "_orig_init" ):
194
+ if not ( hasattr (Tk , "_orig_init" ) or ENV_DISABLED ):
187
195
Tk ._orig_init = Tk .__init__
188
196
Tk .__init__ = _tk_init_override
189
197
190
198
191
- if not hasattr (Tk , "_orig_destroy" ):
199
+ if not ( hasattr (Tk , "_orig_destroy" ) or ENV_DISABLED ):
192
200
Tk ._orig_destroy = Tk .destroy
193
201
Tk .destroy = _tk_destroy_override
0 commit comments