From 483dcaf4c28d4185716975edf8d7f516e38fe2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Tue, 4 Jul 2017 12:19:50 +0200 Subject: [PATCH 1/6] Copying example pywin32 from branch cefpython31 --- examples/pywin32.py | 152 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 examples/pywin32.py diff --git a/examples/pywin32.py b/examples/pywin32.py new file mode 100644 index 00000000..de28bfe9 --- /dev/null +++ b/examples/pywin32.py @@ -0,0 +1,152 @@ +# Example of embedding CEF browser using the PyWin32 extension. +# Tested with pywin32 version 218. + +import os, sys +libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'libcef.dll') +if os.path.exists(libcef_dll): + # Import a local module + if (2,7) <= sys.version_info < (2,8): + import cefpython_py27 as cefpython + elif (3,4) <= sys.version_info < (3,4): + import cefpython_py34 as cefpython + else: + raise Exception("Unsupported python version: %s" % sys.version) +else: + # Import an installed package + from cefpython3 import cefpython + +import cefwindow +import win32con +import win32gui +import win32api +import time + +DEBUG = True + +# ----------------------------------------------------------------------------- +# Helper functions. + +def Log(msg): + print("[pywin32.py] %s" % str(msg)) + +def GetApplicationPath(file=None): + import re, os, platform + # 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 hasattr(GetApplicationPath, "dir"): + if hasattr(sys, "frozen"): + dir = os.path.dirname(sys.executable) + elif "__file__" in globals(): + dir = os.path.dirname(os.path.realpath(__file__)) + else: + dir = os.getcwd() + GetApplicationPath.dir = dir + # 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 = GetApplicationPath.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) + +def ExceptHook(excType, excValue, traceObject): + import traceback, os, time, codecs + # 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" (os._exit()). + errorMsg = "\n".join(traceback.format_exception(excType, excValue, + traceObject)) + errorFile = GetApplicationPath("error.log") + try: + appEncoding = cefpython.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("[pywin32.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") + cefpython.QuitMessageLoop() + cefpython.Shutdown() + os._exit(1) + +# ----------------------------------------------------------------------------- + +def CefAdvanced(): + sys.excepthook = ExceptHook + + appSettings = dict() + # appSettings["cache_path"] = "webcache/" # Disk cache + if DEBUG: + # cefpython debug messages in console and in log_file + appSettings["debug"] = True + cefwindow.g_debug = True + appSettings["log_file"] = GetApplicationPath("debug.log") + appSettings["log_severity"] = cefpython.LOGSEVERITY_INFO + appSettings["release_dcheck_enabled"] = True # Enable only when debugging + appSettings["browser_subprocess_path"] = "%s/%s" % ( + cefpython.GetModuleDirectory(), "subprocess") + cefpython.Initialize(appSettings) + + wndproc = { + win32con.WM_CLOSE: CloseWindow, + win32con.WM_DESTROY: QuitApplication, + win32con.WM_SIZE: cefpython.WindowUtils.OnSize, + win32con.WM_SETFOCUS: cefpython.WindowUtils.OnSetFocus, + win32con.WM_ERASEBKGND: cefpython.WindowUtils.OnEraseBackground + } + + browserSettings = dict() + browserSettings["universal_access_from_file_urls_allowed"] = True + browserSettings["file_access_from_file_urls_allowed"] = True + + if os.path.exists("icon.ico"): + icon = os.path.abspath("icon.ico") + else: + icon = "" + + windowHandle = cefwindow.CreateWindow(title="pywin32 example", + className="cefpython3_example", width=1024, height=768, + icon=icon, windowProc=wndproc) + windowInfo = cefpython.WindowInfo() + windowInfo.SetAsChild(windowHandle) + browser = cefpython.CreateBrowserSync(windowInfo, browserSettings, + navigateUrl=GetApplicationPath("example.html")) + cefpython.MessageLoop() + cefpython.Shutdown() + +def CloseWindow(windowHandle, message, wparam, lparam): + browser = cefpython.GetBrowserByWindowHandle(windowHandle) + browser.CloseBrowser() + return win32gui.DefWindowProc(windowHandle, message, wparam, lparam) + +def QuitApplication(windowHandle, message, wparam, lparam): + win32gui.PostQuitMessage(0) + return 0 + +def GetPywin32Version(): + fixed_file_info = win32api.GetFileVersionInfo(win32api.__file__, '\\') + return fixed_file_info['FileVersionLS'] >> 16 + +if __name__ == "__main__": + Log("pywin32 version = %s" % GetPywin32Version()) + CefAdvanced() \ No newline at end of file From a7661ba891d0fa4ac5387c96614b18b6a262265f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Tue, 4 Jul 2017 12:37:44 +0200 Subject: [PATCH 2/6] Updating pywin32 example (wip) --- examples/pywin32.py | 135 +++++++++++++++----------------------------- 1 file changed, 44 insertions(+), 91 deletions(-) diff --git a/examples/pywin32.py b/examples/pywin32.py index de28bfe9..ffd43081 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -1,95 +1,47 @@ # Example of embedding CEF browser using the PyWin32 extension. -# Tested with pywin32 version 218. - -import os, sys -libcef_dll = os.path.join(os.path.dirname(os.path.abspath(__file__)), - 'libcef.dll') -if os.path.exists(libcef_dll): - # Import a local module - if (2,7) <= sys.version_info < (2,8): - import cefpython_py27 as cefpython - elif (3,4) <= sys.version_info < (3,4): - import cefpython_py34 as cefpython - else: - raise Exception("Unsupported python version: %s" % sys.version) -else: - # Import an installed package - from cefpython3 import cefpython +# Tested with pywin32 version 219. + +from cefpython3 import cefpython as cef + +import distutils.sysconfig +import os +import platform +import sys +import time -import cefwindow +import win32api import win32con import win32gui -import win32api -import time -DEBUG = True - -# ----------------------------------------------------------------------------- -# Helper functions. - -def Log(msg): - print("[pywin32.py] %s" % str(msg)) - -def GetApplicationPath(file=None): - import re, os, platform - # 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 hasattr(GetApplicationPath, "dir"): - if hasattr(sys, "frozen"): - dir = os.path.dirname(sys.executable) - elif "__file__" in globals(): - dir = os.path.dirname(os.path.realpath(__file__)) - else: - dir = os.getcwd() - GetApplicationPath.dir = dir - # 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 = GetApplicationPath.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) - -def ExceptHook(excType, excValue, traceObject): - import traceback, os, time, codecs - # 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" (os._exit()). - errorMsg = "\n".join(traceback.format_exception(excType, excValue, - traceObject)) - errorFile = GetApplicationPath("error.log") - try: - appEncoding = cefpython.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("[pywin32.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") - cefpython.QuitMessageLoop() - cefpython.Shutdown() - os._exit(1) +WindowUtils = cef.WindowUtils() + +# Platforms (Windows only) +assert(platform.system() == "Windows") + +def main(): + check_versions() + sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error + cef.Initialize() + pyWin32Example() + """ + if g_message_loop == MESSAGE_LOOP_CEF: + cef.MessageLoop() + else: + gtk.main() + """ + cef.Shutdown() + + +def check_versions(): + print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) + print("[pywin32.py] Python {ver} {arch}".format(ver=platform.python_version(), arch=platform.architecture()[0])) + print("[pywin32.py] pywin32 {ver}".format(ver=GetPywin32Version())) + assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" + + +def pyWin32Example(): + pass -# ----------------------------------------------------------------------------- def CefAdvanced(): sys.excepthook = ExceptHook @@ -144,9 +96,10 @@ def QuitApplication(windowHandle, message, wparam, lparam): return 0 def GetPywin32Version(): - fixed_file_info = win32api.GetFileVersionInfo(win32api.__file__, '\\') - return fixed_file_info['FileVersionLS'] >> 16 + pth = distutils.sysconfig.get_python_lib(plat_specific=1) + ver = open(os.path.join(pth, "pywin32.version.txt")).read().strip() + return ver + -if __name__ == "__main__": - Log("pywin32 version = %s" % GetPywin32Version()) - CefAdvanced() \ No newline at end of file +if __name__ == '__main__': + main() From be95517e4dd1e6db192cab9ae29f84ac367d35d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Tue, 4 Jul 2017 12:53:58 +0200 Subject: [PATCH 3/6] pywin32 example is working --- examples/pywin32.py | 94 +++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/examples/pywin32.py b/examples/pywin32.py index ffd43081..123a0e89 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -4,6 +4,7 @@ from cefpython3 import cefpython as cef import distutils.sysconfig +import math import os import platform import sys @@ -23,12 +24,6 @@ def main(): sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error cef.Initialize() pyWin32Example() - """ - if g_message_loop == MESSAGE_LOOP_CEF: - cef.MessageLoop() - else: - gtk.main() - """ cef.Shutdown() @@ -40,61 +35,68 @@ def check_versions(): def pyWin32Example(): - pass - - -def CefAdvanced(): - sys.excepthook = ExceptHook - - appSettings = dict() - # appSettings["cache_path"] = "webcache/" # Disk cache - if DEBUG: - # cefpython debug messages in console and in log_file - appSettings["debug"] = True - cefwindow.g_debug = True - appSettings["log_file"] = GetApplicationPath("debug.log") - appSettings["log_severity"] = cefpython.LOGSEVERITY_INFO - appSettings["release_dcheck_enabled"] = True # Enable only when debugging - appSettings["browser_subprocess_path"] = "%s/%s" % ( - cefpython.GetModuleDirectory(), "subprocess") - cefpython.Initialize(appSettings) + + cef.Initialize() wndproc = { win32con.WM_CLOSE: CloseWindow, win32con.WM_DESTROY: QuitApplication, - win32con.WM_SIZE: cefpython.WindowUtils.OnSize, - win32con.WM_SETFOCUS: cefpython.WindowUtils.OnSetFocus, - win32con.WM_ERASEBKGND: cefpython.WindowUtils.OnEraseBackground + win32con.WM_SIZE: WindowUtils.OnSize, + win32con.WM_SETFOCUS: WindowUtils.OnSetFocus, + win32con.WM_ERASEBKGND: WindowUtils.OnEraseBackground } - - browserSettings = dict() - browserSettings["universal_access_from_file_urls_allowed"] = True - browserSettings["file_access_from_file_urls_allowed"] = True - - if os.path.exists("icon.ico"): - icon = os.path.abspath("icon.ico") - else: - icon = "" - - windowHandle = cefwindow.CreateWindow(title="pywin32 example", - className="cefpython3_example", width=1024, height=768, - icon=icon, windowProc=wndproc) - windowInfo = cefpython.WindowInfo() + + windowHandle = CreateWindow(title="pywin32 example", className="cefpython3_example", width=1024, height=768, windowProc=wndproc) + + windowInfo = cef.WindowInfo() windowInfo.SetAsChild(windowHandle) - browser = cefpython.CreateBrowserSync(windowInfo, browserSettings, - navigateUrl=GetApplicationPath("example.html")) - cefpython.MessageLoop() - cefpython.Shutdown() + browser = cef.CreateBrowserSync(windowInfo, settings={}, + url="https://www.google.com/") + cef.MessageLoop() + cef.Shutdown() + def CloseWindow(windowHandle, message, wparam, lparam): - browser = cefpython.GetBrowserByWindowHandle(windowHandle) + browser = cef.GetBrowserByWindowHandle(windowHandle) browser.CloseBrowser() return win32gui.DefWindowProc(windowHandle, message, wparam, lparam) + def QuitApplication(windowHandle, message, wparam, lparam): win32gui.PostQuitMessage(0) return 0 + +def CreateWindow(title, className, width, height, windowProc): + + wndclass = win32gui.WNDCLASS() + wndclass.hInstance = win32api.GetModuleHandle(None) + wndclass.lpszClassName = className + wndclass.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW + # win32con.CS_GLOBALCLASS + wndclass.hbrBackground = win32con.COLOR_WINDOW + wndclass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) + wndclass.lpfnWndProc = windowProc + atomClass = win32gui.RegisterClass(wndclass) + assert(atomClass != 0) + + # Center window on the screen. + screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) + screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) + xpos = int(math.floor((screenx - width) / 2)) + ypos = int(math.floor((screeny - height) / 2)) + if xpos < 0: xpos = 0 + if ypos < 0: ypos = 0 + + windowHandle = win32gui.CreateWindow(className, title, + win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE, + xpos, ypos, width, height, # xpos, ypos, width, height + 0, 0, wndclass.hInstance, None) + + assert(windowHandle != 0) + return windowHandle + + def GetPywin32Version(): pth = distutils.sysconfig.get_python_lib(plat_specific=1) ver = open(os.path.join(pth, "pywin32.version.txt")).read().strip() From d893abe4872c189e1f0c7b6415022d8b1aad0a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Tue, 4 Jul 2017 14:16:27 +0200 Subject: [PATCH 4/6] Implementing 'multi_threaded_message_loop' in pywin32 example --- examples/pywin32.py | 58 +++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/examples/pywin32.py b/examples/pywin32.py index 123a0e89..68c46b67 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -9,6 +9,7 @@ import platform import sys import time +import traceback import win32api import win32con @@ -19,25 +20,14 @@ # Platforms (Windows only) assert(platform.system() == "Windows") -def main(): +def main(multi_threaded_message_loop): + check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error - cef.Initialize() - pyWin32Example() - cef.Shutdown() - - -def check_versions(): - print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) - print("[pywin32.py] Python {ver} {arch}".format(ver=platform.python_version(), arch=platform.architecture()[0])) - print("[pywin32.py] pywin32 {ver}".format(ver=GetPywin32Version())) - assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" - - -def pyWin32Example(): - cef.Initialize() - + settings = {"multi_threaded_message_loop": 1 if multi_threaded_message_loop else 0} + cef.Initialize(settings) + wndproc = { win32con.WM_CLOSE: CloseWindow, win32con.WM_DESTROY: QuitApplication, @@ -45,15 +35,35 @@ def pyWin32Example(): win32con.WM_SETFOCUS: WindowUtils.OnSetFocus, win32con.WM_ERASEBKGND: WindowUtils.OnEraseBackground } - windowHandle = CreateWindow(title="pywin32 example", className="cefpython3_example", width=1024, height=768, windowProc=wndproc) windowInfo = cef.WindowInfo() windowInfo.SetAsChild(windowHandle) + + if(multi_threaded_message_loop): + # when using multi-threaded message loop, CEF's UI thread is no more application's main thread + cef.PostTask(cef.TID_UI, _createBrowserInUiThread, windowInfo, {}, "https://www.google.com/") + win32gui.PumpMessages() + + else: + browser = _createBrowserInUiThread(windowInfo, {}, "https://www.google.com/") + cef.MessageLoop() + + cef.Shutdown() + + +def check_versions(): + print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) + print("[pywin32.py] Python {ver} {arch}".format(ver=platform.python_version(), arch=platform.architecture()[0])) + print("[pywin32.py] pywin32 {ver}".format(ver=GetPywin32Version())) + assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" + + +def _createBrowserInUiThread(windowInfo, settings, url): + + assert(cef.IsThread(cef.TID_UI)) browser = cef.CreateBrowserSync(windowInfo, settings={}, url="https://www.google.com/") - cef.MessageLoop() - cef.Shutdown() def CloseWindow(windowHandle, message, wparam, lparam): @@ -104,4 +114,12 @@ def GetPywin32Version(): if __name__ == '__main__': - main() + + if "--multi_threaded_message_loop" in sys.argv: + print("[pywin32.py] Message loop mode: CEF multi-threaded (best performance)") + multi_threaded_message_loop = True + else: + print("[pywin32.py] Message loop mode: CEF single-threaded") + multi_threaded_message_loop = False + + main(multi_threaded_message_loop) From 45d086a3d8ddbdc61873bab6704c253cdb94753d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Tue, 4 Jul 2017 15:12:32 +0200 Subject: [PATCH 5/6] Code cleanup --- examples/pywin32.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/pywin32.py b/examples/pywin32.py index 68c46b67..2842fab0 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -8,8 +8,6 @@ import os import platform import sys -import time -import traceback import win32api import win32con @@ -62,8 +60,7 @@ def check_versions(): def _createBrowserInUiThread(windowInfo, settings, url): assert(cef.IsThread(cef.TID_UI)) - browser = cef.CreateBrowserSync(windowInfo, settings={}, - url="https://www.google.com/") + browser = cef.CreateBrowserSync(windowInfo, settings, url) def CloseWindow(windowHandle, message, wparam, lparam): From c95f52fe2a8c36368466ba4296ec1eba6cb26e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Verdon?= Date: Wed, 5 Jul 2017 10:13:13 +0200 Subject: [PATCH 6/6] Adding remote debug to example --- examples/pywin32.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/pywin32.py b/examples/pywin32.py index 2842fab0..18924c10 100644 --- a/examples/pywin32.py +++ b/examples/pywin32.py @@ -23,7 +23,8 @@ def main(multi_threaded_message_loop): check_versions() sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error - settings = {"multi_threaded_message_loop": 1 if multi_threaded_message_loop else 0} + settings = {"multi_threaded_message_loop": 1 if multi_threaded_message_loop else 0, + "remote_debugging_port": 2020} cef.Initialize(settings) wndproc = {