|
1 | | -# Example of embedding CEF browser using the PyWin32 extension. |
2 | | -# Tested with pywin32 version 219. |
| 1 | +# Example of embedding CEF browser using PyWin32 library. |
| 2 | +# Tested with pywin32 version 221 and CEF Python v57.0+. |
| 3 | +# |
| 4 | +# Usage: |
| 5 | +# python pywin32.py |
| 6 | +# python pywin32.py --multi-threaded |
| 7 | +# |
| 8 | +# By passing --multi-threaded arg CEF will run using multi threaded |
| 9 | +# message loop which has best performance on Windows. However there |
| 10 | +# is one issue with it on exit, see "Known issues" below. See also |
| 11 | +# docs/Tutorial.md and the "Message loop" section. |
| 12 | +# |
| 13 | +# Known issues: |
| 14 | +# - Crash on exit with multi threaded message loop (Issue #380) |
3 | 15 |
|
4 | 16 | from cefpython3 import cefpython as cef |
5 | 17 |
|
|
13 | 25 | import win32con |
14 | 26 | import win32gui |
15 | 27 |
|
| 28 | +# Globals |
16 | 29 | WindowUtils = cef.WindowUtils() |
| 30 | +g_multi_threaded = False |
17 | 31 |
|
18 | | -# Platforms (Windows only) |
19 | | -assert(platform.system() == "Windows") |
20 | 32 |
|
21 | | -def main(multi_threaded_message_loop): |
22 | | - |
| 33 | +def main(): |
| 34 | + command_line_args() |
23 | 35 | check_versions() |
24 | 36 | sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error |
25 | 37 |
|
26 | | - settings = {"multi_threaded_message_loop": 1 if multi_threaded_message_loop else 0, |
27 | | - "remote_debugging_port": 2020} |
28 | | - cef.Initialize(settings) |
| 38 | + settings = { |
| 39 | + "multi_threaded_message_loop": g_multi_threaded, |
| 40 | + } |
| 41 | + cef.Initialize(settings=settings) |
29 | 42 |
|
30 | | - wndproc = { |
31 | | - win32con.WM_CLOSE: CloseWindow, |
32 | | - win32con.WM_DESTROY: QuitApplication, |
| 43 | + window_proc = { |
| 44 | + win32con.WM_CLOSE: close_window, |
| 45 | + win32con.WM_DESTROY: exit_app, |
33 | 46 | win32con.WM_SIZE: WindowUtils.OnSize, |
34 | 47 | win32con.WM_SETFOCUS: WindowUtils.OnSetFocus, |
35 | 48 | win32con.WM_ERASEBKGND: WindowUtils.OnEraseBackground |
36 | 49 | } |
37 | | - windowHandle = CreateWindow(title="pywin32 example", className="cefpython3_example", width=1024, height=768, windowProc=wndproc) |
| 50 | + window_handle = create_window(title="PyWin32 example", |
| 51 | + class_name="pywin32.example", |
| 52 | + width=800, |
| 53 | + height=600, |
| 54 | + window_proc=window_proc, |
| 55 | + icon="resources/chromium.ico") |
38 | 56 |
|
39 | | - windowInfo = cef.WindowInfo() |
40 | | - windowInfo.SetAsChild(windowHandle) |
| 57 | + window_info = cef.WindowInfo() |
| 58 | + window_info.SetAsChild(window_handle) |
41 | 59 |
|
42 | | - if(multi_threaded_message_loop): |
43 | | - # when using multi-threaded message loop, CEF's UI thread is no more application's main thread |
44 | | - cef.PostTask(cef.TID_UI, _createBrowserInUiThread, windowInfo, {}, "https://www.google.com/") |
| 60 | + if g_multi_threaded: |
| 61 | + # When using multi-threaded message loop, CEF's UI thread |
| 62 | + # is no more application's main thread. In such case browser |
| 63 | + # must be created using cef.PostTask function and CEF message |
| 64 | + # loop must not be run explicitilly. |
| 65 | + cef.PostTask(cef.TID_UI, |
| 66 | + create_browser, |
| 67 | + window_info, |
| 68 | + {}, |
| 69 | + "https://www.google.com/") |
45 | 70 | win32gui.PumpMessages() |
46 | 71 |
|
47 | 72 | else: |
48 | | - browser = _createBrowserInUiThread(windowInfo, {}, "https://www.google.com/") |
| 73 | + create_browser(window_info=window_info, |
| 74 | + settings={}, |
| 75 | + url="https://www.google.com/") |
49 | 76 | cef.MessageLoop() |
50 | 77 |
|
51 | 78 | cef.Shutdown() |
52 | 79 |
|
53 | 80 |
|
54 | | -def check_versions(): |
55 | | - print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) |
56 | | - print("[pywin32.py] Python {ver} {arch}".format(ver=platform.python_version(), arch=platform.architecture()[0])) |
57 | | - print("[pywin32.py] pywin32 {ver}".format(ver=GetPywin32Version())) |
58 | | - assert cef.__version__ >= "55.3", "CEF Python v55.3+ required to run this" |
| 81 | +def command_line_args(): |
| 82 | + global g_multi_threaded |
| 83 | + if "--multi-threaded" in sys.argv: |
| 84 | + sys.argv.remove("--multi-threaded") |
| 85 | + print("[pywin32.py] Message loop mode: CEF multi-threaded" |
| 86 | + " (best performance)") |
| 87 | + g_multi_threaded = True |
| 88 | + else: |
| 89 | + print("[pywin32.py] Message loop mode: CEF single-threaded") |
| 90 | + if len(sys.argv) > 1: |
| 91 | + print("ERROR: Invalid args passed." |
| 92 | + " For usage see top comments in pywin32.py.") |
| 93 | + sys.exit(1) |
59 | 94 |
|
60 | 95 |
|
61 | | -def _createBrowserInUiThread(windowInfo, settings, url): |
62 | | - |
63 | | - assert(cef.IsThread(cef.TID_UI)) |
64 | | - browser = cef.CreateBrowserSync(windowInfo, settings, url) |
| 96 | +def check_versions(): |
| 97 | + if platform.system() != "Windows": |
| 98 | + print("ERROR: This example is for Windows platform only") |
| 99 | + sys.exit(1) |
65 | 100 |
|
| 101 | + print("[pywin32.py] CEF Python {ver}".format(ver=cef.__version__)) |
| 102 | + print("[pywin32.py] Python {ver} {arch}".format( |
| 103 | + ver=platform.python_version(), arch=platform.architecture()[0])) |
66 | 104 |
|
67 | | -def CloseWindow(windowHandle, message, wparam, lparam): |
68 | | - browser = cef.GetBrowserByWindowHandle(windowHandle) |
69 | | - browser.CloseBrowser() |
70 | | - return win32gui.DefWindowProc(windowHandle, message, wparam, lparam) |
| 105 | + # PyWin32 version |
| 106 | + python_lib = distutils.sysconfig.get_python_lib(plat_specific=1) |
| 107 | + with open(os.path.join(python_lib, "pywin32.version.txt")) as fp: |
| 108 | + pywin32_version = fp.read().strip() |
| 109 | + print("[pywin32.py] pywin32 {ver}".format(ver=pywin32_version)) |
71 | 110 |
|
| 111 | + assert cef.__version__ >= "57.0", "CEF Python v57.0 required to run this" |
72 | 112 |
|
73 | | -def QuitApplication(windowHandle, message, wparam, lparam): |
74 | | - win32gui.PostQuitMessage(0) |
75 | | - return 0 |
76 | 113 |
|
| 114 | +def create_browser(window_info, settings, url): |
| 115 | + assert(cef.IsThread(cef.TID_UI)) |
| 116 | + cef.CreateBrowserSync(window_info=window_info, |
| 117 | + settings=settings, |
| 118 | + url=url) |
77 | 119 |
|
78 | | -def CreateWindow(title, className, width, height, windowProc): |
79 | | - |
| 120 | + |
| 121 | +def create_window(title, class_name, width, height, window_proc, icon): |
| 122 | + # Register window class |
80 | 123 | wndclass = win32gui.WNDCLASS() |
81 | 124 | wndclass.hInstance = win32api.GetModuleHandle(None) |
82 | | - wndclass.lpszClassName = className |
| 125 | + wndclass.lpszClassName = class_name |
83 | 126 | wndclass.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW |
84 | | - # win32con.CS_GLOBALCLASS |
85 | 127 | wndclass.hbrBackground = win32con.COLOR_WINDOW |
86 | 128 | wndclass.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) |
87 | | - wndclass.lpfnWndProc = windowProc |
88 | | - atomClass = win32gui.RegisterClass(wndclass) |
89 | | - assert(atomClass != 0) |
90 | | - |
91 | | - # Center window on the screen. |
| 129 | + wndclass.lpfnWndProc = window_proc |
| 130 | + atom_class = win32gui.RegisterClass(wndclass) |
| 131 | + assert(atom_class != 0) |
| 132 | + |
| 133 | + # Center window on screen. |
92 | 134 | screenx = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) |
93 | 135 | screeny = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) |
94 | 136 | xpos = int(math.floor((screenx - width) / 2)) |
95 | 137 | ypos = int(math.floor((screeny - height) / 2)) |
96 | | - if xpos < 0: xpos = 0 |
97 | | - if ypos < 0: ypos = 0 |
98 | | - |
99 | | - windowHandle = win32gui.CreateWindow(className, title, |
100 | | - win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN | win32con.WS_VISIBLE, |
101 | | - xpos, ypos, width, height, # xpos, ypos, width, height |
102 | | - 0, 0, wndclass.hInstance, None) |
103 | | - |
104 | | - assert(windowHandle != 0) |
105 | | - return windowHandle |
106 | | - |
107 | | - |
108 | | -def GetPywin32Version(): |
109 | | - pth = distutils.sysconfig.get_python_lib(plat_specific=1) |
110 | | - ver = open(os.path.join(pth, "pywin32.version.txt")).read().strip() |
111 | | - return ver |
| 138 | + if xpos < 0: |
| 139 | + xpos = 0 |
| 140 | + if ypos < 0: |
| 141 | + ypos = 0 |
| 142 | + |
| 143 | + # Create window |
| 144 | + window_style = (win32con.WS_OVERLAPPEDWINDOW | win32con.WS_CLIPCHILDREN |
| 145 | + | win32con.WS_VISIBLE) |
| 146 | + window_handle = win32gui.CreateWindow(class_name, title, window_style, |
| 147 | + xpos, ypos, width, height, |
| 148 | + 0, 0, wndclass.hInstance, None) |
| 149 | + assert(window_handle != 0) |
| 150 | + |
| 151 | + # Window icon |
| 152 | + icon = os.path.abspath(icon) |
| 153 | + if not os.path.isfile(icon): |
| 154 | + icon = None |
| 155 | + if icon: |
| 156 | + # Load small and big icon. |
| 157 | + # WNDCLASSEX (along with hIconSm) is not supported by pywin32, |
| 158 | + # we need to use WM_SETICON message after window creation. |
| 159 | + # Ref: |
| 160 | + # 1. http://stackoverflow.com/questions/2234988 |
| 161 | + # 2. http://blog.barthe.ph/2009/07/17/wmseticon/ |
| 162 | + bigx = win32api.GetSystemMetrics(win32con.SM_CXICON) |
| 163 | + bigy = win32api.GetSystemMetrics(win32con.SM_CYICON) |
| 164 | + big_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, |
| 165 | + bigx, bigy, |
| 166 | + win32con.LR_LOADFROMFILE) |
| 167 | + smallx = win32api.GetSystemMetrics(win32con.SM_CXSMICON) |
| 168 | + smally = win32api.GetSystemMetrics(win32con.SM_CYSMICON) |
| 169 | + small_icon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, |
| 170 | + smallx, smally, |
| 171 | + win32con.LR_LOADFROMFILE) |
| 172 | + win32api.SendMessage(window_handle, win32con.WM_SETICON, |
| 173 | + win32con.ICON_BIG, big_icon) |
| 174 | + win32api.SendMessage(window_handle, win32con.WM_SETICON, |
| 175 | + win32con.ICON_SMALL, small_icon) |
| 176 | + |
| 177 | + return window_handle |
| 178 | + |
| 179 | + |
| 180 | +def close_window(window_handle, message, wparam, lparam): |
| 181 | + browser = cef.GetBrowserByWindowHandle(window_handle) |
| 182 | + browser.CloseBrowser(True) |
| 183 | + # OFF: win32gui.DestroyWindow(window_handle) |
| 184 | + return win32gui.DefWindowProc(window_handle, message, wparam, lparam) |
| 185 | + |
| 186 | + |
| 187 | +def exit_app(*_): |
| 188 | + win32gui.PostQuitMessage(0) |
| 189 | + return 0 |
112 | 190 |
|
113 | 191 |
|
114 | 192 | if __name__ == '__main__': |
115 | | - |
116 | | - if "--multi_threaded_message_loop" in sys.argv: |
117 | | - print("[pywin32.py] Message loop mode: CEF multi-threaded (best performance)") |
118 | | - multi_threaded_message_loop = True |
119 | | - else: |
120 | | - print("[pywin32.py] Message loop mode: CEF single-threaded") |
121 | | - multi_threaded_message_loop = False |
122 | | - |
123 | | - main(multi_threaded_message_loop) |
| 193 | + main() |
0 commit comments