Skip to content

Commit d81f266

Browse files
committed
Create Tutorial (10% complete, cztomczak#256) and simplify hello_world.py (cztomczak#286).
Update qt.py example. Update compile.py, check Cython version by importing module. Fix tools/toc.py for Python 3.
1 parent 24418ec commit d81f266

File tree

7 files changed

+139
-38
lines changed

7 files changed

+139
-38
lines changed

docs/Tutorial.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Tutorial (STILL A WORK IN PROGRESS.. #256)
2+
3+
This tutorial is for v50+ versions of CEF Python, which are currently
4+
available only for Linux.
5+
6+
7+
Table of contents:
8+
* [Install and download examples](#install-and-download-examples)
9+
* [Hello world](#hello-world)
10+
* [CEF's multiprocess architecture](#cefs-multiprocess-architecture)
11+
* [Handling Python exceptions](#handling-python-exceptions)
12+
13+
14+
## Install and download examples
15+
16+
The easy way to install CEF Python is through PyPI, using the pip tool,
17+
which is bundled with all recent versions of Python. On Linux pip 8.1+
18+
is required. To check version and install CEF Python type:
19+
20+
```
21+
pip --version
22+
pip install cefpython3
23+
```
24+
25+
Alternatively you can download the setup package from
26+
[GitHub Releases](../../../releases) and install it by following
27+
the instructions in README.txt.
28+
29+
Now let's download examples by cloning the GitHub repository. After
30+
that, enter the "cefpython/examples/" directory. In that directory
31+
you will find all the examples from this Tutorial, their names
32+
start with a "tutorial_" prefix, except for the hello world example
33+
which is just named "hello_world.py".
34+
35+
```
36+
git clone https://github.com/cztomczak/cefpython.git
37+
cd cefpython/examples/
38+
```
39+
40+
41+
## Hello world
42+
43+
The [hello_world.py](../../../examples/hello_world.py) example is the
44+
most basic example. It doesn't depend on any third party GUI frameworks.
45+
It creates a browser widget which doesn't provide any window information
46+
(parent window not specified), thus CEF automatically takes care of creating
47+
a top-level window for us, and in that window a Chromium widget is embedded.
48+
When creating the browser, an "url" parameter is specified, which causes the
49+
browser to initially navigate to the Google website. Let's explain the code
50+
from this example:
51+
52+
1. `from cefpython3 import cefpython as cef` - import the cefpython
53+
module and bind it to a shorter name "cef"
54+
2. `sys.excepthook = cef.ExceptHook` - overwrite Python's default
55+
exception handler so that all CEF processes are terminated when
56+
Python exception occurs. To understand this better read the
57+
"CEF's multiprocess architecture" and "Handling Python exceptions"
58+
sections further down in this Tutorial.
59+
3. `cef.Initialize()` - Initialize CEF. This function must be called
60+
somewhere in the beginning of your code. It must be called before
61+
any app window is created. It must be called only once during app's
62+
lifetime and must have a corresponding Shutdown() call.
63+
4. `cef.CreateBrowserSync(url="https://www.google.com/")` - Create
64+
a browser synchronously, this function returns the Browser object.
65+
5. `cef.MessageLoop()` - Run CEF message loop. All desktop GUI programs
66+
run a message loop that waits and dispatches events or messages.
67+
6. `cef.Shutdown()` - Shut down CEF. This function must be called for
68+
CEF to shut down cleanly. It will free CEF system resources, it
69+
will terminate all subprocesses, and it will flush to disk any
70+
yet unsaved data like for example cookies and other data. Call this
71+
function at the very end of your program execution. When using third
72+
party GUI frameworks such as Qt/wxWidgets, CEF should be shut down
73+
after these frameworks' shutdown procedures were called. For example
74+
in Qt, shut down CEF only after QApplication object was destroyed.
75+
76+
Documentation for the functions from this example can be found in
77+
API docs (the api/ directory in GitHub's repository):
78+
79+
* [ExceptHook](../../../api/cefpython.md#excepthook)
80+
* [Initialize()](../../../api/cefpython.md#initialize)
81+
* [CreateBrowserSync()](../../../api/cefpython.md#createbrowsersync)
82+
* [Browser](../../../api/Browser.md) object
83+
* [MessageLoop()](../../../api/cefpython.md#messageloop)
84+
* [Shutdown()](../../../api/cefpython.md#shutdown)
85+
86+
87+
## CEF's multiprocess architecture
88+
89+
...
90+
91+
92+
## Handling Python exceptions
93+
94+
...

examples/hello_world.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,23 @@
11
# Hello world example. Doesn't depend on any third party GUI framework.
2-
# Tested with CEF Python v53.1+, only on Linux.
2+
# Tested with CEF Python v55.3+, only on Linux.
33

44
from cefpython3 import cefpython as cef
55
import sys
66

77

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

1916

20-
class ClientHandler(object):
21-
22-
def OnBeforeClose(self, browser):
23-
"""Called just before a browser is destroyed."""
24-
if not browser.IsPopup():
25-
# Exit app when main window is closed.
26-
cef.QuitMessageLoop()
17+
def check_versions():
18+
print("[hello_world.py] CEF Python {ver}".format(ver=cef.__version__))
19+
print("[hello_world.py] Python {ver}".format(ver=sys.version[:6]))
20+
assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this"
2721

2822

2923
if __name__ == '__main__':

examples/qt.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
# and CEF Python v55.2+, only on Linux.
66
#
77
# Known issue on Linux: Keyboard focus sometimes doesn't work, type cursor
8-
# is blinking, but you can' type anything. In such
8+
# is blinking, but you can' type anything. It seems
9+
# to happen only during initial loading. In such
910
# case clicking on url and then back inside browser
1011
# fixes it. There are multiple keyboard focus
1112
# issues in upstream CEF, see Issue #284 for details.
@@ -81,13 +82,12 @@ def __init__(self):
8182
super(MainWindow, self).__init__(None)
8283
self.cef_widget = None
8384
self.navigation_bar = None
84-
self.setupLayout()
85-
# Title
8685
if "pyqt" in sys.argv:
8786
self.setWindowTitle("PyQt example")
8887
elif "pyside" in sys.argv:
8988
self.setWindowTitle("PySide example")
9089
self.setFocusPolicy(Qt.StrongFocus)
90+
self.setupLayout()
9191

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

107-
def setupNavbar(self):
108-
QLineEdit("Test")
109-
110107
def focusInEvent(self, event):
108+
# This event seems to never get called, as CEF is stealing all
109+
# focus due to Issue #284.
111110
if WINDOWS:
112111
# noinspection PyUnresolvedReferences
113112
cef.WindowUtils.OnSetFocus(int(self.centralWidget().winId()),
@@ -117,6 +116,8 @@ def focusInEvent(self, event):
117116
self.cef_widget.browser.SetFocus(True)
118117

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

122123
def closeEvent(self, event):
@@ -193,6 +194,7 @@ def updateState(self):
193194
self.forward.setEnabled(browser.CanGoForward())
194195
self.reload.setEnabled(True)
195196
self.url.setEnabled(True)
197+
self.url.setText(browser.GetUrl())
196198

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

300302

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

305308
def __init__(self, cef_widget):
306309
self.cef_widget = cef_widget

src/cefpython.pyx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ g_commandLineSwitches = {}
447447

448448
cdef scoped_ptr[MainMessageLoopExternalPump] g_external_message_pump
449449

450-
# noinspection PyUnresolvedReferences
451-
cdef cpp_bool _MessageLoopWork_wasused = False
450+
cdef py_bool g_MessageLoop_called = False
451+
cdef py_bool g_MessageLoopWork_called = False
452452

453453
cdef dict g_globalClientCallbacks = {}
454454

@@ -821,6 +821,11 @@ def CreateBrowserSync(windowInfo=None,
821821

822822
def MessageLoop():
823823
Debug("MessageLoop()")
824+
825+
if not g_MessageLoop_called:
826+
global g_MessageLoop_called
827+
g_MessageLoop_called = True
828+
824829
with nogil:
825830
CefRunMessageLoop()
826831

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

838-
if not _MessageLoopWork_wasused:
839-
global _MessageLoopWork_wasused
840-
_MessageLoopWork_wasused = True
843+
if not g_MessageLoopWork_called:
844+
global g_MessageLoopWork_called
845+
g_MessageLoopWork_called = True
841846

842847
with nogil:
843848
CefDoMessageLoopWork()

src/handlers/lifespan_handler.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Website: http://code.google.com/p/cefpython/
44

55
include "../cefpython.pyx"
6+
include "../browser.pyx"
67

78
# noinspection PyUnresolvedReferences
89
from cef_types cimport WindowOpenDisposition
@@ -110,6 +111,9 @@ cdef public void LifespanHandler_OnBeforeClose(
110111
RemovePythonCallbacksForBrowser(pyBrowser.GetIdentifier())
111112
RemovePyFramesForBrowser(pyBrowser.GetIdentifier())
112113
RemovePyBrowser(pyBrowser.GetIdentifier())
114+
if g_MessageLoop_called and not len(g_pyBrowsers):
115+
QuitMessageLoop()
116+
113117
except:
114118
(exc_type, exc_value, exc_trace) = sys.exc_info()
115119
sys.excepthook(exc_type, exc_value, exc_trace)

src/linux/compile.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,26 @@
3535

3636

3737
def check_cython_version():
38-
output = subprocess.check_output(["cython", "--version"],
39-
stderr=subprocess.STDOUT)
40-
output = output.strip()
41-
if not isinstance(output, str):
42-
output = output.decode("utf-8")
43-
match = re.search(r"[\d+.]+", output)
44-
assert match, "Checking Cython version failed"
45-
version = match.group(0)
4638
with open("../../tools/requirements.txt", "r") as fileobj:
4739
contents = fileobj.read()
4840
match = re.search(r"cython\s*==\s*([\d.]+)", contents,
4941
flags=re.IGNORECASE)
5042
assert match, "cython package not found in requirements.txt"
5143
require_version = match.group(1)
44+
try:
45+
import Cython
46+
version = Cython.__version__
47+
except ImportError:
48+
# noinspection PyUnusedLocal
49+
Cython = None
50+
print("ERROR: Cython is not installed ({0} required)"
51+
.format(require_version))
52+
sys.exit(1)
5253
if version != require_version:
53-
print("ERROR: Wrong Cython version: {}. Required: {}"
54+
print("ERROR: Wrong Cython version: {0}. Required: {1}"
5455
.format(version, require_version))
5556
sys.exit(1)
56-
print("Cython version: {}".format(version))
57+
print("Cython version: {0}".format(version))
5758

5859
check_cython_version()
5960

tools/toc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ def toc_file(file_):
4444
"""A single file was passed to doctoc. Return bool whether modified
4545
and the number of warnings."""
4646
with open(file_, "rb") as fo:
47-
orig_contents = fo.read()
47+
orig_contents = fo.read().decode("utf-8", "ignore")
4848
# Fix new lines just in case. Not using Python's "rU",
4949
# it is causing strange issues.
5050
orig_contents = re.sub(r"(\r\n|\r|\n)", os.linesep, orig_contents)
5151
(tocsize, contents, warnings) = create_toc(orig_contents, file_)
5252
if contents != orig_contents:
5353
with open(file_, "wb") as fo:
54-
fo.write(contents)
54+
fo.write(contents.encode("utf-8"))
5555
tocsize_str = ("TOC size: "+str(tocsize) if tocsize
5656
else "TOC removed")
5757
print("Modified: "+file_+" ("+tocsize_str+")")

0 commit comments

Comments
 (0)