class MSHTML: class JSBridge(IWebBrowserInterop): __namespace__ = 'MSHTML.JSBridge' window = None def call(self, func_name, param, value_id): return js_bridge_call(self.window, func_name, json.loads(param), value_id) def alert(self, message): BrowserView.alert(message) def console(self, message): print(message) def __init__(self, form, window): self.pywebview_window = window self.web_browser = WebBrowserEx() self.web_browser.Dock = WinForms.DockStyle.Fill self.web_browser.ScriptErrorsSuppressed = not _debug self.web_browser.IsWebBrowserContextMenuEnabled = _debug self.web_browser.WebBrowserShortcutsEnabled = False self.web_browser.DpiAware = True user_agent = _user_agent or settings.get('user_agent') if user_agent: self.web_browser.ChangeUserAgent(user_agent) self.web_browser.ScriptErrorsSuppressed = not _debug self.web_browser.IsWebBrowserContextMenuEnabled = _debug self.js_result_semaphore = Semaphore(0) self.js_bridge = MSHTML.JSBridge() self.js_bridge.window = window self.web_browser.ObjectForScripting = self.js_bridge # HACK. Hiding the WebBrowser is needed in order to show a non-default background color. Tweaking the Visible property # results in showing a non-responsive control, until it is loaded fully. To avoid this, we need to disable this behaviour # for the default background color. if window.background_color != '#FFFFFF': self.web_browser.Visible = False self.first_load = True else: self.first_load = False self.cancel_back = False self.web_browser.PreviewKeyDown += self.on_preview_keydown self.web_browser.Navigating += self.on_navigating self.web_browser.NewWindow3 += self.on_new_window self.web_browser.DownloadComplete += self.on_download_complete self.web_browser.DocumentCompleted += self.on_document_completed if window.real_url: self.web_browser.Navigate(window.real_url) elif window.html: self.web_browser.DocumentText = window.html else: self.web_browser.DocumentText = default_html self.form = form form.Controls.Add(self.web_browser) def evaluate_js(self, script): result = self.web_browser.Document.InvokeScript('eval', (script, )) #self.js_result = None if result is None or result is 'null' else json.loads(result) self.js_result = None if result is None or result == 'null' else json.loads( result) ## self.js_result_semaphore.release() def load_html(self, content, base_uri): self.web_browser.DocumentText = inject_base_uri(content, base_uri) self.pywebview_window.loaded.clear() def load_url(self, url): self.web_browser.Navigate(url) def on_preview_keydown(self, sender, args): if args.KeyCode == WinForms.Keys.Back: self.cancel_back = True elif args.KeyCode == WinForms.Keys.Delete: self.web_browser.Document.ExecCommand('Delete', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.C: self.web_browser.Document.ExecCommand('Copy', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.X: self.web_browser.Document.ExecCommand('Cut', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.V: self.web_browser.Document.ExecCommand('Paste', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.Z: self.web_browser.Document.ExecCommand('Undo', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.A: self.web_browser.Document.ExecCommand('selectAll', False, None) def on_new_window(self, sender, args): args.Cancel = True webbrowser.open(args.Url) def on_download_complete(self, sender, args): pass def on_navigating(self, sender, args): if self.cancel_back: args.Cancel = True self.cancel_back = False def on_document_completed(self, sender, args): document = self.web_browser.Document document.InvokeScript('eval', (alert.src, )) if _debug: document.InvokeScript('eval', ( 'window.console = { log: function(msg) { window.external.console(JSON.stringify(msg)) }}', )) if self.first_load: self.web_browser.Visible = True self.first_load = False self.url = None if args.Url.AbsoluteUri == 'about:blank' else str( args.Url.AbsoluteUri) document.InvokeScript( 'eval', (parse_api_js(self.pywebview_window, 'mshtml'), )) if not self.pywebview_window.text_select: document.InvokeScript('eval', (disable_text_select, )) self.pywebview_window.loaded.set() if self.pywebview_window.easy_drag: document.MouseMove += self.on_mouse_move def on_mouse_move(self, sender, e): if e.MouseButtonsPressed == WinForms.MouseButtons.Left: WebBrowserEx.ReleaseCapture() windll.user32.SendMessageW(self.form.Handle.ToInt32(), WebBrowserEx.WM_NCLBUTTONDOWN, WebBrowserEx.HT_CAPTION, 6)
class BrowserForm(WinForms.Form): def __init__(self, uid, title, url, width, height, resizable, fullscreen, min_size, confirm_quit, background_color, debug, js_api, text_select, webview_ready): self.uid = uid self.Text = title self.ClientSize = Size(width, height) self.MinimumSize = Size(min_size[0], min_size[1]) self.BackColor = ColorTranslator.FromHtml(background_color) self.AutoScaleDimensions = SizeF(96.0, 96.0) self.AutoScaleMode = WinForms.AutoScaleMode.Dpi if not resizable: self.FormBorderStyle = WinForms.FormBorderStyle.FixedSingle self.MaximizeBox = False # Application icon handle = windll.kernel32.GetModuleHandleW(None) icon_handle = windll.shell32.ExtractIconW(handle, sys.executable, 0) if icon_handle != 0: self.Icon = Icon.FromHandle( IntPtr.op_Explicit(Int32(icon_handle))).Clone() windll.user32.DestroyIcon(icon_handle) self.webview_ready = webview_ready self.load_event = Event() self.web_browser = WebBrowserEx() self.web_browser.Dock = WinForms.DockStyle.Fill self.web_browser.ScriptErrorsSuppressed = not debug self.web_browser.IsWebBrowserContextMenuEnabled = debug self.web_browser.WebBrowserShortcutsEnabled = False self.web_browser.DpiAware = True self.web_browser.ScriptErrorsSuppressed = not debug self.web_browser.IsWebBrowserContextMenuEnabled = debug self.js_result_semaphore = Semaphore(0) self.js_bridge = BrowserView.JSBridge() self.js_bridge.parent_uid = uid self.web_browser.ObjectForScripting = self.js_bridge self.text_select = text_select if js_api: self.js_bridge.api = js_api # HACK. Hiding the WebBrowser is needed in order to show a non-default background color. Tweaking the Visible property # results in showing a non-responsive control, until it is loaded fully. To avoid this, we need to disable this behaviour # for the default background color. if background_color != '#FFFFFF': self.web_browser.Visible = False self.first_load = True else: self.first_load = False self.cancel_back = False self.web_browser.PreviewKeyDown += self.on_preview_keydown self.web_browser.Navigating += self.on_navigating self.web_browser.NewWindow3 += self.on_new_window self.web_browser.DownloadComplete += self.on_download_complete self.web_browser.DocumentCompleted += self.on_document_completed if url: self.web_browser.Navigate(url) else: self.web_browser.DocumentText = default_html self.url = url self.Controls.Add(self.web_browser) self.is_fullscreen = False self.Shown += self.on_shown self.FormClosed += self.on_close if confirm_quit: self.FormClosing += self.on_closing if fullscreen: self.toggle_fullscreen() def _initialize_js(self): self.web_browser.Document.InvokeScript('eval', (alert.src, )) def on_shown(self, sender, args): self.webview_ready.set() def on_close(self, sender, args): del BrowserView.instances[self.uid] if len(BrowserView.instances) == 0: WinForms.Application.Exit() def on_closing(self, sender, args): result = WinForms.MessageBox.Show( localization['global.quitConfirmation'], self.Text, WinForms.MessageBoxButtons.OKCancel, WinForms.MessageBoxIcon.Asterisk) if result == WinForms.DialogResult.Cancel: args.Cancel = True def on_preview_keydown(self, sender, args): if args.KeyCode == WinForms.Keys.Back: self.cancel_back = True elif args.KeyCode == WinForms.Keys.Delete: self.web_browser.Document.ExecCommand('Delete', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.C: self.web_browser.Document.ExecCommand('Copy', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.X: self.web_browser.Document.ExecCommand('Cut', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.V: self.web_browser.Document.ExecCommand('Paste', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.Z: self.web_browser.Document.ExecCommand('Undo', False, None) elif args.Modifiers == WinForms.Keys.Control and args.KeyCode == WinForms.Keys.A: self.web_browser.Document.ExecCommand('selectAll', False, None) def on_new_window(self, sender, args): args.Cancel = True webbrowser.open(args.Url) def on_download_complete(self, sender, args): document = self.web_browser.Document if self.js_bridge.api: document.InvokeScript('eval', (parse_api_js(self.js_bridge.api), )) if not self.text_select: document.InvokeScript('eval', (disable_text_select, )) def on_navigating(self, sender, args): if self.cancel_back: args.Cancel = True self.cancel_back = False def on_document_completed(self, sender, args): self._initialize_js() if self.first_load: self.web_browser.Visible = True self.first_load = False self.load_event.set() def toggle_fullscreen(self): screen = WinForms.Screen.FromControl(self) if not self.is_fullscreen: self.old_size = self.Size self.old_state = self.WindowState self.old_style = self.FormBorderStyle self.old_location = self.Location self.TopMost = True self.FormBorderStyle = 0 # FormBorderStyle.None self.Bounds = WinForms.Screen.PrimaryScreen.Bounds self.WindowState = WinForms.FormWindowState.Maximized self.is_fullscreen = True windll.user32.SetWindowPos(self.Handle.ToInt32(), None, screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height, 64) else: self.TopMost = False self.Size = self.old_size self.WindowState = self.old_state self.FormBorderStyle = self.old_style self.Location = self.old_location self.is_fullscreen = False def set_window_size(self, width, height): windll.user32.SetWindowPos(self.Handle.ToInt32(), None, self.Location.X, self.Location.Y, width, height, 64)