develop #1
| @@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0') | |||||||
| from gi.repository import Gtk | from gi.repository import Gtk | ||||||
|  |  | ||||||
| # Application imports | # Application imports | ||||||
|  | from ..widgets.webkit_ui import WebkitUI | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -41,6 +42,7 @@ class CenterContainer(Gtk.Box): | |||||||
|  |  | ||||||
|         self.add(button) |         self.add(button) | ||||||
|         self.add(glade_box) |         self.add(glade_box) | ||||||
|  |         self.add( WebkitUI() ) | ||||||
|  |  | ||||||
|     def _hello_world(self, widget = None, eve = None): |     def _hello_world(self, widget = None, eve = None): | ||||||
|         logger.debug("Hello, World!") |         logger.debug("Hello, World!") | ||||||
							
								
								
									
										82
									
								
								src/core/widgets/webkit_ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/core/widgets/webkit_ui.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  | import gi | ||||||
|  | gi.require_version('Gdk', '3.0') | ||||||
|  | gi.require_version('WebKit2', '4.0') | ||||||
|  | from gi.repository import Gdk | ||||||
|  | from gi.repository import WebKit2 | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUI(WebKit2.WebView): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUI, self).__init__() | ||||||
|  |  | ||||||
|  |         self._setup_styling() | ||||||
|  |         self._subscribe_to_events() | ||||||
|  |         self._load_view() | ||||||
|  |         self._setup_content_manager() | ||||||
|  |          | ||||||
|  |         self.show_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def _setup_styling(self): | ||||||
|  |         self.set_vexpand(True) | ||||||
|  |         self.set_hexpand(True) | ||||||
|  |         self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) | ||||||
|  |  | ||||||
|  |     def _subscribe_to_events(self): | ||||||
|  |         # event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) | ||||||
|  |         ... | ||||||
|  |      | ||||||
|  |     def _load_settings(self): | ||||||
|  |         self.set_settings( WebkitUISettings() ) | ||||||
|  |  | ||||||
|  |     def _load_view(self): | ||||||
|  |         path = settings_manager.get_context_path() | ||||||
|  |         data = settings_manager.wrap_html_to_body("") | ||||||
|  |         self.load_html(content = data, base_uri = f"file://{path}/") | ||||||
|  |  | ||||||
|  |     def _setup_content_manager(self): | ||||||
|  |         content_manager = self.get_user_content_manager() | ||||||
|  |         content_manager.register_script_message_handler("backend") | ||||||
|  |         content_manager.connect("script-message-received", self._process_js_message) | ||||||
|  |  | ||||||
|  |     def _process_js_message(self, user_content_manager, js_result): | ||||||
|  |         js_value = js_result.get_js_value() | ||||||
|  |         print(js_value.to_string()) | ||||||
|  |         # self._web_view.run_javascript("do_stuff()", None, None) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class WebkitUISettings(WebKit2.Settings): | ||||||
|  |     def __init__(self): | ||||||
|  |         super(WebkitUISettings, self).__init__() | ||||||
|  |          | ||||||
|  |         self._set_default_settings() | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     # Note: Highly insecure setup but most "app" like setup I could think of. | ||||||
|  |     #       Audit heavily any scripts/links ran/clicked under this setup!  | ||||||
|  |     def _set_default_settings(self): | ||||||
|  |         self.set_enable_offline_web_application_cache(False) | ||||||
|  |         self.enable_html5_local_storage(False) | ||||||
|  |         self.enable_html5_database(False) | ||||||
|  |         self.enable_xss_auditor(False) | ||||||
|  |         self.set_enable_hyperlink_auditing(False) | ||||||
|  |         self.set_enable_tabs_to_links(False) | ||||||
|  |         self.enable_fullscreen(False) | ||||||
|  |         self.set_print_backgrounds(False) | ||||||
|  |         self.enable_webaudio(False) | ||||||
|  |         self.set_enable_page_cache(False) | ||||||
|  |  | ||||||
|  |         self.enable_accelerated_2d_canvas(True) | ||||||
|  |         self.set_allow_file_access_from_file_urls(True) | ||||||
|  |         self.set_allow_universal_access_from_file_urls(True) | ||||||
|  |         self.set_enable_webrtc(True) | ||||||
|  |  | ||||||
|  |         sdelf.set_user_agent(f"{app_name}") | ||||||
| @@ -13,6 +13,7 @@ from os import mkdir | |||||||
| # Application imports | # Application imports | ||||||
| from ..singleton import Singleton | from ..singleton import Singleton | ||||||
| from .start_check_mixin import StartCheckMixin | from .start_check_mixin import StartCheckMixin | ||||||
|  | from .markdown_template_mixin import MarkdownTemplateMixin | ||||||
| from .options.settings import Settings | from .options.settings import Settings | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -22,7 +23,7 @@ class MissingConfigError(Exception): | |||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class SettingsManager(StartCheckMixin, Singleton): | class SettingsManager(StartCheckMixin, MarkdownTemplateMixin, Singleton): | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self._SCRIPT_PTH        = path.dirname(path.realpath(__file__)) |         self._SCRIPT_PTH        = path.dirname(path.realpath(__file__)) | ||||||
|         self._USER_HOME         = path.expanduser('~') |         self._USER_HOME         = path.expanduser('~') | ||||||
| @@ -30,6 +31,7 @@ class SettingsManager(StartCheckMixin, Singleton): | |||||||
|         self._USR_PATH          = f"/usr/share/{app_name.lower()}" |         self._USR_PATH          = f"/usr/share/{app_name.lower()}" | ||||||
|         self._USR_CONFIG_FILE   = f"{self._USR_PATH}/settings.json" |         self._USR_CONFIG_FILE   = f"{self._USR_PATH}/settings.json" | ||||||
|  |  | ||||||
|  |         self._CONTEXT_PATH      = f"{self._HOME_CONFIG_PATH}/context_path" | ||||||
|         self._PLUGINS_PATH      = f"{self._HOME_CONFIG_PATH}/plugins" |         self._PLUGINS_PATH      = f"{self._HOME_CONFIG_PATH}/plugins" | ||||||
|         self._DEFAULT_ICONS     = f"{self._HOME_CONFIG_PATH}/icons" |         self._DEFAULT_ICONS     = f"{self._HOME_CONFIG_PATH}/icons" | ||||||
|         self._CONFIG_FILE       = f"{self._HOME_CONFIG_PATH}/settings.json" |         self._CONFIG_FILE       = f"{self._HOME_CONFIG_PATH}/settings.json" | ||||||
| @@ -146,6 +148,7 @@ class SettingsManager(StartCheckMixin, Singleton): | |||||||
|     def get_ui_widgets_path(self)    -> str: return self._UI_WIDEGTS_PATH |     def get_ui_widgets_path(self)    -> str: return self._UI_WIDEGTS_PATH | ||||||
|     def get_context_menu_data(self)  -> str: return self._context_menu_data |     def get_context_menu_data(self)  -> str: return self._context_menu_data | ||||||
|  |  | ||||||
|  |     def get_context_path(self)     -> str:   return self._CONTEXT_PATH | ||||||
|     def get_plugins_path(self)     -> str:   return self._PLUGINS_PATH |     def get_plugins_path(self)     -> str:   return self._PLUGINS_PATH | ||||||
|     def get_icon_theme(self)       -> str:   return self._ICON_THEME |     def get_icon_theme(self)       -> str:   return self._ICON_THEME | ||||||
|     def get_css_file(self)         -> str:   return self._CSS_FILE |     def get_css_file(self)         -> str:   return self._CSS_FILE | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								src/utils/settings_manager/markdown_template_mixin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/utils/settings_manager/markdown_template_mixin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | # Python imports | ||||||
|  |  | ||||||
|  | # Lib imports | ||||||
|  |  | ||||||
|  | # Application imports | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MarkdownTemplateMixin: | ||||||
|  |     def wrap_html_to_body(self, html): | ||||||
|  |         return f"""\ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en" dir="ltr"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="utf-8"> | ||||||
|  |     <title>Markdown View</title> | ||||||
|  |     <style media="screen"> | ||||||
|  |         html, body {{ | ||||||
|  |             display: block; | ||||||
|  |             background-color: #32383e00; | ||||||
|  |             color: #ffffff; | ||||||
|  |             text-wrap: wrap; | ||||||
|  |         }} | ||||||
|  |          | ||||||
|  |         img {{ | ||||||
|  |             width: 100%; | ||||||
|  |             height: auto; | ||||||
|  |         }} | ||||||
|  |          | ||||||
|  |         code {{ | ||||||
|  |             border: 1px solid #32383e; | ||||||
|  |             background-color: #32383e; | ||||||
|  |             padding: 4px; | ||||||
|  |         }} | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |  | ||||||
|  |     {html} | ||||||
|  |     <span>Button in WebView | ||||||
|  |         <button onclick="say_hello()">Button in WebView</button> | ||||||
|  |     </span> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <!-- For internal scripts... --> | ||||||
|  |     <script src="js/libs/jquery-3.7.1.min.js"></script> | ||||||
|  |  | ||||||
|  |     <!-- For Bootstrap... --> | ||||||
|  |     <script src="resources/js/libs/bootstrap5/bootstrap.bundle.min.js"></script> | ||||||
|  |  | ||||||
|  |     <!-- For Application... --> | ||||||
|  |     <script src="resources/js/ui-logic.js"></script> | ||||||
|  |     <script src="resources/js/post-ajax.js"></script> | ||||||
|  |     <script src="resources/js/ajax.js"></script> | ||||||
|  |     <script src="resources/js/events.js"></script> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
|  |  | ||||||
|  | """ | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | const logUIExceptionAjax = (data, action = "client-exception-logger") => { | ||||||
|  |     const postArgs = 'exception_data=' + data; | ||||||
|  |  | ||||||
|  |     messenger.backend.postMessage(postArgs); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | const doAjax = (actionPath, data, action) => { | ||||||
|  |     let xhttp = new XMLHttpRequest(); | ||||||
|  |  | ||||||
|  |     xhttp.onreadystatechange = function() { | ||||||
|  |         if (this.readyState === 4 && this.status === 200) { | ||||||
|  |             if (this.responseText != null) {  // this.responseXML if getting XML data | ||||||
|  |                 postAjaxController(JSON.parse(this.responseText), action); | ||||||
|  |             } else { | ||||||
|  |                 let type = "danger" | ||||||
|  |                 let msg  = "No content returned. Check the target path."; | ||||||
|  |                 data     = '{"message": { "type": "' + type +  '", "text": "' + text + '" } }' | ||||||
|  |                 postAjaxController(JSON.parse(data)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     xhttp.open("POST", actionPath, true); | ||||||
|  |     xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); | ||||||
|  |     // Force return to be JSON NOTE: Use application/xml to force XML | ||||||
|  |     xhttp.overrideMimeType('application/json'); | ||||||
|  |     xhttp.send(data); | ||||||
|  | } | ||||||
|  |   | ||||||
|  | const formatURL = (basePath) => { | ||||||
|  |     url = window.location.href; | ||||||
|  |     if ( url.endsWith('/') ) | ||||||
|  |         return url + basePath; | ||||||
|  |     else | ||||||
|  |         return url + '/' + basePath; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const fetchData = async (url) => { | ||||||
|  |     let response = null; | ||||||
|  |     response = await fetch(url); | ||||||
|  |     return await response.json(); | ||||||
|  | } | ||||||
| @@ -0,0 +1,28 @@ | |||||||
|  | const menu = document.querySelector(".menu"); | ||||||
|  | let menuVisible = false; | ||||||
|  |  | ||||||
|  | const toggleMenu = command => { | ||||||
|  |     menu.style.display = command === "show" ? "block" : "none"; | ||||||
|  |     menu.style.zIndex  = "9999"; | ||||||
|  |     menuVisible        = !menuVisible; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const setPosition = ({ top, left }) => { | ||||||
|  |     menu.style.left = `${left}px`; | ||||||
|  |     menu.style.top  = `${top}px`; | ||||||
|  |     toggleMenu("show"); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | window.addEventListener("click", e => { | ||||||
|  |     if(menuVisible) toggleMenu("hide"); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | window.addEventListener("contextmenu", e => { | ||||||
|  |     e.preventDefault(); | ||||||
|  |     const origin = { | ||||||
|  |         left: e.pageX, | ||||||
|  |         top: e.pageY | ||||||
|  |     }; | ||||||
|  |     setPosition(origin); | ||||||
|  |     return false; | ||||||
|  | }); | ||||||
| @@ -0,0 +1,22 @@ | |||||||
|  | const messenger = window.webkit.messageHandlers | ||||||
|  |  | ||||||
|  | window.onload = (eve) => { | ||||||
|  |     console.log("Loaded..."); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | window.onerror = function(msg, url, line, col, error) { | ||||||
|  |    // Note that col & error are new to the HTML 5 spec and may not be supported in every browser. | ||||||
|  |    const suppressErrorAlert = false; | ||||||
|  |    let extra = !col ? '' : '\ncolumn: ' + col; | ||||||
|  |    extra += !error ? '' : '\nerror: ' + error; | ||||||
|  |    const data = `Error:  ${msg} \nurl: ${url} \nline: ${line}  ${extra}` | ||||||
|  |  | ||||||
|  |    logUIExceptionAjax(data); | ||||||
|  |  | ||||||
|  |    // If you return true, then error alerts (like in older versions of Internet Explorer) will be suppressed. | ||||||
|  |    return suppressErrorAlert; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const say_hello = () => { | ||||||
|  |     messenger.backend.postMessage("Hello, World!"); | ||||||
|  | } | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								user_config/usr/share/app_name/context_path/resources/js/libs/jquery-3.7.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								user_config/usr/share/app_name/context_path/resources/js/libs/jquery-3.7.1.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | const postAjaxController = (data, action) => { | ||||||
|  |     if (data.message) { | ||||||
|  |         message = data.message | ||||||
|  |  | ||||||
|  |         if (action == "someAction" && message.type.includes("success")) { | ||||||
|  |             console.log("Success!"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         displayMessage(message.text, message.type, 0); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | // Message handler | ||||||
|  | const displayMessage = (message, type, timeout, msgWindow = "page-alert-zone") => { | ||||||
|  |     let alertField  = document.getElementById(msgWindow); | ||||||
|  |     let divElm      = document.createElement("DIV"); | ||||||
|  |     let btnElm      = document.createElement("BUTTON"); | ||||||
|  |     let spnElm      = document.createElement("SPAN"); | ||||||
|  |     let textElm     = document.createTextNode(message); | ||||||
|  |  | ||||||
|  |     divElm.setAttribute("class", "alert alert-dismissible fade show alert-" + type); | ||||||
|  |     divElm.setAttribute("role", "alert"); | ||||||
|  |     divElm.appendChild(textElm); | ||||||
|  |     btnElm.type     = "button"; | ||||||
|  |     textElm         = document.createTextNode("X"); | ||||||
|  |     btnElm.setAttribute("class", "btn-dark btn-close"); | ||||||
|  |     btnElm.setAttribute("data-bs-dismiss", "alert"); | ||||||
|  |     btnElm.setAttribute("aria-label", "close"); | ||||||
|  |     spnElm.setAttribute("aria-hidden", "true"); | ||||||
|  |     spnElm.appendChild(textElm); | ||||||
|  |     btnElm.appendChild(spnElm); | ||||||
|  |     divElm.appendChild(btnElm); | ||||||
|  |     alertField.appendChild(divElm); | ||||||
|  |  | ||||||
|  |     if (timeout > 0) { | ||||||
|  |         setTimeout(function () { | ||||||
|  |             clearChildNodes(alertField); | ||||||
|  |         }, timeout * 1000); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const clearChildNodes = (parent) => { | ||||||
|  |     while (parent.firstChild) { | ||||||
|  |         parent.removeChild(parent.firstChild); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // Cache Buster | ||||||
|  | const clearCache = () => { | ||||||
|  |     const rep = /.*\?.*/; | ||||||
|  |     let links     = document.getElementsByTagName('link'), | ||||||
|  |         scripts   = document.getElementsByTagName('script'), | ||||||
|  |         video     = document.getElementsByTagName('video'), | ||||||
|  |         process_scripts = false; | ||||||
|  |  | ||||||
|  |     for (let i = 0; i < links.length; i++) { | ||||||
|  |         let link = links[i], | ||||||
|  |         href = link.href; | ||||||
|  |         if(rep.test(href)) { | ||||||
|  |             link.href = href + '&' + Date.now(); | ||||||
|  |         } else { | ||||||
|  |             link.href = href + '?' + Date.now(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  |     if(process_scripts) { | ||||||
|  |         for (let i = 0; i < scripts.length; i++) { | ||||||
|  |             let script = scripts[i], | ||||||
|  |             src = script.src; | ||||||
|  |             if(rep.test(src)) { | ||||||
|  |                 script.src = src+'&'+Date.now(); | ||||||
|  |             } else { | ||||||
|  |                 script.src = src+'?'+Date.now(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user