Skip to content

Commit

Permalink
Add experimental Python 3 support (#121, #183) among others.
Browse files Browse the repository at this point in the history
Add hello_world.py example (#207).

Update to Cython 0.24.1 (#110).

Test with Python 3.4.5 (#121) on Ubuntu 14.04 by running
the hello_world.py example. No other examples were run,
so there still may be bugs.

Expose new funcs in the cefpython module: CreateBrowser, ExceptHook,
GetAppPath.

Add --fast flag to compile.py for faster build time of the cefpython
module. Don't use it for building official binary distrib.
  • Loading branch information
cztomczak committed Sep 17, 2016
1 parent 21549c7 commit 6f0d9b3
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 130 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea/
build/
*.log
5 changes: 2 additions & 3 deletions api/WindowInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ Table of contents:
| Parameter | Type |
| --- | --- |
| parentWindowHandle | int |
| windowRect | list |
| windowRect (optional) | list |
| __Return__ | void |

Create the browser as a child window/view.

`windowRect` param is optional on Windows. On Linux & Mac it is required.
Example value: [left, top, right, bottom].
`windowRect` example value: [left, top, right, bottom].


### SetAsPopup
Expand Down
48 changes: 43 additions & 5 deletions api/cefpython.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ Functions in the cefpython module.

Table of contents:
* [Functions](#functions)
* [CreateBrowser](#createbrowsersync)
* [CreateBrowserSync](#createbrowsersync)
* [ExceptHook](#excepthook)
* [GetAppSetting](#getappsetting)
* [GetAppPath](#getapppath)
* [GetBrowserByWindowHandle](#getbrowserbywindowhandle)
* [GetCommandLineSwitch](#getcommandlineswitch)
* [GetGlobalClientCallback](#getglobalclientcallback)
Expand All @@ -28,17 +31,26 @@ Table of contents:
## Functions


### CreateBrowser

Create browser asynchronously (does not return Browser object).
See `CreateBrowserSync()` for params list.

NOTE: currently this is just an alias and actually creates browser
synchronously. The async call to CefCreateBrowser is yet TODO.


### CreateBrowserSync

| Parameter | Type |
| --- | --- |
| windowInfo | [WindowInfo](WindowInfo.md) |
| [BrowserSettings](BrowserSettings.md) | dict |
| navigateUrl | string |
| requestContext | void |
| window_info | [WindowInfo](WindowInfo.md) |
| [settings](BrowserSettings.md) | [BrowserSettings](BrowserSettings.md) |
| url | string |
| request_context | void |
| __Return__ | [Browser](Browser.md) |

This function should only be called on the UI thread. The 'requestContext' parameter is not yet implemented. You must first create a window and initialize 'windowInfo' by calling WindowInfo.SetAsChild().
This function should only be called on the UI thread. The 'request_context' parameter is not yet implemented. You must first create a window and initialize 'window_info' by calling WindowInfo.SetAsChild().

After the call to CreateBrowserSync() the page is not yet loaded, if you want your next lines of code to do some stuff on the webpage you will have to implement [LoadHandler](LoadHandler.md).OnLoadEnd() callback, see example below:

Expand All @@ -52,6 +64,23 @@ browser = cefpython.CreateBrowserSync(windowInfo, settings, url)
browser.SetClientCallback("OnLoadEnd", OnLoadEnd)
```


### ExceptHook

| Parameter | Type |
| --- | --- |
| excType | - |
| excValue | - |
| traceObject | - |
| __Return__ | string |

Global except hook to exit app cleanly on error.

This hook does the following: in case of exception write it to
the "error.log" file, display it to the console, shutdown CEF
and exit application immediately by ignoring "finally" (_exit())


### GetAppSetting

| Parameter | Type |
Expand All @@ -62,6 +91,15 @@ browser.SetClientCallback("OnLoadEnd", OnLoadEnd)
Returns [ApplicationSettings](ApplicationSettings.md) option that was passed to Initialize(). Returns None if key is not found.


### GetAppPath

| | |
| --- | --- |
| __Return__ | string |

Get path to where application resides.


### GetBrowserByWindowHandle

| Parameter | Type |
Expand Down
25 changes: 25 additions & 0 deletions examples/hello_world.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Hello world example doesn't depend on any third party GUI framework.

from cefpython3 import cefpython as cef
import sys


def main():
"""Main entry point."""
sys.excepthook = cef.ExceptHook
cef.Initialize()
browser = cef.CreateBrowserSync(url="https://www.google.com/")
browser.SetClientHandler(ClientHandler())
cef.MessageLoop()
cef.Shutdown()


class ClientHandler:
"""Client handler."""
def OnBeforeClose(self, browser):
if not browser.IsPopup():
cef.QuitMessageLoop()


if __name__ == '__main__':
main()
58 changes: 49 additions & 9 deletions src/cefpython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ g_debugFile = "debug.log"
# When put None here and assigned a local dictionary in Initialize(), later
# while running app this global variable was garbage collected, see topic:
# https://groups.google.com/d/topic/cython-users/0dw3UASh7HY/discussion
g_applicationSettings = {}
# The string_encoding key must be set early here and also in Initialize.
g_applicationSettings = {"string_encoding": "utf-8"}
g_commandLineSwitches = {}

# noinspection PyUnresolvedReferences
Expand Down Expand Up @@ -504,6 +505,7 @@ include "command_line.pyx"
include "app.pyx"
include "javascript_dialog_handler.pyx"
include "drag_data.pyx"
include "helpers.pyx"

# -----------------------------------------------------------------------------
# Utility functions to provide settings to the C++ browser process code.
Expand All @@ -513,7 +515,7 @@ cdef public void cefpython_GetDebugOptions(
cpp_string* debugFile
) except * with gil:
# Called from subprocess/cefpython_app.cpp -> CefPythonApp constructor.
cdef cpp_string cppString = g_debugFile
cdef cpp_string cppString = PyStringToChar(g_debugFile)
try:
debug[0] = <cpp_bool>bool(g_debug)
debugFile.assign(cppString)
Expand Down Expand Up @@ -547,7 +549,7 @@ cdef public cpp_string ApplicationSettings_GetString(const char* key
cdef py_string pyKey = CharToPyString(key)
cdef cpp_string cppString
if pyKey in g_applicationSettings:
cppString = AnyToPyString(g_applicationSettings[pyKey])
cppString = PyStringToChar(AnyToPyString(g_applicationSettings[pyKey]))
return cppString

cdef public int CommandLineSwitches_GetInt(const char* key) except * with gil:
Expand All @@ -567,11 +569,11 @@ def Initialize(applicationSettings=None, commandLineSwitches=None):
# Fix Issue #231 - Discovery of the "icudtl.dat" file fails on Linux.
# Apply patch for all platforms just in case.
cdef str py_module_dir = GetModuleDirectory()
cdef CefString module_dir
PyToCefString(py_module_dir, module_dir)
CefOverridePath(PK_DIR_EXE, module_dir)\
cdef CefString cef_module_dir
PyToCefString(py_module_dir, cef_module_dir)
CefOverridePath(PK_DIR_EXE, cef_module_dir)\
or Debug("ERROR: CefOverridePath failed")
CefOverridePath(PK_DIR_MODULE, module_dir)\
CefOverridePath(PK_DIR_MODULE, cef_module_dir)\
or Debug("ERROR: CefOverridePath failed")

if not applicationSettings:
Expand Down Expand Up @@ -611,6 +613,20 @@ def Initialize(applicationSettings=None, commandLineSwitches=None):
if DpiAware.IsProcessDpiAware():
applicationSettings["auto_zooming"] = "system_dpi"

# Paths
cdef str module_dir = GetModuleDirectory()
if "locales_dir_path" not in applicationSettings:
if platform.system() != "Darwin":
applicationSettings["locales_dir_path"] = os.path.join(
module_dir, "/locales")
if "resources_dir_path" not in applicationSettings:
applicationSettings["resources_dir_path"] = module_dir
if platform.system() == "Darwin":
applicationSettings["resources_dir_path"] = module_dir+"/Resources"
if "browser_subprocess_path" not in applicationSettings:
applicationSettings["browser_subprocess_path"] = os.path.join(
module_dir, "subprocess")

# Mouse context menu
if "context_menu" not in applicationSettings:
applicationSettings["context_menu"] = {}
Expand Down Expand Up @@ -682,22 +698,46 @@ def Initialize(applicationSettings=None, commandLineSwitches=None):

if not ret:
Debug("CefInitialize() failed")

IF UNAME_SYSNAME == "Linux":
# Install by default.
WindowUtils.InstallX11ErrorHandlers()

return ret

def CreateBrowserSync(windowInfo, browserSettings, navigateUrl, requestContext=None):
def CreateBrowser(**kwargs):
"""Create browser asynchronously. TODO. """
CreateBrowserSync(**kwargs)

def CreateBrowserSync(windowInfo=None,
browserSettings=None,
navigateUrl="",
**kwargs):
Debug("CreateBrowserSync() called")
assert IsThread(TID_UI), (
"cefpython.CreateBrowserSync() may only be called on the UI thread")

if not isinstance(windowInfo, WindowInfo):
if "window_info" in kwargs:
windowInfo = kwargs["window_info"]
if not windowInfo:
windowInfo = WindowInfo()
windowInfo.SetAsChild(0)
elif not isinstance(windowInfo, WindowInfo):
raise Exception("CreateBrowserSync() failed: windowInfo: invalid object")

if "settings" in kwargs:
browserSettings = kwargs["settings"]
if not browserSettings:
browserSettings = {}

cdef CefBrowserSettings cefBrowserSettings
SetBrowserSettings(browserSettings, &cefBrowserSettings)

cdef CefWindowInfo cefWindowInfo
SetCefWindowInfo(cefWindowInfo, windowInfo)

if "url" in kwargs:
navigateUrl = kwargs["url"]
navigateUrl = GetNavigateUrl(navigateUrl)
Debug("navigateUrl: %s" % navigateUrl)
cdef CefString cefNavigateUrl
Expand Down
2 changes: 1 addition & 1 deletion src/compile_time_constants.pxi
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# This file was generated by setup.py
DEF UNAME_SYSNAME = "Linux"
DEF PY_MAJOR_VERSION = 2
DEF PY_MAJOR_VERSION = 3
92 changes: 92 additions & 0 deletions src/helpers.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright (c) 2016 The CEF Python authors. All rights reserved.

include "cefpython.pyx"

import os
import platform
import re
import traceback
import time
import codecs


cpdef str GetModuleDirectory():
"""Get path to the cefpython module (so/pyd)."""
if platform.system() == "Linux" and os.getenv("CEFPYTHON3_PATH"):
# cefpython3 package __init__.py sets CEFPYTHON3_PATH.
# When cefpython3 is installed as debian package, this
# env variable is the only way of getting valid path.
return os.getenv("CEFPYTHON3_PATH")
if hasattr(sys, "frozen"):
path = os.path.dirname(sys.executable)
elif "__file__" in globals():
path = os.path.dirname(os.path.realpath(__file__))
else:
path = os.getcwd()
if platform.system() == "Windows":
path = re.sub(r"[/\\]+", re.escape(os.sep), path)
path = re.sub(r"[/\\]+$", "", path)
return os.path.abspath(path)

g_GetAppPath_dir = None

cpdef GetAppPath(file=None):
"""Get application path."""
# On Windows after downloading file and calling Browser.GoForward(),
# current working directory is set to %UserProfile%.
# Calling os.path.dirname(os.path.realpath(__file__))
# returns for eg. "C:\Users\user\Downloads". A solution
# is to cache path on first call.
if not g_GetAppPath_dir:
if hasattr(sys, "frozen"):
adir = os.path.dirname(sys.executable)
else:
adir = os.getcwd()
global g_GetAppPath_dir
g_GetAppPath_dir = adir
# If file is None return current directory without trailing slash.
if file is None:
file = ""
# Only when relative path.
if not file.startswith("/") and not file.startswith("\\") and (
not re.search(r"^[\w-]+:", file)):
path = g_GetAppPath_dir + os.sep + file
if platform.system() == "Windows":
path = re.sub(r"[/\\]+", re.escape(os.sep), path)
path = re.sub(r"[/\\]+$", "", path)
return path
return str(file)


cpdef ExceptHook(excType, excValue, traceObject):
"""Global except hook to exit app cleanly on error."""
# This hook does the following: in case of exception write it to
# the "error.log" file, display it to the console, shutdown CEF
# and exit application immediately by ignoring "finally" (_exit()).
errorMsg = "\n".join(traceback.format_exception(excType, excValue,
traceObject))
errorFile = GetAppPath("error.log")
try:
appEncoding = g_applicationSettings["string_encoding"]
except:
appEncoding = "utf-8"
if type(errorMsg) == bytes:
errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace")
try:
with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp:
fp.write("\n[%s] %s\n" % (
time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg))
except:
print("[pygtk_.py]: WARNING: failed writing to error file: %s" % (
errorFile))
# Convert error message to ascii before printing, otherwise
# you may get error like this:
# | UnicodeEncodeError: 'charmap' codec can't encode characters
errorMsg = errorMsg.encode("ascii", errors="replace")
errorMsg = errorMsg.decode("ascii", errors="replace")
print("\n"+errorMsg+"\n")
QuitMessageLoop()
Shutdown()
# noinspection PyProtectedMember
os._exit(1)

Loading

0 comments on commit 6f0d9b3

Please sign in to comment.