diff --git a/BOARD.txt b/BOARD.txt index 7c16593..f579334 100644 --- a/BOARD.txt +++ b/BOARD.txt @@ -4,7 +4,7 @@ TODO DONE links redirections web links - history (back/forward) + history (back) simple caching simple text files encodings @@ -14,21 +14,26 @@ TODO DONE configuration help page TOFU -open last download + view history + open last download + media files home page -media files -view history identity management -------------------------------------------------------------------------------- BACKLOG click on links to open them download to disk, not in memory +download in the background +download view instead of last download does encoding really work? cf. egsam margins / centering pre blocks folding buffers (tabs) +a11y? tts? handle soft-hyphens on wrapping bug: combining chars reduce lengths non shit command-line response code 11 (if still there) gopher? +save history +history (forward) (useful?) diff --git a/README.md b/README.md index 1fd792c..9d5da28 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ It also provide these features: - History - Caching -- Bookmarks: it's just a text file with bindings. +- Bookmarks (it's just a text file with bindings) - Downloads Check out [this board](BOARD.txt) for what's done and coming next. @@ -73,12 +73,14 @@ you want, just restart Bebop to take changes into account. Here are the available options: -| Key | Type | Default | Description | -|-------------------|-------------|----------|---------------------------------------| -| `connect_timeout` | int | 10 | Seconds before connection times out. | -| `text_width` | int | 80 | Rendered line length. | -| `source_editor` | string list | `["vi"]` | Command to use for editing sources. | -| `command_editor` | string list | `["vi"]` | Command to use for editing CLI input. | +| Key | Type | Default | Description | +|----------------------------|--------------|----------------|---------------------------------------| +| `connect_timeout` | int | 10 | Seconds before connection times out. | +| `text_width` | int | 80 | Rendered line length. | +| `source_editor` | string list | `["vi"]` | Command to use for editing sources. | +| `command_editor` | string list | `["vi"]` | Command to use for editing CLI input. | +| `external_commands` | (see note 2) | {} | Commands to open various files. | +| `external_command_default` | string list | `["xdg-open"]` | Default command to open files. | Note: for the "command" parameters such as `source_editor` and `command_editor`, a string list is used to separate the different program arguments, e.g. if you @@ -86,6 +88,12 @@ wish to use `vim -c 'startinsert'`, you should write the list `["vim", "-c", "startinsert"]`. In both case, a temporary or regular file name will be appended to this command when run. +2: the `external_commands` dict maps MIME types to commands just as above. For +example, if you want to open video files with VLC and audio files in Clementine, +you can use the following dict: `{"audio": ["clementine"], "video", ["vlc"]}`. +For now only "main" MIME types are supported, i.e. you cannot specify precise +types like "audio/flac", just "audio". + FAQ diff --git a/bebop/browser/browser.py b/bebop/browser/browser.py index c863506..80993f8 100644 --- a/bebop/browser/browser.py +++ b/bebop/browser/browser.py @@ -4,8 +4,11 @@ import curses import curses.ascii import curses.textpad import os +import subprocess import tempfile from math import inf +from pathlib import Path +from typing import Optional, Tuple from bebop.bookmarks import ( get_bookmarks_path, get_bookmarks_document, save_bookmark @@ -16,6 +19,7 @@ from bebop.external import open_external_program from bebop.help import HELP_PAGE from bebop.history import History from bebop.links import Links +from bebop.mime import MimeType from bebop.mouse import ButtonState from bebop.navigation import ( get_parent_url, get_root_url, join_url, parse_url, unparse_url @@ -44,6 +48,7 @@ class Browser: values are dicts as well: the "open" key maps to a callable to use when the page is accessed, and the optional "source" key maps to callable returning the page source path. + - last_download: tuple of MimeType and path, or None. """ def __init__(self, config, cert_stash): @@ -59,6 +64,7 @@ class Browser: self.history = History(self.config["history_limit"]) self.cache = {} self.special_pages = self.setup_special_pages() + self.last_download: Optional[Tuple[MimeType, Path]] = None self._current_url = "" @property @@ -168,6 +174,8 @@ class Browser: self.scroll_page_vertically(inf) elif char == ord("o"): self.quick_command("open") + elif char == ord("O"): + self.open_last_download() elif char == ord("p"): self.go_back() elif char == ord("u"): @@ -574,3 +582,23 @@ class Browser: def open_history(self): """Show a generated history of visited pages.""" self.open_internal_page("history", self.history.to_gemtext()) + + def open_last_download(self): + """Open the last downloaded file.""" + if not self.last_download: + return + mime_type, path = self.last_download + command = self.config["external_commands"].get(mime_type.main_type) + if not command: + command = self.config["external_command_default"] + command = command + [str(path)] + self.set_status(f"Running '{' '.join(command)}'...") + try: + subprocess.Popen( + command, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + except FileNotFoundError as exc: + self.set_status_error(f"Failed to run command: {exc}") diff --git a/bebop/browser/gemini.py b/bebop/browser/gemini.py index 60dfa38..9917cc0 100644 --- a/bebop/browser/gemini.py +++ b/bebop/browser/gemini.py @@ -193,6 +193,7 @@ def _handle_successful_response(browser: Browser, response: Response, url: str): browser.set_status_error(f"Failed to save {url} ({exc})") else: browser.set_status(f"Downloaded {url} ({mime_type.short}).") + browser.last_download = mime_type, filepath return True elif error: browser.set_status_error(error) diff --git a/bebop/config.py b/bebop/config.py index f076a29..fe97638 100644 --- a/bebop/config.py +++ b/bebop/config.py @@ -10,6 +10,8 @@ DEFAULT_CONFIG = { "source_editor": ["vi"], "command_editor": ["vi"], "history_limit": 1000, + "external_commands": {}, + "external_command_default": ["xdg-open"] } diff --git a/bebop/help.py b/bebop/help.py index 53f00e6..1521a32 100644 --- a/bebop/help.py +++ b/bebop/help.py @@ -26,6 +26,7 @@ name, not the symbol itself. - gg: go to the top of the page - G: go to the bottom of the page - o: open an URL +- O: open last download with an external command - p: go to the previous page - u: go to the parent page (up a level in URL) - U: go to the root page (root URL for the current domain) diff --git a/bebop/navigation.py b/bebop/navigation.py index 415bfe5..7f85bd4 100644 --- a/bebop/navigation.py +++ b/bebop/navigation.py @@ -6,7 +6,6 @@ turned into a basic re-implementation of the RFC. """ import re -from ssl import RAND_pseudo_bytes from typing import Any, Dict, Optional from urllib.parse import quote diff --git a/bebop/tests/test_navigation.py b/bebop/tests/test_navigation.py index 2ee0c26..a97ec0b 100644 --- a/bebop/tests/test_navigation.py +++ b/bebop/tests/test_navigation.py @@ -79,7 +79,7 @@ class TestNavigation(unittest.TestCase): self.assertEqual( remove_dot_segments(path), expected, - msg=f"path was " + path + msg="path was " + path ) def test_remove_last_segment(self): @@ -123,7 +123,7 @@ class TestNavigation(unittest.TestCase): self.assertEqual( get_parent_url(url), parent, - msg=f"URL was " + url) + msg="URL was " + url) def test_get_root_url(self): urls_and_roots = [ @@ -140,5 +140,5 @@ class TestNavigation(unittest.TestCase): self.assertEqual( get_root_url(url), root, - msg=f"URL was " + url + msg="URL was " + url )