Skip to content

Commit 039a461

Browse files
committed
Fix errors in tkinter_.py example on Mac (cztomczak#306, cztomczak#308).
Fix tkinter error on Mac "unrecognized selector sent to instance" (Issue cztomczak#306"). CEF must be initialized after Tk. Fix error with winfo_id() returning a negative value (Issue cztomczak#308). This was resolved by obtaining NSView pointer using PyObjC package. There is yet another error "Segmentation fault: 11" which crashes app often, however it's hard to debug it (Issue cztomczak#309).
1 parent e906a98 commit 039a461

File tree

3 files changed

+135
-106
lines changed

3 files changed

+135
-106
lines changed

docs/Knowledge-Base.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ settings:
6969
```
7070
cefpython_package/
7171
cefpython_py27.so
72-
rpath=@loader_path
72+
rpath=@loader_path/
7373
load:@rpath/Chromium Embedded Framework.framework/Chromium Embedded Framework
7474
Chromium Embedded Framework.framework/
7575
Chromium Embedded Framework
@@ -117,7 +117,8 @@ release perform the following steps:
117117
(http://opensource.spotify.com/cefbuilds/index.html)
118118
and download latest CEF for your platform. Choose "Standard
119119
Distribution" binaries.
120-
3. Follow the instructions in `CMakeLists.txt` file
120+
3. Follow the instructions in `CMakeLists.txt` file, choose steps
121+
for building using Ninja
121122
4. Run either cefclient or cefsimple to test features
122123

123124

examples/tkinter_.py

Lines changed: 129 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# Example of embedding CEF Python browser using Tkinter toolkit.
22
# This example has two widgets: a navigation bar and a browser.
33
#
4-
# NOTE: This example doesn't work on Mac, details in Issue #306.
4+
# NOTE: This example often crashes on Mac (Tk 8.5) during initial
5+
# app loading with such message: "Segmentation fault: 11".
6+
# Reported as Issue #309.
57
#
68
# Tested configurations:
7-
# - Tk 8.5 on Windows
9+
# - Tk 8.5 on Windows/Mac
810
# - Tk 8.6 on Linux
911
# - CEF Python v55.3+
1012
#
@@ -32,8 +34,10 @@
3234

3335
# Globals
3436
logger = _logging.getLogger("tkinter_.py")
35-
# Python 2.7 on Windows comes with Tk 8.5 which doesn't support PNG images
36-
IMAGE_EXT = ".gif" if WINDOWS else ".png"
37+
38+
# Constants
39+
# Tk 8.5 doesn't support png images
40+
IMAGE_EXT = ".png" if tk.TkVersion > 8.5 else ".gif"
3741

3842

3943
def main():
@@ -47,25 +51,27 @@ def main():
4751
logger.info("Tk {ver}".format(ver=tk.Tcl().eval('info patchlevel')))
4852
assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"
4953
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
54+
root = tk.Tk()
55+
app = MainFrame(root)
56+
# Tk must be initialized before CEF otherwise fatal error (Issue #306)
5057
cef.Initialize()
51-
app = MainFrame(tk.Tk())
5258
app.mainloop()
5359
cef.Shutdown()
5460

5561

5662
class MainFrame(tk.Frame):
5763

58-
def __init__(self, master):
64+
def __init__(self, root):
5965
self.browser_frame = None
6066
self.navigation_bar = None
6167

6268
# Root
63-
master.geometry("800x600")
64-
tk.Grid.rowconfigure(master, 0, weight=1)
65-
tk.Grid.columnconfigure(master, 0, weight=1)
69+
root.geometry("800x600")
70+
tk.Grid.rowconfigure(root, 0, weight=1)
71+
tk.Grid.columnconfigure(root, 0, weight=1)
6672

6773
# MainFrame
68-
tk.Frame.__init__(self, master)
74+
tk.Frame.__init__(self, root)
6975
self.master.title("Tkinter example")
7076
self.master.protocol("WM_DELETE_WINDOW", self.on_close)
7177
self.master.bind("<Configure>", self.on_root_configure)
@@ -135,6 +141,119 @@ def setup_icon(self):
135141
self.master.call("wm", "iconphoto", self.master._w, self.icon)
136142

137143

144+
class BrowserFrame(tk.Frame):
145+
146+
def __init__(self, master, navigation_bar=None):
147+
self.navigation_bar = navigation_bar
148+
self.closing = False
149+
self.browser = None
150+
tk.Frame.__init__(self, master)
151+
self.bind("<FocusIn>", self.on_focus_in)
152+
self.bind("<FocusOut>", self.on_focus_out)
153+
self.bind("<Configure>", self.on_configure)
154+
self.focus_set()
155+
156+
def embed_browser(self):
157+
window_info = cef.WindowInfo()
158+
rect = [0, 0, self.winfo_width(), self.winfo_height()]
159+
window_info.SetAsChild(self.get_window_handle(), rect)
160+
self.browser = cef.CreateBrowserSync(window_info,
161+
url="https://www.google.com/")
162+
assert self.browser
163+
self.browser.SetClientHandler(LoadHandler(self))
164+
self.browser.SetClientHandler(FocusHandler(self))
165+
self.message_loop_work()
166+
167+
def get_window_handle(self):
168+
if self.winfo_id() > 0:
169+
return self.winfo_id()
170+
elif MAC:
171+
# On Mac window id is an invalid negative value (Issue #308).
172+
# This is kind of a dirty hack to get window handle using
173+
# PyObjC package. If you change structure of windows then you
174+
# need to do modifications here as well.
175+
# noinspection PyUnresolvedReferences
176+
from AppKit import NSApp
177+
# noinspection PyUnresolvedReferences
178+
import objc
179+
# Sometimes there is more than one window, when application
180+
# didn't close cleanly last time Python displays an NSAlert
181+
# window asking whether to Reopen that window.
182+
# noinspection PyUnresolvedReferences
183+
return objc.pyobjc_id(NSApp.windows()[-1].contentView())
184+
else:
185+
raise Exception("Couldn't obtain window handle")
186+
187+
def message_loop_work(self):
188+
cef.MessageLoopWork()
189+
self.after(10, self.message_loop_work)
190+
191+
def on_configure(self, _):
192+
if not self.browser:
193+
self.embed_browser()
194+
195+
def on_root_configure(self):
196+
# Root <Configure> event will be called when top window is moved
197+
if self.browser:
198+
self.browser.NotifyMoveOrResizeStarted()
199+
200+
def on_mainframe_configure(self, width, height):
201+
if self.browser:
202+
if WINDOWS:
203+
WindowUtils.OnSize(self.get_window_handle(), 0, 0, 0)
204+
elif LINUX:
205+
self.browser.SetBounds(0, 0, width, height)
206+
self.browser.NotifyMoveOrResizeStarted()
207+
208+
def on_focus_in(self, _):
209+
logger.debug("BrowserFrame.on_focus_in")
210+
if self.browser:
211+
self.browser.SetFocus(True)
212+
213+
def on_focus_out(self, _):
214+
logger.debug("BrowserFrame.on_focus_out")
215+
if self.browser:
216+
self.browser.SetFocus(False)
217+
218+
def on_root_close(self):
219+
if self.browser:
220+
# Close browser and free reference by setting to None
221+
self.browser.CloseBrowser(True)
222+
self.browser = None
223+
self.destroy()
224+
225+
226+
class LoadHandler(object):
227+
228+
def __init__(self, browser_frame):
229+
self.browser_frame = browser_frame
230+
231+
def OnLoadStart(self, browser, **_):
232+
if self.browser_frame.master.navigation_bar:
233+
self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())
234+
235+
236+
class FocusHandler(object):
237+
238+
def __init__(self, browser_frame):
239+
self.browser_frame = browser_frame
240+
241+
def OnTakeFocus(self, next_component, **_):
242+
logger.debug("FocusHandler.OnTakeFocus, next={next}"
243+
.format(next=next_component))
244+
245+
def OnSetFocus(self, source, **_):
246+
logger.debug("FocusHandler.OnSetFocus, source={source}"
247+
.format(source=source))
248+
return False
249+
250+
def OnGotFocus(self, **_):
251+
"""Fix CEF focus issues (#255). Call browser frame's focus_set
252+
to get rid of type cursor in url entry widget."""
253+
logger.debug("FocusHandler.OnGotFocus")
254+
self.browser_frame.focus_set()
255+
256+
138257
class NavigationBar(tk.Frame):
139258

140259
def __init__(self, master):
@@ -254,97 +373,5 @@ def __init__(self):
254373
# TODO: implement tabs
255374

256375

257-
class BrowserFrame(tk.Frame):
258-
259-
def __init__(self, master, navigation_bar=None):
260-
self.navigation_bar = navigation_bar
261-
self.closing = False
262-
self.browser = None
263-
tk.Frame.__init__(self, master)
264-
self.bind("<FocusIn>", self.on_focus_in)
265-
self.bind("<FocusOut>", self.on_focus_out)
266-
self.bind("<Configure>", self.on_configure)
267-
self.focus_set()
268-
269-
def embed_browser(self):
270-
window_info = cef.WindowInfo()
271-
window_info.SetAsChild(self.winfo_id())
272-
self.browser = cef.CreateBrowserSync(window_info,
273-
url="https://www.google.com/")
274-
assert self.browser
275-
self.browser.SetClientHandler(LoadHandler(self))
276-
self.browser.SetClientHandler(FocusHandler(self))
277-
self.message_loop_work()
278-
279-
def message_loop_work(self):
280-
cef.MessageLoopWork()
281-
self.after(10, self.message_loop_work)
282-
283-
def on_configure(self, _):
284-
if not self.browser:
285-
self.embed_browser()
286-
287-
def on_root_configure(self):
288-
# Root <Configure> event will be called when top window is moved
289-
if self.browser:
290-
self.browser.NotifyMoveOrResizeStarted()
291-
292-
def on_mainframe_configure(self, width, height):
293-
if self.browser:
294-
if WINDOWS:
295-
WindowUtils.OnSize(self.winfo_id(), 0, 0, 0)
296-
elif LINUX:
297-
self.browser.SetBounds(0, 0, width, height)
298-
self.browser.NotifyMoveOrResizeStarted()
299-
300-
def on_focus_in(self, _):
301-
logger.debug("BrowserFrame.on_focus_in")
302-
if self.browser:
303-
self.browser.SetFocus(True)
304-
305-
def on_focus_out(self, _):
306-
logger.debug("BrowserFrame.on_focus_out")
307-
if self.browser:
308-
self.browser.SetFocus(False)
309-
310-
def on_root_close(self):
311-
if self.browser:
312-
# Close browser and free reference by setting to None
313-
self.browser.CloseBrowser(True)
314-
self.browser = None
315-
self.destroy()
316-
317-
318-
class LoadHandler(object):
319-
320-
def __init__(self, browser_frame):
321-
self.browser_frame = browser_frame
322-
323-
def OnLoadStart(self, browser, **_):
324-
if self.browser_frame.master.navigation_bar:
325-
self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())
326-
327-
328-
class FocusHandler(object):
329-
330-
def __init__(self, browser_frame):
331-
self.browser_frame = browser_frame
332-
333-
def OnTakeFocus(self, next_component, **_):
334-
logger.debug("FocusHandler.OnTakeFocus, next={next}"
335-
.format(next=next_component))
336-
337-
def OnSetFocus(self, source, **_):
338-
logger.debug("FocusHandler.OnSetFocus, source={source}"
339-
.format(source=source))
340-
return False
341-
342-
def OnGotFocus(self, **_):
343-
"""Fix CEF focus issues (#255). Call browser frame's focus_set
344-
to get rid of type cursor in url entry widget."""
345-
logger.debug("FocusHandler.OnGotFocus")
346-
self.browser_frame.focus_set()
347-
348-
349376
if __name__ == '__main__':
350377
main()

tools/run_examples.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ def main():
6565
print("[run_examples.py] PASS: qt.py pyside (PySide not installed)")
6666
passed.append("qt.py pyside")
6767

68-
# tkinter
69-
if packages["tkinter"] or packages["Tkinter"]:
68+
# tkinter.
69+
# This example often crashes on Mac (Issue #309), so don't run it.
70+
if not MAC and (packages["tkinter"] or packages["Tkinter"]):
7071
examples.append("tkinter_.py")
7172
else:
7273
print(["run_examples.py] PASS: tkinter_.py (tkinter not installed)"])

0 commit comments

Comments
 (0)