Skip to content

Commit

Permalink
Create Tutorial (10% complete, #256) and simplify hello_world.py (#286).
Browse files Browse the repository at this point in the history
Update qt.py example.
Update compile.py, check Cython version by importing module.
Fix tools/toc.py for Python 3.
  • Loading branch information
cztomczak committed Dec 18, 2016
1 parent 24418ec commit d81f266
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 38 deletions.
94 changes: 94 additions & 0 deletions docs/Tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Tutorial (STILL A WORK IN PROGRESS.. #256)

This tutorial is for v50+ versions of CEF Python, which are currently
available only for Linux.


Table of contents:
* [Install and download examples](#install-and-download-examples)
* [Hello world](#hello-world)
* [CEF's multiprocess architecture](#cefs-multiprocess-architecture)
* [Handling Python exceptions](#handling-python-exceptions)


## Install and download examples

The easy way to install CEF Python is through PyPI, using the pip tool,
which is bundled with all recent versions of Python. On Linux pip 8.1+
is required. To check version and install CEF Python type:

```
pip --version
pip install cefpython3
```

Alternatively you can download the setup package from
[GitHub Releases](../../../releases) and install it by following
the instructions in README.txt.

Now let's download examples by cloning the GitHub repository. After
that, enter the "cefpython/examples/" directory. In that directory
you will find all the examples from this Tutorial, their names
start with a "tutorial_" prefix, except for the hello world example
which is just named "hello_world.py".

```
git clone https://github.com/cztomczak/cefpython.git
cd cefpython/examples/
```


## Hello world

The [hello_world.py](../../../examples/hello_world.py) example is the
most basic example. It doesn't depend on any third party GUI frameworks.
It creates a browser widget which doesn't provide any window information
(parent window not specified), thus CEF automatically takes care of creating
a top-level window for us, and in that window a Chromium widget is embedded.
When creating the browser, an "url" parameter is specified, which causes the
browser to initially navigate to the Google website. Let's explain the code
from this example:

1. `from cefpython3 import cefpython as cef` - import the cefpython
module and bind it to a shorter name "cef"
2. `sys.excepthook = cef.ExceptHook` - overwrite Python's default
exception handler so that all CEF processes are terminated when
Python exception occurs. To understand this better read the
"CEF's multiprocess architecture" and "Handling Python exceptions"
sections further down in this Tutorial.
3. `cef.Initialize()` - Initialize CEF. This function must be called
somewhere in the beginning of your code. It must be called before
any app window is created. It must be called only once during app's
lifetime and must have a corresponding Shutdown() call.
4. `cef.CreateBrowserSync(url="https://www.google.com/")` - Create
a browser synchronously, this function returns the Browser object.
5. `cef.MessageLoop()` - Run CEF message loop. All desktop GUI programs
run a message loop that waits and dispatches events or messages.
6. `cef.Shutdown()` - Shut down CEF. This function must be called for
CEF to shut down cleanly. It will free CEF system resources, it
will terminate all subprocesses, and it will flush to disk any
yet unsaved data like for example cookies and other data. Call this
function at the very end of your program execution. When using third
party GUI frameworks such as Qt/wxWidgets, CEF should be shut down
after these frameworks' shutdown procedures were called. For example
in Qt, shut down CEF only after QApplication object was destroyed.

Documentation for the functions from this example can be found in
API docs (the api/ directory in GitHub's repository):

* [ExceptHook](../../../api/cefpython.md#excepthook)
* [Initialize()](../../../api/cefpython.md#initialize)
* [CreateBrowserSync()](../../../api/cefpython.md#createbrowsersync)
* [Browser](../../../api/Browser.md) object
* [MessageLoop()](../../../api/cefpython.md#messageloop)
* [Shutdown()](../../../api/cefpython.md#shutdown)


## CEF's multiprocess architecture

...


## Handling Python exceptions

...
20 changes: 7 additions & 13 deletions examples/hello_world.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
# Hello world example. Doesn't depend on any third party GUI framework.
# Tested with CEF Python v53.1+, only on Linux.
# Tested with CEF Python v55.3+, only on Linux.

from cefpython3 import cefpython as cef
import sys


def main():
print("[hello_world.py] CEF Python {ver}".format(ver=cef.__version__))
print("[hello_world.py] Python {ver}".format(ver=sys.version[:6]))
assert cef.__version__ >= "53.1", "CEF Python v53.1+ required to run this"
check_versions()
sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error
cef.Initialize()
browser = cef.CreateBrowserSync(url="https://www.google.com/")
browser.SetClientHandler(ClientHandler())
cef.CreateBrowserSync(url="https://www.google.com/")
cef.MessageLoop()
cef.Shutdown()


class ClientHandler(object):

def OnBeforeClose(self, browser):
"""Called just before a browser is destroyed."""
if not browser.IsPopup():
# Exit app when main window is closed.
cef.QuitMessageLoop()
def check_versions():
print("[hello_world.py] CEF Python {ver}".format(ver=cef.__version__))
print("[hello_world.py] Python {ver}".format(ver=sys.version[:6]))
assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"


if __name__ == '__main__':
Expand Down
19 changes: 11 additions & 8 deletions examples/qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
# and CEF Python v55.2+, only on Linux.
#
# Known issue on Linux: Keyboard focus sometimes doesn't work, type cursor
# is blinking, but you can' type anything. In such
# is blinking, but you can' type anything. It seems
# to happen only during initial loading. In such
# case clicking on url and then back inside browser
# fixes it. There are multiple keyboard focus
# issues in upstream CEF, see Issue #284 for details.
Expand Down Expand Up @@ -81,13 +82,12 @@ def __init__(self):
super(MainWindow, self).__init__(None)
self.cef_widget = None
self.navigation_bar = None
self.setupLayout()
# Title
if "pyqt" in sys.argv:
self.setWindowTitle("PyQt example")
elif "pyside" in sys.argv:
self.setWindowTitle("PySide example")
self.setFocusPolicy(Qt.StrongFocus)
self.setupLayout()

def setupLayout(self):
self.resize(WIDTH, HEIGHT)
Expand All @@ -104,10 +104,9 @@ def setupLayout(self):
# Browser can be embedded only after layout was set up
self.cef_widget.embedBrowser()

def setupNavbar(self):
QLineEdit("Test")

def focusInEvent(self, event):
# This event seems to never get called, as CEF is stealing all
# focus due to Issue #284.
if WINDOWS:
# noinspection PyUnresolvedReferences
cef.WindowUtils.OnSetFocus(int(self.centralWidget().winId()),
Expand All @@ -117,6 +116,8 @@ def focusInEvent(self, event):
self.cef_widget.browser.SetFocus(True)

def focusOutEvent(self, event):
# This event seems to never get called, as CEF is stealing all
# focus due to Issue #284.
print("[qt.py] focusOutEvent")

def closeEvent(self, event):
Expand Down Expand Up @@ -193,6 +194,7 @@ def updateState(self):
self.forward.setEnabled(browser.CanGoForward())
self.reload.setEnabled(True)
self.url.setEnabled(True)
self.url.setText(browser.GetUrl())

def createButton(self, name):
resources = os.path.join(os.path.abspath(os.path.dirname(__file__)),
Expand Down Expand Up @@ -299,8 +301,9 @@ def OnLoadStart(self, browser, *_):


class FocusHandler(object):
"""FocusHandler must be set for the browser, otherwise keyboard
focus issues occur. If there are still focus issues see Issue #284."""
"""FocusHandler must be set for the browser to partially fix
keyboard focus issues. However it seems there are still some
focus issues, see Issue #284 for more details."""

def __init__(self, cef_widget):
self.cef_widget = cef_widget
Expand Down
15 changes: 10 additions & 5 deletions src/cefpython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,8 @@ g_commandLineSwitches = {}

cdef scoped_ptr[MainMessageLoopExternalPump] g_external_message_pump

# noinspection PyUnresolvedReferences
cdef cpp_bool _MessageLoopWork_wasused = False
cdef py_bool g_MessageLoop_called = False
cdef py_bool g_MessageLoopWork_called = False

cdef dict g_globalClientCallbacks = {}

Expand Down Expand Up @@ -821,6 +821,11 @@ def CreateBrowserSync(windowInfo=None,

def MessageLoop():
Debug("MessageLoop()")

if not g_MessageLoop_called:
global g_MessageLoop_called
g_MessageLoop_called = True

with nogil:
CefRunMessageLoop()

Expand All @@ -835,9 +840,9 @@ def MessageLoopWork():
# GIL must be released here otherwise we will get dead lock
# when calling from c++ to python.

if not _MessageLoopWork_wasused:
global _MessageLoopWork_wasused
_MessageLoopWork_wasused = True
if not g_MessageLoopWork_called:
global g_MessageLoopWork_called
g_MessageLoopWork_called = True

with nogil:
CefDoMessageLoopWork()
Expand Down
4 changes: 4 additions & 0 deletions src/handlers/lifespan_handler.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Website: http://code.google.com/p/cefpython/

include "../cefpython.pyx"
include "../browser.pyx"

# noinspection PyUnresolvedReferences
from cef_types cimport WindowOpenDisposition
Expand Down Expand Up @@ -110,6 +111,9 @@ cdef public void LifespanHandler_OnBeforeClose(
RemovePythonCallbacksForBrowser(pyBrowser.GetIdentifier())
RemovePyFramesForBrowser(pyBrowser.GetIdentifier())
RemovePyBrowser(pyBrowser.GetIdentifier())
if g_MessageLoop_called and not len(g_pyBrowsers):
QuitMessageLoop()

except:
(exc_type, exc_value, exc_trace) = sys.exc_info()
sys.excepthook(exc_type, exc_value, exc_trace)
21 changes: 11 additions & 10 deletions src/linux/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,26 @@


def check_cython_version():
output = subprocess.check_output(["cython", "--version"],
stderr=subprocess.STDOUT)
output = output.strip()
if not isinstance(output, str):
output = output.decode("utf-8")
match = re.search(r"[\d+.]+", output)
assert match, "Checking Cython version failed"
version = match.group(0)
with open("../../tools/requirements.txt", "r") as fileobj:
contents = fileobj.read()
match = re.search(r"cython\s*==\s*([\d.]+)", contents,
flags=re.IGNORECASE)
assert match, "cython package not found in requirements.txt"
require_version = match.group(1)
try:
import Cython
version = Cython.__version__
except ImportError:
# noinspection PyUnusedLocal
Cython = None
print("ERROR: Cython is not installed ({0} required)"
.format(require_version))
sys.exit(1)
if version != require_version:
print("ERROR: Wrong Cython version: {}. Required: {}"
print("ERROR: Wrong Cython version: {0}. Required: {1}"
.format(version, require_version))
sys.exit(1)
print("Cython version: {}".format(version))
print("Cython version: {0}".format(version))

check_cython_version()

Expand Down
4 changes: 2 additions & 2 deletions tools/toc.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ def toc_file(file_):
"""A single file was passed to doctoc. Return bool whether modified
and the number of warnings."""
with open(file_, "rb") as fo:
orig_contents = fo.read()
orig_contents = fo.read().decode("utf-8", "ignore")
# Fix new lines just in case. Not using Python's "rU",
# it is causing strange issues.
orig_contents = re.sub(r"(\r\n|\r|\n)", os.linesep, orig_contents)
(tocsize, contents, warnings) = create_toc(orig_contents, file_)
if contents != orig_contents:
with open(file_, "wb") as fo:
fo.write(contents)
fo.write(contents.encode("utf-8"))
tocsize_str = ("TOC size: "+str(tocsize) if tocsize
else "TOC removed")
print("Modified: "+file_+" ("+tocsize_str+")")
Expand Down

0 comments on commit d81f266

Please sign in to comment.