diff --git a/README.md b/README.md index 9443314..56bfe9c 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,9 @@ DevDocs desktop application created with GTK3 and Python. To launch the application from the terminal use `devdocs-desktop [STRING]`. If a string is provided, the application will open the first available result page. -## Requirements (for development) +## Requirements -* python (>= 3) -* python-gobject -* webkitgtk (engine for GTK+ 3) - -## To Do - -Add support for offline documentation. + python, python-gobject, webkitgtk ## Installation diff --git a/devdocs_desktop.py b/devdocs_desktop.py index 95edebf..f16730d 100755 --- a/devdocs_desktop.py +++ b/devdocs_desktop.py @@ -1,4 +1,4 @@ -#! /usr/bin/python3 +#! /usr/bin/python import os import gi @@ -9,9 +9,9 @@ gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') gi.require_version('GLib', '2.0') -gi.require_version('WebKit', '3.0') +gi.require_version('WebKit2', '4.0') -from gi.repository import Gtk, Gdk, GLib, WebKit, Soup +from gi.repository import Gtk, Gdk, GLib, WebKit2 class DevdocsDesktop: @@ -23,31 +23,34 @@ def __init__(self): self.args = argparse.ArgumentParser(prog='devdocs-desktop') self.args.add_argument('s', metavar='STR', help='the string to search', nargs='?', default='') - self.app_url = 'https://devdocs.io' - self.do_link = False - self.search = self.args.parse_args().s - self.session = WebKit.get_default_session() + self.app_url = 'https://devdocs.io' + self.search = self.args.parse_args().s + self.open_link = False self.main = Gtk.Builder() self.main.add_from_file(self.file_path('ui/main.ui')) self.main.connect_signals(self) - self.webview = WebKit.WebView() + self.cookies = WebKit2.WebContext.get_default().get_cookie_manager() + self.manager = WebKit2.UserContentManager() + self.webview = WebKit2.WebView.new_with_user_content_manager(self.manager) self.webview.load_uri(self.url_with_search()) - self.webview.connect('navigation-requested', self.on_webview_nav_requested) - self.webview.connect('load-committed', self.on_webview_load_commited) - self.webview.connect('load-finished', self.on_webview_load_finished) - self.webview.connect('title-changed', self.on_webview_title_changed) + self.history = self.webview.get_back_forward_list() + self.history.connect('changed', self.on_history_changed) + + self.webview.connect('notify::uri', self.on_webview_uri_changed) + self.webview.connect('notify::title', self.on_webview_title_changed) + self.webview.connect('decide-policy', self.on_webview_decide_policy) self.webview.connect('context-menu', self.on_webview_context_menu) self.scrolled = self.main.get_object('scrolled_main') self.scrolled.add(self.webview) - self.header_back = self.main.get_object('header_button_back') + self.header_back = self.main.get_object('header_button_back') self.header_forward = self.main.get_object('header_button_forward') - self.header_title = self.main.get_object('header_label_title') - self.header_save = self.main.get_object('header_button_save') + self.header_title = self.main.get_object('header_label_title') + self.header_save = self.main.get_object('header_button_save') self.header_search = self.main.get_object('header_search_entry') self.header_search.get_style_context().remove_class('search') @@ -57,7 +60,7 @@ def __init__(self): self.window.show_all() self.create_settings_path() - self.set_webview_settings() + self.inject_custom_styles() self.enable_persistent_cookies() def run(self): @@ -67,48 +70,36 @@ def quit(self): Gtk.main_quit() def url_with_search(self): - url = self.app_url - - if self.search != '': - url = url + '#q=' + self.search - + url = "%s#q=%s" % (self.app_url, self.search) return url def create_settings_path(self): - directory = self.settings_path() - - if not os.path.exists(directory): - os.makedirs(directory) + if not os.path.exists(self.settings_path()): + os.makedirs(self.settings_path()) def settings_path(self, filepath=''): - root = os.path.expanduser('~') + '/.devdocs-desktop' + root = "%s/.devdocs-desktop" % os.path.expanduser('~') return os.path.join(root, filepath) def file_path(self, filepath): root = os.path.dirname(os.path.realpath(__file__)) return os.path.join(root, filepath) - def set_webview_settings(self): - userstyle = 'file://' + self.file_path('styles/user.css') - settings = self.webview.get_settings() + def inject_custom_styles(self): + style = open(self.file_path('styles/user.css'), 'r').read() + frame = WebKit2.UserContentInjectedFrames.ALL_FRAMES + level = WebKit2.UserStyleLevel.USER + style = WebKit2.UserStyleSheet(style, frame, level, None, None) - settings.set_property('enable-webaudio', True) - settings.set_property('enable-media-stream', True) - settings.set_property('user-stylesheet-uri', userstyle) - settings.set_property('javascript-can-access-clipboard', True) + self.manager.add_style_sheet(style) def enable_persistent_cookies(self): - cookiefile = self.settings_path('cookies.txt') - cookiejar = Soup.CookieJarText.new(cookiefile, False) - cookiejar.set_accept_policy(Soup.CookieJarAcceptPolicy.ALWAYS) - self.session.add_feature(cookiejar) - - def update_history_buttons(self): - back = self.webview.can_go_back() - self.header_back.set_sensitive(back) + filepath = self.settings_path('cookies.txt') + storage = WebKit2.CookiePersistentStorage.TEXT + policy = WebKit2.CookieAcceptPolicy.ALWAYS - forward = self.webview.can_go_forward() - self.header_forward.set_sensitive(forward) + self.cookies.set_accept_policy(policy) + self.cookies.set_persistent_storage(filepath, storage) def toggle_save_button(self, visible): self.header_save.set_visible(visible) @@ -118,8 +109,8 @@ def on_window_main_destroy(self, _event): self.quit() def on_window_main_key_release_event(self, _widget, event): - kname = Gdk.keyval_name(event.keyval) - text = self.header_search.get_text() + kname = Gdk.keyval_name(event.keyval) + text = self.header_search.get_text() visible = self.header_search.get_visible() if kname == 'Escape' and visible: @@ -160,55 +151,60 @@ def on_menu_main_link_clicked(self, widget): link = '' if link == 'home' else link self.header_search.set_text('') - self.js_click_element('a[href="/' + link + '"]') + self.js_open_link(link) def on_header_button_save_clicked(self, _widget): self.toggle_save_button(False) self.js_click_element('._sidebar-footer ._settings-btn') self.header_title.set_label('Downloading...') - def on_webview_nav_requested(self, _widget, _frame, request): - uri = request.get_uri() + def on_webview_decide_policy(self, _widget, decision, dtype): + types = WebKit2.PolicyDecisionType - if self.do_link: - if self.app_url in uri: - link = uri.split(self.app_url)[-1] - self.js_click_element('a[href="' + link + '"]') - else: - webbrowser.open(uri) + if self.open_link and dtype == types.NAVIGATION_ACTION: + self.open_link = False + uri = decision.get_request().get_uri() - return True + if not self.app_url in uri: + decision.ignore() + webbrowser.open(uri) - self.do_link = False - return False + def on_webview_title_changed(self, _widget, _title): + title = self.webview.get_title() + self.header_title.set_label(title) - def on_webview_load_commited(self, _widget, frame): - self.do_link = False - self.update_history_buttons() - self.toggle_save_button(frame.get_uri().endswith('settings')) + def on_webview_uri_changed(self, _widget, _uri): + save = self.webview.get_uri().endswith('settings') + self.toggle_save_button(save) - def on_webview_load_finished(self, _widget, frame): - self.update_history_buttons() - self.toggle_save_button(frame.get_uri().endswith('settings')) + def on_history_changed(self, _list, _added, _removed): + back = self.webview.can_go_back() + self.header_back.set_sensitive(back) - def on_webview_title_changed(self, _widget, _frame, title): - self.header_title.set_label(title) + forward = self.webview.can_go_forward() + self.header_forward.set_sensitive(forward) - def on_webview_open_link(self, _widget): - self.do_link = True + def on_webview_open_link(self, action): + self.open_link = True def on_webview_context_menu(self, _widget, menu, _coords, _keyboard): - for item in menu.get_children(): - label = item.get_label() - lnk_open = '_Open' in label - new_open = '_Window' in label - download = '_Download' in label + actions = WebKit2.ContextMenuAction + include = [ + actions.GO_BACK, actions.GO_FORWARD, actions.STOP, actions.RELOAD, + actions.COPY, actions.CUT, actions.PASTE, actions.DELETE, actions.SELECT_ALL, + actions.OPEN_LINK, actions.COPY_LINK_TO_CLIPBOARD, + actions.COPY_IMAGE_TO_CLIPBOARD, actions.COPY_IMAGE_URL_TO_CLIPBOARD, + actions.COPY_VIDEO_LINK_TO_CLIPBOARD, actions.COPY_AUDIO_LINK_TO_CLIPBOARD + ] + + for item in menu.get_items(): + action = item.get_stock_action() - if new_open or download: - item.destroy() + if not action in include: + menu.remove(item) - if lnk_open: - item.connect('select', self.on_webview_open_link) + if action == actions.OPEN_LINK: + item.get_action().connect('activate', self.on_webview_open_link) def js_form_input(self, text): script = """ @@ -218,15 +214,17 @@ def js_form_input(self, text): if (fi) { fi.value = '%s' }; if (fe) { fe.dispatchEvent(ev); } """ - script = script % (text) - self.webview.execute_script(script) + script = script % text + self.webview.run_javascript(script) def js_click_element(self, selector): - script = "var sl = $('%s'); if (sl) { sl.click(); }" - script = script % (selector) + script = "var sl = $('%s'); if (sl) { sl.click(); }" % selector + self.webview.run_javascript(script) - self.webview.execute_script(script) + def js_open_link(self, link): + link = """a[href="/%s"]""" % link.split(self.app_url)[-1] + self.js_click_element(link) if __name__ == '__main__': diff --git a/screenshot.png b/screenshot.png index 4053d8c..e9526b2 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/ui/main.ui b/ui/main.ui index 878ef1d..598902a 100644 --- a/ui/main.ui +++ b/ui/main.ui @@ -43,7 +43,6 @@ True - False True True Offline Data @@ -289,6 +288,7 @@ True + False True True @@ -309,6 +309,7 @@ True + False True True