From 788250068f44e44a586dd8d455d1b7382322c442 Mon Sep 17 00:00:00 2001 From: rs <> Date: Thu, 17 Mar 2022 22:08:09 -0500 Subject: [PATCH] Add panes, clickable link list, etc. * Better status handling and reporting * Input support * Clickable links --- cbs.py | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/cbs.py b/cbs.py index e76ce82..c6e0bca 100755 --- a/cbs.py +++ b/cbs.py @@ -3,6 +3,9 @@ import socket, ssl, urllib import PySimpleGUI as sg +urllib.parse.uses_relative.append('gemini') +urllib.parse.uses_netloc.append('gemini') + # ------------------------------------------------------------------------------ # Settings @@ -34,49 +37,91 @@ h1_color = 'green' def gemini_request(url): + """ + Make a request to a Gemini server + :param url: URL to request + :return: Tuple(status:int, meta:str, body:str) + """ + parsed = urllib.parse.urlparse(url) ctxt = ssl.create_default_context() ctxt.check_hostname = False ctxt.verify_mode = ssl.CERT_NONE - with socket.create_connection((parsed.hostname, parsed.port or 1965)) as sock: with ctxt.wrap_socket(sock, server_hostname=parsed.hostname) as ssock: ssock.write((url + '\r\n').encode('utf-8')) resp = b'' while data := ssock.recv(4096): resp += data - return resp + resp = resp.split(b'\r\n', maxsplit=1) + header = resp[0].split(maxsplit=1) + status = int(header[0] if len(header) >= 1 else b'') + meta = header[1] if len(header) >= 2 else b'' + body = resp[1] if len(resp) >= 1 else b'' + return status, meta.decode('utf-8'), body.decode('utf-8') def browser_window_layout(): - return [ - [sg.Button('Back'), sg.Button('Forward'), sg.Text('URL:'), sg.InputText(homepage, key='-URL-'), sg.Button('Go'), sg.Button('Home')], + # Navbar + nav = [sg.Button('Back'), sg.Button('Forward'), sg.Text('URL:'), sg.InputText(homepage, expand_x=True, key='-URL-'), sg.Button('Go'), sg.Button('Home')] + + # Pane contents + overv = [[sg.Listbox([], enable_events=True, horizontal_scroll=True, expand_x=True, expand_y=True, select_mode=sg.LISTBOX_SELECT_MODE_SINGLE, key='-OVERV-')]] + certs = [[sg.Listbox([], enable_events=True, horizontal_scroll=True, expand_x=True, expand_y=True, select_mode=sg.LISTBOX_SELECT_MODE_SINGLE, key='-CERTS-')]] + links = [[sg.Listbox([], enable_events=True, horizontal_scroll=True, expand_x=True, expand_y=True, select_mode=sg.LISTBOX_SELECT_MODE_SINGLE, key='-LINKS-')]] + + # Panes + left_sidebar = sg.Column([[sg.TabGroup([ + [sg.Tab('Overview', overv), sg.Tab('Certs', certs)] + ], expand_x=True, expand_y=True)]]) + content = sg.Column([ [sg.Multiline(size=(100, 100), expand_x=True, expand_y=True, write_only=True, key='-CONTENT-')] + ]) + right_sidebar = sg.Column([[sg.TabGroup([ + [sg.Tab('Links', links)] + ], expand_x=True, expand_y=True)]]) + + # Window + return [ + nav, + [sg.Pane([left_sidebar, content, right_sidebar], orientation='h', expand_x=True, expand_y=True)], ] -def update_content(window): +def update_content(window, content: str): is_format = True window['-CONTENT-'].update('') - content = gemini_request(window['-URL-'].get()).decode('utf-8') + links = [] + overview = [] for line in content.splitlines(keepends=True): if line.startswith('```'): is_format = not is_format continue if not is_format: + # FIXME: This will still word-wrap - probably nothing to be done about that window['-CONTENT-'].update(line, text_color_for_value=preform_color, font_for_value=preform_font, append=True) elif line.startswith('=> '): - window['-CONTENT-'].update(line, text_color_for_value=link_color, font_for_value=link_font, append=True) + splitlink = line.split(maxsplit=2) + link = urllib.parse.urljoin(window['-URL-'].get(), (splitlink[1] if len(splitlink) >= 2 else '').strip()) + text = (splitlink[2] if len(splitlink) >= 3 else '').strip() + window['-CONTENT-'].update('[{}] => {} {}\n'.format(len(links), link, text), text_color_for_value=link_color, font_for_value=link_font, append=True) + links.append((text or link, link)) elif line.startswith('# '): window['-CONTENT-'].update(line[2:], text_color_for_value=h1_color, font_for_value=h1_font, append=True) + overview.append(line) elif line.startswith('## '): window['-CONTENT-'].update(line[3:], text_color_for_value=h2_color, font_for_value=h2_font, append=True) + overview.append(line) elif line.startswith('###'): window['-CONTENT-'].update(line[4:], text_color_for_value=h3_color, font_for_value=h3_font, append=True) + overview.append(line) elif line.startswith('* '): window['-CONTENT-'].update(line, text_color_for_value=list_color, font_for_value=list_font, append=True) else: window['-CONTENT-'].update(line, text_color_for_value=content_color, font_for_value=content_font, append=True) + window['-LINKS-'].update(['{} - {}'.format(i, text) for i, (text, _) in enumerate(links)]) + window['-OVERV-'].update(overview) + return [link for (text, link) in links], overview class History(object): @@ -105,10 +150,9 @@ def main(): hist = History() hist.add(window['-URL-'].get()) window.finalize() - update_content(window) + links, overv = update_content(window, '') while True: event, values = window.read() - print(event, values) if event == sg.WIN_CLOSED: break else: @@ -122,7 +166,31 @@ def main(): window['-URL-'].update(url) elif event == 'Home': window['-URL-'].update(homepage) - update_content(window) + elif event == '-LINKS-': + item = window['-LINKS-'].get_indexes()[0] + window['-URL-'].update(links[item]) + + # Make a request and handle the response + status, meta, body = gemini_request(window['-URL-'].get()) + if 10 <= status < 20: + # Input request + layout = [[sg.Text(meta)], [sg.InputText()], [sg.Submit(), sg.Cancel()]] + event, values = sg.Window('Input Requested', layout).read(close=True) + query = '?' + urllib.parse.quote(values[0]) + window['-URL-'].update(urllib.parse.urljoin(window['-URL-'].get(), query)) + elif 20 <= status < 30: + pass # Success + elif 30 <= status < 40: + body = '# {} - Redirect\n## {}'.format(status, meta) + elif 40 <= status < 50: + body = '# {} - Temporary falure\n## {}'.format(status, meta) + elif 50 <= status <= 60: + body = '# {} - Permanent falure\n## {}'.format(status, meta) + elif 60 <= status < 70: + body = '# {} - Certificate required\n## {}'.format(status, meta) + else: + body = '# {} - Unknown status code\n## {}'.format(status, meta) + links, overv = update_content(window, body) if __name__ == '__main__': -- 2.43.0