Skip to content

Commit

Permalink
Fix errors in tkinter_.py example on Mac (#306, #308).
Browse files Browse the repository at this point in the history
Fix tkinter error on Mac "unrecognized selector sent to instance"
(Issue #306"). CEF must be initialized after Tk.

Fix error with winfo_id() returning a negative value (Issue #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 #309).
  • Loading branch information
cztomczak committed Mar 1, 2017
1 parent e906a98 commit 039a461
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 106 deletions.
5 changes: 3 additions & 2 deletions docs/Knowledge-Base.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ settings:
```
cefpython_package/
cefpython_py27.so
rpath=@loader_path
rpath=@loader_path/
load:@rpath/Chromium Embedded Framework.framework/Chromium Embedded Framework
Chromium Embedded Framework.framework/
Chromium Embedded Framework
Expand Down Expand Up @@ -117,7 +117,8 @@ release perform the following steps:
(http://opensource.spotify.com/cefbuilds/index.html)
and download latest CEF for your platform. Choose "Standard
Distribution" binaries.
3. Follow the instructions in `CMakeLists.txt` file
3. Follow the instructions in `CMakeLists.txt` file, choose steps
for building using Ninja
4. Run either cefclient or cefsimple to test features


Expand Down
231 changes: 129 additions & 102 deletions examples/tkinter_.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Example of embedding CEF Python browser using Tkinter toolkit.
# This example has two widgets: a navigation bar and a browser.
#
# NOTE: This example doesn't work on Mac, details in Issue #306.
# NOTE: This example often crashes on Mac (Tk 8.5) during initial
# app loading with such message: "Segmentation fault: 11".
# Reported as Issue #309.
#
# Tested configurations:
# - Tk 8.5 on Windows
# - Tk 8.5 on Windows/Mac
# - Tk 8.6 on Linux
# - CEF Python v55.3+
#
Expand Down Expand Up @@ -32,8 +34,10 @@

# Globals
logger = _logging.getLogger("tkinter_.py")
# Python 2.7 on Windows comes with Tk 8.5 which doesn't support PNG images
IMAGE_EXT = ".gif" if WINDOWS else ".png"

# Constants
# Tk 8.5 doesn't support png images
IMAGE_EXT = ".png" if tk.TkVersion > 8.5 else ".gif"


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


class MainFrame(tk.Frame):

def __init__(self, master):
def __init__(self, root):
self.browser_frame = None
self.navigation_bar = None

# Root
master.geometry("800x600")
tk.Grid.rowconfigure(master, 0, weight=1)
tk.Grid.columnconfigure(master, 0, weight=1)
root.geometry("800x600")
tk.Grid.rowconfigure(root, 0, weight=1)
tk.Grid.columnconfigure(root, 0, weight=1)

# MainFrame
tk.Frame.__init__(self, master)
tk.Frame.__init__(self, root)
self.master.title("Tkinter example")
self.master.protocol("WM_DELETE_WINDOW", self.on_close)
self.master.bind("<Configure>", self.on_root_configure)
Expand Down Expand Up @@ -135,6 +141,119 @@ def setup_icon(self):
self.master.call("wm", "iconphoto", self.master._w, self.icon)


class BrowserFrame(tk.Frame):

def __init__(self, master, navigation_bar=None):
self.navigation_bar = navigation_bar
self.closing = False
self.browser = None
tk.Frame.__init__(self, master)
self.bind("<FocusIn>", self.on_focus_in)
self.bind("<FocusOut>", self.on_focus_out)
self.bind("<Configure>", self.on_configure)
self.focus_set()

def embed_browser(self):
window_info = cef.WindowInfo()
rect = [0, 0, self.winfo_width(), self.winfo_height()]
window_info.SetAsChild(self.get_window_handle(), rect)
self.browser = cef.CreateBrowserSync(window_info,
url="https://www.google.com/")
assert self.browser
self.browser.SetClientHandler(LoadHandler(self))
self.browser.SetClientHandler(FocusHandler(self))
self.message_loop_work()

def get_window_handle(self):
if self.winfo_id() > 0:
return self.winfo_id()
elif MAC:
# On Mac window id is an invalid negative value (Issue #308).
# This is kind of a dirty hack to get window handle using
# PyObjC package. If you change structure of windows then you
# need to do modifications here as well.
# noinspection PyUnresolvedReferences
from AppKit import NSApp
# noinspection PyUnresolvedReferences
import objc
# Sometimes there is more than one window, when application
# didn't close cleanly last time Python displays an NSAlert
# window asking whether to Reopen that window.
# noinspection PyUnresolvedReferences
return objc.pyobjc_id(NSApp.windows()[-1].contentView())
else:
raise Exception("Couldn't obtain window handle")

def message_loop_work(self):
cef.MessageLoopWork()
self.after(10, self.message_loop_work)

def on_configure(self, _):
if not self.browser:
self.embed_browser()

def on_root_configure(self):
# Root <Configure> event will be called when top window is moved
if self.browser:
self.browser.NotifyMoveOrResizeStarted()

def on_mainframe_configure(self, width, height):
if self.browser:
if WINDOWS:
WindowUtils.OnSize(self.get_window_handle(), 0, 0, 0)
elif LINUX:
self.browser.SetBounds(0, 0, width, height)
self.browser.NotifyMoveOrResizeStarted()

def on_focus_in(self, _):
logger.debug("BrowserFrame.on_focus_in")
if self.browser:
self.browser.SetFocus(True)

def on_focus_out(self, _):
logger.debug("BrowserFrame.on_focus_out")
if self.browser:
self.browser.SetFocus(False)

def on_root_close(self):
if self.browser:
# Close browser and free reference by setting to None
self.browser.CloseBrowser(True)
self.browser = None
self.destroy()


class LoadHandler(object):

def __init__(self, browser_frame):
self.browser_frame = browser_frame

def OnLoadStart(self, browser, **_):
if self.browser_frame.master.navigation_bar:
self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())


class FocusHandler(object):

def __init__(self, browser_frame):
self.browser_frame = browser_frame

def OnTakeFocus(self, next_component, **_):
logger.debug("FocusHandler.OnTakeFocus, next={next}"
.format(next=next_component))

def OnSetFocus(self, source, **_):
logger.debug("FocusHandler.OnSetFocus, source={source}"
.format(source=source))
return False

def OnGotFocus(self, **_):
"""Fix CEF focus issues (#255). Call browser frame's focus_set
to get rid of type cursor in url entry widget."""
logger.debug("FocusHandler.OnGotFocus")
self.browser_frame.focus_set()


class NavigationBar(tk.Frame):

def __init__(self, master):
Expand Down Expand Up @@ -254,97 +373,5 @@ def __init__(self):
# TODO: implement tabs


class BrowserFrame(tk.Frame):

def __init__(self, master, navigation_bar=None):
self.navigation_bar = navigation_bar
self.closing = False
self.browser = None
tk.Frame.__init__(self, master)
self.bind("<FocusIn>", self.on_focus_in)
self.bind("<FocusOut>", self.on_focus_out)
self.bind("<Configure>", self.on_configure)
self.focus_set()

def embed_browser(self):
window_info = cef.WindowInfo()
window_info.SetAsChild(self.winfo_id())
self.browser = cef.CreateBrowserSync(window_info,
url="https://www.google.com/")
assert self.browser
self.browser.SetClientHandler(LoadHandler(self))
self.browser.SetClientHandler(FocusHandler(self))
self.message_loop_work()

def message_loop_work(self):
cef.MessageLoopWork()
self.after(10, self.message_loop_work)

def on_configure(self, _):
if not self.browser:
self.embed_browser()

def on_root_configure(self):
# Root <Configure> event will be called when top window is moved
if self.browser:
self.browser.NotifyMoveOrResizeStarted()

def on_mainframe_configure(self, width, height):
if self.browser:
if WINDOWS:
WindowUtils.OnSize(self.winfo_id(), 0, 0, 0)
elif LINUX:
self.browser.SetBounds(0, 0, width, height)
self.browser.NotifyMoveOrResizeStarted()

def on_focus_in(self, _):
logger.debug("BrowserFrame.on_focus_in")
if self.browser:
self.browser.SetFocus(True)

def on_focus_out(self, _):
logger.debug("BrowserFrame.on_focus_out")
if self.browser:
self.browser.SetFocus(False)

def on_root_close(self):
if self.browser:
# Close browser and free reference by setting to None
self.browser.CloseBrowser(True)
self.browser = None
self.destroy()


class LoadHandler(object):

def __init__(self, browser_frame):
self.browser_frame = browser_frame

def OnLoadStart(self, browser, **_):
if self.browser_frame.master.navigation_bar:
self.browser_frame.master.navigation_bar.set_url(browser.GetUrl())


class FocusHandler(object):

def __init__(self, browser_frame):
self.browser_frame = browser_frame

def OnTakeFocus(self, next_component, **_):
logger.debug("FocusHandler.OnTakeFocus, next={next}"
.format(next=next_component))

def OnSetFocus(self, source, **_):
logger.debug("FocusHandler.OnSetFocus, source={source}"
.format(source=source))
return False

def OnGotFocus(self, **_):
"""Fix CEF focus issues (#255). Call browser frame's focus_set
to get rid of type cursor in url entry widget."""
logger.debug("FocusHandler.OnGotFocus")
self.browser_frame.focus_set()


if __name__ == '__main__':
main()
5 changes: 3 additions & 2 deletions tools/run_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ def main():
print("[run_examples.py] PASS: qt.py pyside (PySide not installed)")
passed.append("qt.py pyside")

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

0 comments on commit 039a461

Please sign in to comment.