diff --git a/docs/Tutorial.md b/docs/Tutorial.md new file mode 100644 index 00000000..23df5702 --- /dev/null +++ b/docs/Tutorial.md @@ -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 + +... diff --git a/examples/hello_world.py b/examples/hello_world.py index 3da9daf6..5417bcfd 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -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__': diff --git a/examples/qt.py b/examples/qt.py index f187b4cb..3c258c28 100644 --- a/examples/qt.py +++ b/examples/qt.py @@ -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. @@ -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) @@ -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()), @@ -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): @@ -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__)), @@ -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 diff --git a/src/cefpython.pyx b/src/cefpython.pyx index 8c7e1f3e..c44b7fc5 100644 --- a/src/cefpython.pyx +++ b/src/cefpython.pyx @@ -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 = {} @@ -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() @@ -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() diff --git a/src/handlers/lifespan_handler.pyx b/src/handlers/lifespan_handler.pyx index b6fea18e..74039391 100644 --- a/src/handlers/lifespan_handler.pyx +++ b/src/handlers/lifespan_handler.pyx @@ -3,6 +3,7 @@ # Website: http://code.google.com/p/cefpython/ include "../cefpython.pyx" +include "../browser.pyx" # noinspection PyUnresolvedReferences from cef_types cimport WindowOpenDisposition @@ -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) diff --git a/src/linux/compile.py b/src/linux/compile.py index dc1150a8..125c5dda 100644 --- a/src/linux/compile.py +++ b/src/linux/compile.py @@ -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() diff --git a/tools/toc.py b/tools/toc.py index a8451934..a0791e0b 100644 --- a/tools/toc.py +++ b/tools/toc.py @@ -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+")")