Skip to content

Commit 6f0d9b3

Browse files
committed
Add experimental Python 3 support (cztomczak#121, cztomczak#183) among others.
Add hello_world.py example (cztomczak#207). Update to Cython 0.24.1 (cztomczak#110). Test with Python 3.4.5 (cztomczak#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.
1 parent 21549c7 commit 6f0d9b3

File tree

15 files changed

+382
-130
lines changed

15 files changed

+382
-130
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.idea/
22
build/
3+
*.log

api/WindowInfo.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,12 @@ Table of contents:
2525
| Parameter | Type |
2626
| --- | --- |
2727
| parentWindowHandle | int |
28-
| windowRect | list |
28+
| windowRect (optional) | list |
2929
| __Return__ | void |
3030

3131
Create the browser as a child window/view.
3232

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

3635

3736
### SetAsPopup

api/cefpython.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ Functions in the cefpython module.
88

99
Table of contents:
1010
* [Functions](#functions)
11+
* [CreateBrowser](#createbrowsersync)
1112
* [CreateBrowserSync](#createbrowsersync)
13+
* [ExceptHook](#excepthook)
1214
* [GetAppSetting](#getappsetting)
15+
* [GetAppPath](#getapppath)
1316
* [GetBrowserByWindowHandle](#getbrowserbywindowhandle)
1417
* [GetCommandLineSwitch](#getcommandlineswitch)
1518
* [GetGlobalClientCallback](#getglobalclientcallback)
@@ -28,17 +31,26 @@ Table of contents:
2831
## Functions
2932

3033

34+
### CreateBrowser
35+
36+
Create browser asynchronously (does not return Browser object).
37+
See `CreateBrowserSync()` for params list.
38+
39+
NOTE: currently this is just an alias and actually creates browser
40+
synchronously. The async call to CefCreateBrowser is yet TODO.
41+
42+
3143
### CreateBrowserSync
3244

3345
| Parameter | Type |
3446
| --- | --- |
35-
| windowInfo | [WindowInfo](WindowInfo.md) |
36-
| [BrowserSettings](BrowserSettings.md) | dict |
37-
| navigateUrl | string |
38-
| requestContext | void |
47+
| window_info | [WindowInfo](WindowInfo.md) |
48+
| [settings](BrowserSettings.md) | [BrowserSettings](BrowserSettings.md) |
49+
| url | string |
50+
| request_context | void |
3951
| __Return__ | [Browser](Browser.md) |
4052

41-
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().
53+
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().
4254

4355
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:
4456

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

67+
68+
### ExceptHook
69+
70+
| Parameter | Type |
71+
| --- | --- |
72+
| excType | - |
73+
| excValue | - |
74+
| traceObject | - |
75+
| __Return__ | string |
76+
77+
Global except hook to exit app cleanly on error.
78+
79+
This hook does the following: in case of exception write it to
80+
the "error.log" file, display it to the console, shutdown CEF
81+
and exit application immediately by ignoring "finally" (_exit())
82+
83+
5584
### GetAppSetting
5685

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

6493

94+
### GetAppPath
95+
96+
| | |
97+
| --- | --- |
98+
| __Return__ | string |
99+
100+
Get path to where application resides.
101+
102+
65103
### GetBrowserByWindowHandle
66104

67105
| Parameter | Type |

examples/hello_world.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Hello world example doesn't depend on any third party GUI framework.
2+
3+
from cefpython3 import cefpython as cef
4+
import sys
5+
6+
7+
def main():
8+
"""Main entry point."""
9+
sys.excepthook = cef.ExceptHook
10+
cef.Initialize()
11+
browser = cef.CreateBrowserSync(url="https://www.google.com/")
12+
browser.SetClientHandler(ClientHandler())
13+
cef.MessageLoop()
14+
cef.Shutdown()
15+
16+
17+
class ClientHandler:
18+
"""Client handler."""
19+
def OnBeforeClose(self, browser):
20+
if not browser.IsPopup():
21+
cef.QuitMessageLoop()
22+
23+
24+
if __name__ == '__main__':
25+
main()

src/cefpython.pyx

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ g_debugFile = "debug.log"
437437
# When put None here and assigned a local dictionary in Initialize(), later
438438
# while running app this global variable was garbage collected, see topic:
439439
# https://groups.google.com/d/topic/cython-users/0dw3UASh7HY/discussion
440-
g_applicationSettings = {}
440+
# The string_encoding key must be set early here and also in Initialize.
441+
g_applicationSettings = {"string_encoding": "utf-8"}
441442
g_commandLineSwitches = {}
442443

443444
# noinspection PyUnresolvedReferences
@@ -504,6 +505,7 @@ include "command_line.pyx"
504505
include "app.pyx"
505506
include "javascript_dialog_handler.pyx"
506507
include "drag_data.pyx"
508+
include "helpers.pyx"
507509

508510
# -----------------------------------------------------------------------------
509511
# Utility functions to provide settings to the C++ browser process code.
@@ -513,7 +515,7 @@ cdef public void cefpython_GetDebugOptions(
513515
cpp_string* debugFile
514516
) except * with gil:
515517
# Called from subprocess/cefpython_app.cpp -> CefPythonApp constructor.
516-
cdef cpp_string cppString = g_debugFile
518+
cdef cpp_string cppString = PyStringToChar(g_debugFile)
517519
try:
518520
debug[0] = <cpp_bool>bool(g_debug)
519521
debugFile.assign(cppString)
@@ -547,7 +549,7 @@ cdef public cpp_string ApplicationSettings_GetString(const char* key
547549
cdef py_string pyKey = CharToPyString(key)
548550
cdef cpp_string cppString
549551
if pyKey in g_applicationSettings:
550-
cppString = AnyToPyString(g_applicationSettings[pyKey])
552+
cppString = PyStringToChar(AnyToPyString(g_applicationSettings[pyKey]))
551553
return cppString
552554

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

577579
if not applicationSettings:
@@ -611,6 +613,20 @@ def Initialize(applicationSettings=None, commandLineSwitches=None):
611613
if DpiAware.IsProcessDpiAware():
612614
applicationSettings["auto_zooming"] = "system_dpi"
613615

616+
# Paths
617+
cdef str module_dir = GetModuleDirectory()
618+
if "locales_dir_path" not in applicationSettings:
619+
if platform.system() != "Darwin":
620+
applicationSettings["locales_dir_path"] = os.path.join(
621+
module_dir, "/locales")
622+
if "resources_dir_path" not in applicationSettings:
623+
applicationSettings["resources_dir_path"] = module_dir
624+
if platform.system() == "Darwin":
625+
applicationSettings["resources_dir_path"] = module_dir+"/Resources"
626+
if "browser_subprocess_path" not in applicationSettings:
627+
applicationSettings["browser_subprocess_path"] = os.path.join(
628+
module_dir, "subprocess")
629+
614630
# Mouse context menu
615631
if "context_menu" not in applicationSettings:
616632
applicationSettings["context_menu"] = {}
@@ -682,22 +698,46 @@ def Initialize(applicationSettings=None, commandLineSwitches=None):
682698

683699
if not ret:
684700
Debug("CefInitialize() failed")
701+
702+
IF UNAME_SYSNAME == "Linux":
703+
# Install by default.
704+
WindowUtils.InstallX11ErrorHandlers()
705+
685706
return ret
686707

687-
def CreateBrowserSync(windowInfo, browserSettings, navigateUrl, requestContext=None):
708+
def CreateBrowser(**kwargs):
709+
"""Create browser asynchronously. TODO. """
710+
CreateBrowserSync(**kwargs)
711+
712+
def CreateBrowserSync(windowInfo=None,
713+
browserSettings=None,
714+
navigateUrl="",
715+
**kwargs):
688716
Debug("CreateBrowserSync() called")
689717
assert IsThread(TID_UI), (
690718
"cefpython.CreateBrowserSync() may only be called on the UI thread")
691719

692-
if not isinstance(windowInfo, WindowInfo):
720+
if "window_info" in kwargs:
721+
windowInfo = kwargs["window_info"]
722+
if not windowInfo:
723+
windowInfo = WindowInfo()
724+
windowInfo.SetAsChild(0)
725+
elif not isinstance(windowInfo, WindowInfo):
693726
raise Exception("CreateBrowserSync() failed: windowInfo: invalid object")
694727

728+
if "settings" in kwargs:
729+
browserSettings = kwargs["settings"]
730+
if not browserSettings:
731+
browserSettings = {}
732+
695733
cdef CefBrowserSettings cefBrowserSettings
696734
SetBrowserSettings(browserSettings, &cefBrowserSettings)
697735

698736
cdef CefWindowInfo cefWindowInfo
699737
SetCefWindowInfo(cefWindowInfo, windowInfo)
700738

739+
if "url" in kwargs:
740+
navigateUrl = kwargs["url"]
701741
navigateUrl = GetNavigateUrl(navigateUrl)
702742
Debug("navigateUrl: %s" % navigateUrl)
703743
cdef CefString cefNavigateUrl

src/compile_time_constants.pxi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# This file was generated by setup.py
22
DEF UNAME_SYSNAME = "Linux"
3-
DEF PY_MAJOR_VERSION = 2
3+
DEF PY_MAJOR_VERSION = 3

src/helpers.pyx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) 2016 The CEF Python authors. All rights reserved.
2+
3+
include "cefpython.pyx"
4+
5+
import os
6+
import platform
7+
import re
8+
import traceback
9+
import time
10+
import codecs
11+
12+
13+
cpdef str GetModuleDirectory():
14+
"""Get path to the cefpython module (so/pyd)."""
15+
if platform.system() == "Linux" and os.getenv("CEFPYTHON3_PATH"):
16+
# cefpython3 package __init__.py sets CEFPYTHON3_PATH.
17+
# When cefpython3 is installed as debian package, this
18+
# env variable is the only way of getting valid path.
19+
return os.getenv("CEFPYTHON3_PATH")
20+
if hasattr(sys, "frozen"):
21+
path = os.path.dirname(sys.executable)
22+
elif "__file__" in globals():
23+
path = os.path.dirname(os.path.realpath(__file__))
24+
else:
25+
path = os.getcwd()
26+
if platform.system() == "Windows":
27+
path = re.sub(r"[/\\]+", re.escape(os.sep), path)
28+
path = re.sub(r"[/\\]+$", "", path)
29+
return os.path.abspath(path)
30+
31+
g_GetAppPath_dir = None
32+
33+
cpdef GetAppPath(file=None):
34+
"""Get application path."""
35+
# On Windows after downloading file and calling Browser.GoForward(),
36+
# current working directory is set to %UserProfile%.
37+
# Calling os.path.dirname(os.path.realpath(__file__))
38+
# returns for eg. "C:\Users\user\Downloads". A solution
39+
# is to cache path on first call.
40+
if not g_GetAppPath_dir:
41+
if hasattr(sys, "frozen"):
42+
adir = os.path.dirname(sys.executable)
43+
else:
44+
adir = os.getcwd()
45+
global g_GetAppPath_dir
46+
g_GetAppPath_dir = adir
47+
# If file is None return current directory without trailing slash.
48+
if file is None:
49+
file = ""
50+
# Only when relative path.
51+
if not file.startswith("/") and not file.startswith("\\") and (
52+
not re.search(r"^[\w-]+:", file)):
53+
path = g_GetAppPath_dir + os.sep + file
54+
if platform.system() == "Windows":
55+
path = re.sub(r"[/\\]+", re.escape(os.sep), path)
56+
path = re.sub(r"[/\\]+$", "", path)
57+
return path
58+
return str(file)
59+
60+
61+
cpdef ExceptHook(excType, excValue, traceObject):
62+
"""Global except hook to exit app cleanly on error."""
63+
# This hook does the following: in case of exception write it to
64+
# the "error.log" file, display it to the console, shutdown CEF
65+
# and exit application immediately by ignoring "finally" (_exit()).
66+
errorMsg = "\n".join(traceback.format_exception(excType, excValue,
67+
traceObject))
68+
errorFile = GetAppPath("error.log")
69+
try:
70+
appEncoding = g_applicationSettings["string_encoding"]
71+
except:
72+
appEncoding = "utf-8"
73+
if type(errorMsg) == bytes:
74+
errorMsg = errorMsg.decode(encoding=appEncoding, errors="replace")
75+
try:
76+
with codecs.open(errorFile, mode="a", encoding=appEncoding) as fp:
77+
fp.write("\n[%s] %s\n" % (
78+
time.strftime("%Y-%m-%d %H:%M:%S"), errorMsg))
79+
except:
80+
print("[pygtk_.py]: WARNING: failed writing to error file: %s" % (
81+
errorFile))
82+
# Convert error message to ascii before printing, otherwise
83+
# you may get error like this:
84+
# | UnicodeEncodeError: 'charmap' codec can't encode characters
85+
errorMsg = errorMsg.encode("ascii", errors="replace")
86+
errorMsg = errorMsg.decode("ascii", errors="replace")
87+
print("\n"+errorMsg+"\n")
88+
QuitMessageLoop()
89+
Shutdown()
90+
# noinspection PyProtectedMember
91+
os._exit(1)
92+

0 commit comments

Comments
 (0)