Skip to content

Commit 43e4601

Browse files
committed
update to webkit2
support for offline docs optimize code
1 parent 55f685c commit 43e4601

File tree

4 files changed

+84
-91
lines changed

4 files changed

+84
-91
lines changed

README.md

+2-8
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,9 @@ DevDocs desktop application created with GTK3 and Python.
99
To launch the application from the terminal use `devdocs-desktop [STRING]`.
1010
If a string is provided, the application will open the first available result page.
1111

12-
## Requirements (for development)
12+
## Requirements
1313

14-
* python (>= 3)
15-
* python-gobject
16-
* webkitgtk (engine for GTK+ 3)
17-
18-
## To Do
19-
20-
Add support for offline documentation.
14+
python, python-gobject, webkitgtk
2115

2216
## Installation
2317

devdocs_desktop.py

+80-82
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#! /usr/bin/python3
1+
#! /usr/bin/python
22

33
import os
44
import gi
@@ -9,9 +9,9 @@
99
gi.require_version('Gtk', '3.0')
1010
gi.require_version('Gdk', '3.0')
1111
gi.require_version('GLib', '2.0')
12-
gi.require_version('WebKit', '3.0')
12+
gi.require_version('WebKit2', '4.0')
1313

14-
from gi.repository import Gtk, Gdk, GLib, WebKit, Soup
14+
from gi.repository import Gtk, Gdk, GLib, WebKit2
1515

1616

1717
class DevdocsDesktop:
@@ -23,31 +23,34 @@ def __init__(self):
2323
self.args = argparse.ArgumentParser(prog='devdocs-desktop')
2424
self.args.add_argument('s', metavar='STR', help='the string to search', nargs='?', default='')
2525

26-
self.app_url = 'https://devdocs.io'
27-
self.do_link = False
28-
self.search = self.args.parse_args().s
29-
self.session = WebKit.get_default_session()
26+
self.app_url = 'https://devdocs.io'
27+
self.search = self.args.parse_args().s
28+
self.open_link = False
3029

3130
self.main = Gtk.Builder()
3231
self.main.add_from_file(self.file_path('ui/main.ui'))
3332
self.main.connect_signals(self)
3433

35-
self.webview = WebKit.WebView()
34+
self.cookies = WebKit2.WebContext.get_default().get_cookie_manager()
35+
self.manager = WebKit2.UserContentManager()
36+
self.webview = WebKit2.WebView.new_with_user_content_manager(self.manager)
3637
self.webview.load_uri(self.url_with_search())
3738

38-
self.webview.connect('navigation-requested', self.on_webview_nav_requested)
39-
self.webview.connect('load-committed', self.on_webview_load_commited)
40-
self.webview.connect('load-finished', self.on_webview_load_finished)
41-
self.webview.connect('title-changed', self.on_webview_title_changed)
39+
self.history = self.webview.get_back_forward_list()
40+
self.history.connect('changed', self.on_history_changed)
41+
42+
self.webview.connect('notify::uri', self.on_webview_uri_changed)
43+
self.webview.connect('notify::title', self.on_webview_title_changed)
44+
self.webview.connect('decide-policy', self.on_webview_decide_policy)
4245
self.webview.connect('context-menu', self.on_webview_context_menu)
4346

4447
self.scrolled = self.main.get_object('scrolled_main')
4548
self.scrolled.add(self.webview)
4649

47-
self.header_back = self.main.get_object('header_button_back')
50+
self.header_back = self.main.get_object('header_button_back')
4851
self.header_forward = self.main.get_object('header_button_forward')
49-
self.header_title = self.main.get_object('header_label_title')
50-
self.header_save = self.main.get_object('header_button_save')
52+
self.header_title = self.main.get_object('header_label_title')
53+
self.header_save = self.main.get_object('header_button_save')
5154

5255
self.header_search = self.main.get_object('header_search_entry')
5356
self.header_search.get_style_context().remove_class('search')
@@ -57,7 +60,7 @@ def __init__(self):
5760
self.window.show_all()
5861

5962
self.create_settings_path()
60-
self.set_webview_settings()
63+
self.inject_custom_styles()
6164
self.enable_persistent_cookies()
6265

6366
def run(self):
@@ -67,48 +70,36 @@ def quit(self):
6770
Gtk.main_quit()
6871

6972
def url_with_search(self):
70-
url = self.app_url
71-
72-
if self.search != '':
73-
url = url + '#q=' + self.search
74-
73+
url = "%s#q=%s" % (self.app_url, self.search)
7574
return url
7675

7776
def create_settings_path(self):
78-
directory = self.settings_path()
79-
80-
if not os.path.exists(directory):
81-
os.makedirs(directory)
77+
if not os.path.exists(self.settings_path()):
78+
os.makedirs(self.settings_path())
8279

8380
def settings_path(self, filepath=''):
84-
root = os.path.expanduser('~') + '/.devdocs-desktop'
81+
root = "%s/.devdocs-desktop" % os.path.expanduser('~')
8582
return os.path.join(root, filepath)
8683

8784
def file_path(self, filepath):
8885
root = os.path.dirname(os.path.realpath(__file__))
8986
return os.path.join(root, filepath)
9087

91-
def set_webview_settings(self):
92-
userstyle = 'file://' + self.file_path('styles/user.css')
93-
settings = self.webview.get_settings()
88+
def inject_custom_styles(self):
89+
style = open(self.file_path('styles/user.css'), 'r').read()
90+
frame = WebKit2.UserContentInjectedFrames.ALL_FRAMES
91+
level = WebKit2.UserStyleLevel.USER
92+
style = WebKit2.UserStyleSheet(style, frame, level, None, None)
9493

95-
settings.set_property('enable-webaudio', True)
96-
settings.set_property('enable-media-stream', True)
97-
settings.set_property('user-stylesheet-uri', userstyle)
98-
settings.set_property('javascript-can-access-clipboard', True)
94+
self.manager.add_style_sheet(style)
9995

10096
def enable_persistent_cookies(self):
101-
cookiefile = self.settings_path('cookies.txt')
102-
cookiejar = Soup.CookieJarText.new(cookiefile, False)
103-
cookiejar.set_accept_policy(Soup.CookieJarAcceptPolicy.ALWAYS)
104-
self.session.add_feature(cookiejar)
105-
106-
def update_history_buttons(self):
107-
back = self.webview.can_go_back()
108-
self.header_back.set_sensitive(back)
97+
filepath = self.settings_path('cookies.txt')
98+
storage = WebKit2.CookiePersistentStorage.TEXT
99+
policy = WebKit2.CookieAcceptPolicy.ALWAYS
109100

110-
forward = self.webview.can_go_forward()
111-
self.header_forward.set_sensitive(forward)
101+
self.cookies.set_accept_policy(policy)
102+
self.cookies.set_persistent_storage(filepath, storage)
112103

113104
def toggle_save_button(self, visible):
114105
self.header_save.set_visible(visible)
@@ -118,8 +109,8 @@ def on_window_main_destroy(self, _event):
118109
self.quit()
119110

120111
def on_window_main_key_release_event(self, _widget, event):
121-
kname = Gdk.keyval_name(event.keyval)
122-
text = self.header_search.get_text()
112+
kname = Gdk.keyval_name(event.keyval)
113+
text = self.header_search.get_text()
123114
visible = self.header_search.get_visible()
124115

125116
if kname == 'Escape' and visible:
@@ -160,55 +151,60 @@ def on_menu_main_link_clicked(self, widget):
160151
link = '' if link == 'home' else link
161152

162153
self.header_search.set_text('')
163-
self.js_click_element('a[href="/' + link + '"]')
154+
self.js_open_link(link)
164155

165156
def on_header_button_save_clicked(self, _widget):
166157
self.toggle_save_button(False)
167158
self.js_click_element('._sidebar-footer ._settings-btn')
168159
self.header_title.set_label('Downloading...')
169160

170-
def on_webview_nav_requested(self, _widget, _frame, request):
171-
uri = request.get_uri()
161+
def on_webview_decide_policy(self, _widget, decision, dtype):
162+
types = WebKit2.PolicyDecisionType
172163

173-
if self.do_link:
174-
if self.app_url in uri:
175-
link = uri.split(self.app_url)[-1]
176-
self.js_click_element('a[href="' + link + '"]')
177-
else:
178-
webbrowser.open(uri)
164+
if self.open_link and dtype == types.NAVIGATION_ACTION:
165+
self.open_link = False
166+
uri = decision.get_request().get_uri()
179167

180-
return True
168+
if not self.app_url in uri:
169+
decision.ignore()
170+
webbrowser.open(uri)
181171

182-
self.do_link = False
183-
return False
172+
def on_webview_title_changed(self, _widget, _title):
173+
title = self.webview.get_title()
174+
self.header_title.set_label(title)
184175

185-
def on_webview_load_commited(self, _widget, frame):
186-
self.do_link = False
187-
self.update_history_buttons()
188-
self.toggle_save_button(frame.get_uri().endswith('settings'))
176+
def on_webview_uri_changed(self, _widget, _uri):
177+
save = self.webview.get_uri().endswith('settings')
178+
self.toggle_save_button(save)
189179

190-
def on_webview_load_finished(self, _widget, frame):
191-
self.update_history_buttons()
192-
self.toggle_save_button(frame.get_uri().endswith('settings'))
180+
def on_history_changed(self, _list, _added, _removed):
181+
back = self.webview.can_go_back()
182+
self.header_back.set_sensitive(back)
193183

194-
def on_webview_title_changed(self, _widget, _frame, title):
195-
self.header_title.set_label(title)
184+
forward = self.webview.can_go_forward()
185+
self.header_forward.set_sensitive(forward)
196186

197-
def on_webview_open_link(self, _widget):
198-
self.do_link = True
187+
def on_webview_open_link(self, action):
188+
self.open_link = True
199189

200190
def on_webview_context_menu(self, _widget, menu, _coords, _keyboard):
201-
for item in menu.get_children():
202-
label = item.get_label()
203-
lnk_open = '_Open' in label
204-
new_open = '_Window' in label
205-
download = '_Download' in label
191+
actions = WebKit2.ContextMenuAction
192+
include = [
193+
actions.GO_BACK, actions.GO_FORWARD, actions.STOP, actions.RELOAD,
194+
actions.COPY, actions.CUT, actions.PASTE, actions.DELETE, actions.SELECT_ALL,
195+
actions.OPEN_LINK, actions.COPY_LINK_TO_CLIPBOARD,
196+
actions.COPY_IMAGE_TO_CLIPBOARD, actions.COPY_IMAGE_URL_TO_CLIPBOARD,
197+
actions.COPY_VIDEO_LINK_TO_CLIPBOARD, actions.COPY_AUDIO_LINK_TO_CLIPBOARD
198+
]
199+
200+
for item in menu.get_items():
201+
action = item.get_stock_action()
206202

207-
if new_open or download:
208-
item.destroy()
203+
if not action in include:
204+
menu.remove(item)
209205

210-
if lnk_open:
211-
item.connect('select', self.on_webview_open_link)
206+
if action == actions.OPEN_LINK:
207+
item.get_action().connect('activate', self.on_webview_open_link)
212208

213209
def js_form_input(self, text):
214210
script = """
@@ -218,15 +214,17 @@ def js_form_input(self, text):
218214
if (fi) { fi.value = '%s' };
219215
if (fe) { fe.dispatchEvent(ev); }
220216
"""
221-
script = script % (text)
222217

223-
self.webview.execute_script(script)
218+
script = script % text
219+
self.webview.run_javascript(script)
224220

225221
def js_click_element(self, selector):
226-
script = "var sl = $('%s'); if (sl) { sl.click(); }"
227-
script = script % (selector)
222+
script = "var sl = $('%s'); if (sl) { sl.click(); }" % selector
223+
self.webview.run_javascript(script)
228224

229-
self.webview.execute_script(script)
225+
def js_open_link(self, link):
226+
link = """a[href="/%s"]""" % link.split(self.app_url)[-1]
227+
self.js_click_element(link)
230228

231229

232230
if __name__ == '__main__':

screenshot.png

-10.1 KB
Loading

ui/main.ui

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
<child>
4444
<object class="GtkModelButton" id="menu_main_offline">
4545
<property name="visible">True</property>
46-
<property name="sensitive">False</property>
4746
<property name="can_focus">True</property>
4847
<property name="receives_default">True</property>
4948
<property name="text" translatable="yes">Offline Data</property>
@@ -289,6 +288,7 @@
289288
<child>
290289
<object class="GtkButton" id="header_button_back">
291290
<property name="visible">True</property>
291+
<property name="sensitive">False</property>
292292
<property name="can_focus">True</property>
293293
<property name="receives_default">True</property>
294294
<signal name="clicked" handler="on_header_button_back_clicked" swapped="no"/>
@@ -309,6 +309,7 @@
309309
<child>
310310
<object class="GtkButton" id="header_button_forward">
311311
<property name="visible">True</property>
312+
<property name="sensitive">False</property>
312313
<property name="can_focus">True</property>
313314
<property name="receives_default">True</property>
314315
<signal name="clicked" handler="on_header_button_forward_clicked" swapped="no"/>

0 commit comments

Comments
 (0)