]> git.the-white-hart.net Git - gemini/cbs-client.git/commitdiff
Add panes, clickable link list, etc.
authorrs <>
Fri, 18 Mar 2022 03:08:09 +0000 (22:08 -0500)
committerrs <>
Fri, 18 Mar 2022 03:08:09 +0000 (22:08 -0500)
* Better status handling and reporting
* Input support
* Clickable links

cbs.py

diff --git a/cbs.py b/cbs.py
index e76ce8285c370677a39b2c93aecc0b90e5ec8b2f..c6e0bcaa2d22b92cd8c6f130df6c0f0a7127c8ea 100755 (executable)
--- 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__':