├── .gitignore ├── LICENSE ├── README.md ├── THANKSTO ├── build.sh ├── clean.sh ├── com.github.koyuspace.fossil.desktop.in ├── data ├── hypertext_theme_rules │ └── orange.json └── hypertext_themes │ ├── blue_dark.json │ ├── blue_dark_gemini.json │ ├── default.json │ ├── orange_dark.json │ ├── orange_dark_gemini.json │ ├── orange_dark_gus.json │ ├── orange_on_grey_demo.json │ └── vulpes_proxy.json ├── debug.sh ├── install-binary.sh ├── install.sh ├── meson.build ├── po └── LINGUAS ├── remove-binary.sh ├── rename.sh ├── src ├── asm │ ├── argparse.vala │ ├── asm.vala │ ├── simple_asmObject.vala │ └── super_registry_constructor_function_provider.vala ├── asm_init │ ├── about.store.vala │ ├── bookmarks.registry.vala │ ├── file.store.vala │ ├── finger.store.vala │ ├── gemini.store.vala │ ├── gopher.store.vala │ ├── gopher.type.registry.vala │ ├── mimeguesser.registry.vala │ ├── session.registry.vala │ ├── store.registry.vala │ ├── switch.store.vala │ └── uri_autoprefix.registry.vala ├── downloader.vala ├── external.vala ├── gtk_ui │ ├── application.vala │ ├── interface │ │ ├── legacy_view.vala │ │ └── theming │ │ │ ├── hypertext_theme_loader.i.vala │ │ │ ├── hypertext_theme_rule_provider.i.vala │ │ │ ├── hypertext_view_theme.vala │ │ │ └── hypertext_view_theme_provider.vala │ ├── json_integration │ │ └── theming │ │ │ ├── hypertext_theme_rule.vala │ │ │ ├── hypertext_view_theme.vala │ │ │ └── text_tag_theme.vala │ ├── legacy_util │ │ ├── default_gtk_link_icon_loader.vala │ │ ├── gtk_scroll_export.vala │ │ └── message_view_factory.vala │ ├── legacy_view_registry.vala │ ├── legacy_widget │ │ ├── bookmark_adder.vala │ │ ├── bookmark_editor.vala │ │ ├── cacheview.vala │ │ ├── dialog_view_base.vala │ │ ├── header_bar.vala │ │ ├── hypertext_content.vala │ │ ├── inline_search.vala │ │ ├── inline_search_post_b64_file_popover_easter_egg.vala │ │ ├── link_button.vala │ │ ├── link_popover.vala │ │ ├── menu_entries.vala │ │ ├── request_argument_display.vala │ │ ├── session_chooser.vala │ │ ├── subview_base.vala │ │ ├── tab.vala │ │ ├── tab_head.vala │ │ ├── text_content.vala │ │ ├── text_entry.vala │ │ ├── view_chooser.vala │ │ └── window.vala │ ├── settings_integration │ │ ├── settings_hypertext_json_theme_loader.vala │ │ └── settings_hypertext_json_theme_rule_provider.vala │ ├── theming │ │ ├── default_hypertext_view_theme_provider.vala │ │ ├── hypertext_theme_rule.vala │ │ ├── hypertext_view_theme.vala │ │ └── text_tag_theme.vala │ └── views │ │ ├── bookmarks.vala │ │ ├── cache.vala │ │ ├── directory.vala │ │ ├── download.vala │ │ ├── error_generic.vala │ │ ├── geminiInput.vala │ │ ├── hypertext.vala │ │ ├── image.vala │ │ ├── loading.vala │ │ ├── message.vala │ │ ├── plaintext.vala │ │ ├── redirect.vala │ │ ├── tls_session.vala │ │ ├── unknown_uri_scheme.vala │ │ ├── upload_file.vala │ │ ├── upload_text.vala │ │ ├── uri_error_generic.vala │ │ └── uri_merge_test.vala ├── interface │ ├── cache.i.vala │ ├── document │ │ ├── token_parser.i.vala │ │ ├── token_parser_factory.i.vala │ │ └── token_renderer.i.vala │ ├── page │ │ ├── page.i.vala │ │ └── service │ │ │ ├── external_navigation.i.vala │ │ │ ├── internal_navigation.i.vala │ │ │ ├── linear_history.i.vala │ │ │ ├── metadata.i.vala │ │ │ └── syncronisation.i.vala │ ├── resource_store.i.vala │ ├── session.i.vala │ └── settings │ │ └── provider.i.vala ├── main.vala ├── meson.build ├── page │ ├── page.vala │ └── service │ │ ├── metadata.vala │ │ └── syncronisation.vala ├── registries │ ├── bookmark_registry.vala │ ├── gopher_type_registry.vala │ ├── mimeguesser.vala │ ├── session_registry.vala │ ├── store_registry.vala │ ├── super_registry.vala │ ├── translation.vala │ └── uri_autocorrect.vala ├── request.vala ├── resource.vala ├── sessions │ ├── default.vala │ ├── dummy.vala │ ├── tls.vala │ └── uncached.vala ├── settings │ ├── bridge │ │ ├── bookmark_settings_bridge.vala │ │ └── kv_settings_bridge.vala │ ├── context │ │ ├── fallback.vala │ │ └── prefix.vala │ ├── file_settings_provider.vala │ ├── ram_settings_provider.vala │ └── report.vala ├── startup │ ├── about.backend.vala │ ├── bookmarks.backend.vala │ ├── bookmarks.gtk.vala │ ├── bookmarks.settings.vala │ ├── cache.backend.vala │ ├── cache.gtk.vala │ ├── file.backend.vala │ ├── file.gtk.vala │ ├── finger.backend.vala │ ├── frontend.settings.vala │ ├── gemini.backend.vala │ ├── gemini.gtk.vala │ ├── geminiupload.backend.vala │ ├── geminiwrite.backend.vala │ ├── gopher.backend.vala │ ├── gopherwrite.backend.vala │ ├── hypertext.gtk.vala │ ├── hypertext.settings.vala │ ├── localization.english.vala │ ├── localization.vala │ ├── sessions.backend.vala │ ├── sessions.gtk.vala │ ├── settings.backend.vala │ ├── store_switch.vala │ ├── upload.gtk.vala │ ├── utiltest.backend.vala │ └── utiltest.gtk.vala ├── stores │ ├── about.vala │ ├── cache.vala │ ├── file.vala │ ├── finger.vala │ ├── gemini.vala │ ├── geminiupload.vala │ ├── geminiwrite.vala │ ├── gopher.vala │ ├── gopherwrite.vala │ ├── internal.vala │ └── switch.vala ├── ui │ ├── document │ │ ├── default_token_parser_factory.vala │ │ ├── token.vala │ │ ├── token_parser │ │ │ ├── gemini.vala │ │ │ ├── gopher.vala │ │ │ └── plaintext.vala │ │ └── token_type.vala │ ├── tab.vala │ └── tab_display_state.vala └── util │ ├── connection_helper.vala │ ├── flaglist.vala │ ├── intparser.vala │ ├── kv.vala │ ├── resource_file_helper.vala │ ├── stack.vala │ ├── tls_certificate_generator.vala │ └── uri.vala ├── tools └── bulksed.lua ├── update_src_build_files └── update_src_build_files.lua /.gitignore: -------------------------------------------------------------------------------- 1 | meson_build 2 | fossil 3 | fossil.old 4 | src/meson.build.old 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Baschdel 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | 4. Neither the source nor binary forms may be used commercially without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fossil 2 | 3 | A simple GTK Gopher/Gemini client written in Vala 4 | 5 | ### Supported download protocols 6 | 7 | - Gopher 8 | - Gemini 9 | - Finger 10 | 11 | ### Supported upload protocols 12 | 13 | - [gopher+write](https://alexschroeder.ch/wiki/2017-12-30_Gopher_Wiki) 14 | - [gemini+write](https://alexschroeder.ch/wiki/2020-06-04_Gemini_Upload) 15 | - [gemini+upload](https://alexschroeder.ch/wiki/Baschdels_spin_on_Gemini_uploading) 16 | 17 | ### Noteworthy features 18 | 19 | - Tabs 20 | - Bookmarks 21 | - In-application image display 22 | - View page source option 23 | - Support for `file://` URIs 24 | - Per tab history 25 | - Ability to save everything to disk 26 | - Cache 27 | - Works on Linux-based smartphones 28 | - Tries to be as themeable as possible using GTK themes and icon packs 29 | 30 | ## How to build/install? 31 | 32 | Note: to build Fossil you need the following dependencies: 33 | 34 | - gtk3+ - the graphics toolkit 35 | - valac - the Vala compiler 36 | - meson - the build system 37 | - cmake - used by meson 38 | - python 3.x 39 | - json-glib 40 | - gnutls 41 | - gettext 42 | 43 | One-liner for Debian-based systems: 44 | 45 | ``` 46 | sudo apt install libgtk-3-dev valac meson cmake libgdk-pixbuf2.0-dev python3 libjson-glib-dev libgnutls28-dev gettext 47 | ``` 48 | 49 | To build and install Fossil, run the `install.sh` script, which will automatically setup the build folder, run ninja, put the output in the projects root directory, copy the files for Fossil to your system and registers the protocol handler. You should then find a new entry in your application launcher. If you want to use the stable version checkout to the latest tag before compiling. 50 | 51 | ## Packaged builds 52 | 53 | ### Arch Linux 54 | 55 | If you want to use Fossil on Arch Linux you can install the package [fossil-gemini-git](https://aur.archlinux.org/packages/fossil-gemini-git/) from the AUR. 56 | 57 | ### Flatpaks? 58 | 59 | Flatpaks are coming soon! If you want to write a flatpak manifest file for Fossil submit a pull request. 60 | 61 | ![Screenshot](https://fossil.koyu.space/screenshot.png) 62 | -------------------------------------------------------------------------------- /THANKSTO: -------------------------------------------------------------------------------- 1 | alecaddd [LBRY] - for the amazing vala tutorial series 2 | rain-1 [github] - for his simple gopher client, I used as a reference 3 | baschdel [gitlab] - for Dragonstone making this browser possible 4 | Everyone who made gtk and vala a thing, beacause otherwise this if at all would have been written in python or java 5 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | builddir=meson_build 3 | output="src/com.github.koyuspace.fossil" 4 | dest="fossil" 5 | 6 | if [ ! -d "$builddir" ] 7 | then 8 | meson "$builddir" --prefix=/usr 9 | fi 10 | 11 | cd "$builddir" 12 | rm -f "$output" 13 | ninja 14 | cp -f "$output" "../$dest" 15 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat .gitignore | xargs rm -rf -------------------------------------------------------------------------------- /com.github.koyuspace.fossil.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Fossil 3 | Comment=Gopher and Gemini browser 4 | Keywords=gemini;gopher;browser 5 | #GenericName=Fossil 6 | Exec=com.github.koyuspace.fossil %U 7 | Icon=applications-internet 8 | Terminal=false 9 | StartupNotify=true 10 | Type=Application 11 | MimeType=text/gopher;text/gemini;x-scheme-handler/gopher;x-scheme-handler/gemini; 12 | Categories=Network;Internet 13 | X-GNOME-Gettext-Domain=com.github.koyupace.fossil 14 | -------------------------------------------------------------------------------- /data/hypertext_theme_rules/orange.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "content_type":"text/gemini", 4 | "theme":"orange_dark_gemini" 5 | },{ 6 | "content_type":"text/gemini", 7 | "path_prefix":"/search", 8 | "theme":"orange_dark_gus" 9 | },{ 10 | "content_type":"text/gemini", 11 | "path_prefix":"/v/search", 12 | "theme":"orange_dark_gus" 13 | },{ 14 | "theme":"orange_dark" 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /data/hypertext_themes/blue_dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic", 14 | "foreground":"#C4CCD7" 15 | }, 16 | "link :hover":{ 17 | "underline":"single", 18 | "scale":1.11 19 | }, 20 | "link :prefix":{ 21 | "scale":1.15, 22 | "font":"bold", 23 | "foreground":"#A1A49E" 24 | }, 25 | "link_icon":{ 26 | "scale":1.5 27 | }, 28 | "list_item :prefix":{ 29 | "foreground":"#3465A4" 30 | }, 31 | "title +0":{ 32 | "scale":1.9, 33 | "foreground":"#144E94" 34 | }, 35 | "title +1":{ 36 | "scale":1.5 37 | }, 38 | "title":{ 39 | "scale":1.2 40 | }, 41 | "quote":{ 42 | "font":"oblique", 43 | "foreground":"#A1A49E" 44 | }, 45 | "description":{ 46 | "scale":0.9, 47 | "font":"italic", 48 | "paragraph_background":"#111111", 49 | "foreground":"#A1A49E", 50 | "indent":10 51 | }, 52 | "paragraph :preformatted":{ 53 | "wrap_mode":"none", 54 | "indent":10 55 | }, 56 | "error":{ 57 | "foreground":"#FB3934" 58 | }, 59 | "parser_error":{ 60 | "foreground":"#FB3934", 61 | "font":"italic" 62 | }, 63 | "*:preformatted":{ 64 | "font":"monospace" 65 | }, 66 | "*":{ 67 | "foreground":"#D3D7CF", 68 | "paragraph_background":"#191919", 69 | "font":"Noto Sans Mono Medium 12", 70 | "wrap_mode":"word_char" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /data/hypertext_themes/blue_dark_gemini.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic", 14 | "foreground":"#C4CCD7" 15 | }, 16 | "link :hover":{ 17 | "underline":"single", 18 | "scale":1.11 19 | }, 20 | "link :prefix":{ 21 | "scale":1.15, 22 | "font":"bold", 23 | "foreground":"#A1A49E" 24 | }, 25 | "link_icon":{ 26 | "scale":1.5 27 | }, 28 | "list_item :prefix":{ 29 | "foreground":"#3465A4" 30 | }, 31 | "title +0":{ 32 | "scale":1.9, 33 | "foreground":"#144E94" 34 | }, 35 | "title +1":{ 36 | "scale":1.5 37 | }, 38 | "title":{ 39 | "scale":1.2 40 | }, 41 | "quote":{ 42 | "font":"oblique", 43 | "foreground":"#A1A49E" 44 | }, 45 | "description":{ 46 | "scale":0.9, 47 | "font":"italic", 48 | "paragraph_background":"#111111", 49 | "foreground":"#A1A49E", 50 | "indent":10 51 | }, 52 | "paragraph :preformatted":{ 53 | "wrap_mode":"none", 54 | "indent":10 55 | }, 56 | "error":{ 57 | "foreground":"#FB3934" 58 | }, 59 | "parser_error":{ 60 | "foreground":"#FB3934", 61 | "font":"italic" 62 | }, 63 | "*:preformatted":{ 64 | "font":"monospace", 65 | "paragraph_background":"#141414", 66 | "foreground":"#D3D7CF" 67 | }, 68 | "*":{ 69 | "foreground":"#D3D7CF", 70 | "paragraph_background":"#191919", 71 | "font":"Noto Sans Mono Medium 12", 72 | "wrap_mode":"word_char" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /data/hypertext_themes/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: ", 9 | "exception":"[INTERNAL ERROR]" 10 | }, 11 | "tag_themes":{ 12 | "link":{ 13 | "scale":1.1, 14 | "font":"italic" 15 | }, 16 | "link :hover":{ 17 | "underline":"single", 18 | "scale":1.15 19 | }, 20 | "link :prefix":{ 21 | "scale":1.15 22 | }, 23 | "link_icon":{ 24 | "scale":1.5 25 | }, 26 | "title +0":{ 27 | "scale":1.7 28 | }, 29 | "title +1":{ 30 | "scale":1.5 31 | }, 32 | "title":{ 33 | "scale":1.2 34 | }, 35 | "quote":{ 36 | "font":"oblique" 37 | }, 38 | "description":{ 39 | "scale":0.9, 40 | "font":"italic", 41 | "indent":10 42 | }, 43 | "paragraph :preformatted":{ 44 | "wrap_mode":"none", 45 | "indent":10 46 | }, 47 | "error":{ 48 | "foreground":"#FB3934" 49 | }, 50 | "parser_error":{ 51 | "foreground":"#FB3934", 52 | "font":"italic" 53 | }, 54 | "exception":{ 55 | "foreground":"#FB3934", 56 | "font":"italic" 57 | }, 58 | "*:preformatted":{ 59 | "font":"monospace" 60 | }, 61 | "*":{ 62 | "wrap_mode":"word_char" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /data/hypertext_themes/orange_dark.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic", 14 | "foreground":"#D7D1C4" 15 | }, 16 | "link :hover":{ 17 | "underline":"single", 18 | "scale":1.11 19 | }, 20 | "link :prefix":{ 21 | "scale":1.15, 22 | "font":"bold", 23 | "foreground":"#A1A49E" 24 | }, 25 | "link_icon":{ 26 | "scale":1.5 27 | }, 28 | "list_item :prefix":{ 29 | "foreground":"#A1A49E" 30 | }, 31 | "title +0":{ 32 | "scale":1.7, 33 | "foreground":"#CE5C00" 34 | }, 35 | "title +1":{ 36 | "scale":1.5 37 | }, 38 | "title":{ 39 | "scale":1.2 40 | }, 41 | "quote":{ 42 | "font":"oblique", 43 | "foreground":"#A1A49E" 44 | }, 45 | "description":{ 46 | "scale":0.9, 47 | "font":"italic", 48 | "paragraph_background":"#111111", 49 | "foreground":"#A1A49E", 50 | "indent":10 51 | }, 52 | "paragraph :preformatted":{ 53 | "wrap_mode":"none", 54 | "indent":10 55 | }, 56 | "error":{ 57 | "foreground":"#FB3934" 58 | }, 59 | "parser_error":{ 60 | "foreground":"#FB3934", 61 | "font":"italic" 62 | }, 63 | "*:preformatted":{ 64 | "font":"monospace" 65 | }, 66 | "*":{ 67 | "foreground":"#D3D7CF", 68 | "paragraph_background":"#191919", 69 | "font":"Noto Sans Mono Medium 12", 70 | "wrap_mode":"word_char" 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /data/hypertext_themes/orange_dark_gemini.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic", 14 | "foreground":"#D7D1C4" 15 | }, 16 | "link :hover":{ 17 | "underline":"single", 18 | "scale":1.11 19 | }, 20 | "link :prefix":{ 21 | "scale":1.15, 22 | "font":"bold", 23 | "foreground":"#A1A49E" 24 | }, 25 | "link_icon":{ 26 | "scale":1.5 27 | }, 28 | "list_item :prefix":{ 29 | "foreground":"#A1A49E" 30 | }, 31 | "title +0":{ 32 | "scale":1.7, 33 | "foreground":"#CE5C00" 34 | }, 35 | "title +1":{ 36 | "scale":1.5 37 | }, 38 | "title":{ 39 | "scale":1.2 40 | }, 41 | "quote":{ 42 | "font":"oblique", 43 | "foreground":"#A1A49E" 44 | }, 45 | "description":{ 46 | "scale":0.9, 47 | "font":"italic", 48 | "paragraph_background":"#111111", 49 | "foreground":"#A1A49E", 50 | "indent":10 51 | }, 52 | "paragraph :preformatted":{ 53 | "wrap_mode":"none", 54 | "indent":10 55 | }, 56 | "error":{ 57 | "foreground":"#FB3934" 58 | }, 59 | "parser_error":{ 60 | "foreground":"#FB3934", 61 | "font":"italic" 62 | }, 63 | "*:preformatted":{ 64 | "font":"monospace", 65 | "paragraph_background":"#141414", 66 | "foreground":"#D3D7CF" 67 | }, 68 | "*":{ 69 | "foreground":"#D3D7CF", 70 | "paragraph_background":"#191919", 71 | "font":"Noto Sans Mono Medium 12", 72 | "wrap_mode":"word_char" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /data/hypertext_themes/orange_dark_gus.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"{{{link_icon}}}", 4 | "link +1":"", 5 | "link :inline":" ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic", 14 | "foreground":"#D7D1C4" 15 | }, 16 | "link +1":{ 17 | "scale":1.2, 18 | "paragraph_background":"#111111", 19 | "foreground":"#F57900", 20 | "indent":10 21 | }, 22 | "link :hover":{ 23 | "underline":"single" 24 | }, 25 | "link :prefix":{ 26 | "scale":1.15, 27 | "font":"bold", 28 | "foreground":"#A1A49E" 29 | }, 30 | "link_icon":{ 31 | "scale":1.5 32 | }, 33 | "title +0":{ 34 | "scale":1.7, 35 | "foreground":"#CE5C00" 36 | }, 37 | "title +1":{ 38 | "scale":1.5 39 | }, 40 | "title":{ 41 | "scale":1.2 42 | }, 43 | "quote":{ 44 | "font":"oblique", 45 | "foreground":"#A1A49E" 46 | }, 47 | "list_item":{ 48 | "scale":0.9, 49 | "font":"italic", 50 | "paragraph_background":"#111111", 51 | "foreground":"#A1A49E", 52 | "indent":20 53 | }, 54 | "paragraph :preformatted":{ 55 | "wrap_mode":"none", 56 | "indent":10 57 | }, 58 | "error":{ 59 | "foreground":"#FB3934" 60 | }, 61 | "parser_error":{ 62 | "foreground":"#FB3934", 63 | "font":"italic" 64 | }, 65 | "*:preformatted":{ 66 | "font":"monospace", 67 | "paragraph_background":"#141414", 68 | "foreground":"#D3D7CF" 69 | }, 70 | "*":{ 71 | "foreground":"#D3D7CF", 72 | "paragraph_background":"#191919", 73 | "font":"Noto Sans Mono Medium 12", 74 | "wrap_mode":"word_char" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /data/hypertext_themes/orange_on_grey_demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":"-> ", 4 | "link :inline":" ", 5 | "list_item":"▶ ", 6 | "parser_error":"[PARSER_ERROR] ", 7 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 8 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 9 | }, 10 | "tag_themes":{ 11 | "link":{ 12 | "scale":1.1, 13 | "font":"italic" 14 | }, 15 | "link :hover":{ 16 | "underline":"single", 17 | "scale":1.15 18 | }, 19 | "link :prefix":{ 20 | "scale":1.15, 21 | "font":"bold", 22 | "foreground":"#A1A49E" 23 | }, 24 | "link_icon":{ 25 | "scale":1.5 26 | }, 27 | "list_item :prefix":{ 28 | "foreground":"#4E9A06" 29 | }, 30 | "title +0":{ 31 | "scale":1.7, 32 | "foreground":"#CE5C00" 33 | }, 34 | "title +1":{ 35 | "scale":1.5 36 | }, 37 | "title":{ 38 | "scale":1.2 39 | }, 40 | "quote":{ 41 | "font":"oblique" 42 | }, 43 | "description":{ 44 | "scale":0.9, 45 | "font":"NoGameNoLife", 46 | "paragraph_background":"#111111", 47 | "foreground":"#A1A49E", 48 | "indent":10 49 | }, 50 | "paragraph :preformatted":{ 51 | "wrap_mode":"none", 52 | "indent":10 53 | }, 54 | "error":{ 55 | "foreground":"#FB3934" 56 | }, 57 | "parser_error":{ 58 | "foreground":"#FB3934", 59 | "font":"italic" 60 | }, 61 | "*:preformatted":{ 62 | "font":"monospace", 63 | "paragraph_background":"#191919", 64 | "foreground":"#D3D7CF" 65 | }, 66 | "*":{ 67 | "foreground":"#FCAF3E", 68 | "paragraph_background":"#222222", 69 | "wrap_mode":"word_char", 70 | "font":"saoui", 71 | "scale":1.5 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /data/hypertext_themes/vulpes_proxy.json: -------------------------------------------------------------------------------- 1 | { 2 | "prefixes":{ 3 | "link":" => ", 4 | "link :inline":" ", 5 | "list_item":"– ", 6 | "quote":"> ", 7 | "title +0":" # ", 8 | "title +1":" ## ", 9 | "title":"### ", 10 | "parser_error":"[PARSER_ERROR] ", 11 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 12 | "search_without_uri":"[PARSER MISTAKE] Search without uri: " 13 | }, 14 | "tag_themes":{ 15 | "link":{ 16 | "underline":"single", 17 | "foreground":"#FFFFFF" 18 | }, 19 | "link :hover":{ 20 | "font":"bold" 21 | }, 22 | "link :prefix":{ 23 | "foreground":"#929ba3", 24 | "left_margin":10 25 | }, 26 | "link_icon":{ 27 | "scale":1.5 28 | }, 29 | "list_item :prefix":{ 30 | "foreground":"#A1A49E" 31 | }, 32 | "title":{ 33 | "font":"bold", 34 | "foreground":"#FFFFFF" 35 | }, 36 | "title :prefix":{ 37 | "foreground":"#929ba3", 38 | "left_margin":10 39 | }, 40 | "paragraph":{ 41 | "margin_left":"20" 42 | }, 43 | "paragraph :preformatted":{ 44 | "wrap_mode":"none", 45 | "indent":50 46 | }, 47 | "error":{ 48 | "foreground":"#FB3934" 49 | }, 50 | "parser_error":{ 51 | "foreground":"#FB3934", 52 | "font":"italic" 53 | }, 54 | "description":{ 55 | "invisible":true 56 | }, 57 | "*:preformatted":{ 58 | "font":"Noto Sans Mono Medium 14" 59 | }, 60 | "*":{ 61 | "foreground":"#cad1d8", 62 | "paragraph_background":"#14171a", 63 | "font":"Noto Sans Mono Medium 14", 64 | "wrap_mode":"word_char", 65 | "right_margin":20, 66 | "left_margin":50 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ./build.sh 3 | meson_build/src/com.github.koyuspace.fossil -------------------------------------------------------------------------------- /install-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo wget -O /usr/bin/com.github.koyuspace.fossil https://github.com/koyuspace/fossil/releases/download/v1.3/fossil 3 | sudo chmod +x /usr/bin/com.github.koyuspace.fossil 4 | sudo wget -O /usr/share/applications/com.github.koyuspace.fossil.desktop https://raw.githubusercontent.com/koyuspace/fossil/main/com.github.koyuspace.fossil.desktop.in 5 | xdg-mime default com.github.koyuspace.fossil.desktop x-scheme-handler/gemini 6 | xdg-mime default com.github.koyuspace.fossil.desktop x-scheme-handler/gopher 7 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | builddir=meson_build 3 | 4 | if [ ! -d "$builddir" ] 5 | then 6 | meson "$builddir" --prefix=/usr 7 | fi 8 | 9 | cd "$builddir" 10 | ninja 11 | sudo ninja install 12 | xdg-mime default com.github.koyuspace.fossil.desktop x-scheme-handler/gemini 13 | xdg-mime default com.github.koyuspace.fossil.desktop x-scheme-handler/gopher 14 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('com.github.koyuspace.fossil','vala','c') 2 | 3 | subdir('src') 4 | 5 | i18n = import('i18n') 6 | 7 | i18n.merge_file( 8 | input: meson.project_name() + '.desktop.in', 9 | output: meson.project_name() + '.desktop', 10 | po_dir: join_paths(meson.source_root(), 'po'), 11 | type: 'desktop', 12 | install: true, 13 | install_dir: join_paths(get_option('datadir'), 'applications') 14 | ) 15 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koyuspace/fossil/11ce8411712cd74b23547113573e12ffaeae2976/po/LINGUAS -------------------------------------------------------------------------------- /remove-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo rm -f /usr/bin/com.github.koyuspace.fossil 3 | sudo rm -f /usr/share/applications/com.github.koyuspace.fossil.desktop 4 | -------------------------------------------------------------------------------- /rename.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | find -name "*.vala" | lua tools/bulksed.lua "s/$1/$2/g" 3 | -------------------------------------------------------------------------------- /src/asm/argparse.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Asm.Argparse : Object { 2 | 3 | public string[] arguments; 4 | 5 | public const uint TYPE_NULL = 1; 6 | public const uint TYPE_STRING = 2; 7 | public const uint TYPE_UINT = 4; 8 | 9 | public Argparse(string args){ 10 | arguments = args.split("\t"); 11 | } 12 | 13 | public uint length { 14 | get { 15 | return arguments.length; 16 | } 17 | } 18 | 19 | public string? get_string(uint argnum, string? _default = null){ 20 | if (argnum < this.length){ 21 | return arguments[argnum]; 22 | } else { 23 | return _default; 24 | } 25 | } 26 | 27 | public uint64? get_uint(uint argnum, uint64? _default = null){ 28 | string? text = get_string(argnum); 29 | if (text == null){ return _default; } 30 | uint64 val; 31 | if (Fossil.Util.Intparser.try_parse_unsigned(text, out val)){ 32 | return val; 33 | } 34 | return _default; 35 | } 36 | 37 | public bool verify_argument(uint argnum, uint type){ 38 | if (argnum >= this.length){ 39 | return (type&TYPE_NULL) > 0; 40 | } 41 | bool passed = true; 42 | if ((type&TYPE_STRING) > 0){ 43 | passed = passed && (get_string(argnum) != null); 44 | } 45 | if ((type&TYPE_UINT) > 0){ 46 | passed = passed && (get_uint(argnum) != null); 47 | } 48 | return passed; 49 | } 50 | 51 | public static uint parse_type(string type){ 52 | uint rettype = 0; 53 | foreach(string t in type.split("/")){ 54 | if (t == "NULL"){ 55 | rettype = rettype|TYPE_NULL; 56 | } else if (t == "STRING"){ 57 | rettype = rettype|TYPE_STRING; 58 | } else if (t == "UINT"){ 59 | rettype = rettype|TYPE_UINT; 60 | } 61 | } 62 | return rettype; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/asm/simple_asmObject.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Asm.SimpleAsmObject : Object, Fossil.Asm.AsmObject { 2 | 3 | protected HashTable asm_functions = new HashTable(str_hash,str_equal); 4 | 5 | public void add_asm_function(Fossil.Asm.FunctionDescriptor function) { 6 | asm_functions.set(function.name,function); 7 | } 8 | 9 | public void foreach_asm_function(Func cb){ 10 | asm_functions.@foreach((name,_) => { 11 | cb(name); 12 | }); 13 | } 14 | 15 | public Fossil.Asm.Scriptreturn? exec(string method, string arg, Object? context = null){ 16 | var function = asm_functions.get(method); 17 | if (function != null){ 18 | return function.callback(arg,context); 19 | } 20 | return new Fossil.Asm.Scriptreturn.unknown_function(method); 21 | } 22 | 23 | public string? get_localizable_helptext(string method){ 24 | var function = asm_functions.get(method); 25 | if (function != null){ 26 | return function.localizable_helptext; 27 | } 28 | return null; 29 | } 30 | 31 | public string? get_unlocalized_helptext(string method){ 32 | var function = asm_functions.get(method); 33 | if (function != null){ 34 | return function.unlocalized_helptext; 35 | } 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/asm/super_registry_constructor_function_provider.vala: -------------------------------------------------------------------------------- 1 | public delegate Object Fossil.Asm.SuperRegistrySimpleConstructorFunction(); 2 | 3 | public class Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor : Fossil.Asm.FunctionDescriptor { 4 | 5 | Fossil.Asm.SuperRegistrySimpleConstructorFunction constructor_function; 6 | 7 | public SuperRegistrySimpleConstructorFunctionDescriptor(string name, owned Fossil.Asm.SuperRegistrySimpleConstructorFunction constructor_function){ 8 | base.empty(); 9 | this.constructor_function = (owned) constructor_function; 10 | this.name = name; 11 | this.callback = this.construct_object; 12 | this.localizable_helptext = "asm.help.cinstructor_simple"; 13 | this.unlocalized_helptext = @"$name initializes an object and stores it in the context at "; 14 | } 15 | 16 | private Fossil.Asm.Scriptreturn? construct_object(string _arg, Object? context = null){ 17 | string arg = _arg.strip(); 18 | if (arg == ""){ 19 | return new Fossil.Asm.Scriptreturn.missing_argument(); 20 | } 21 | Fossil.SuperRegistry? super_registry = (Fossil.SuperRegistry) context; 22 | if (super_registry == null){ 23 | print(@"error while constructing $arg = new $name(): wrong context\n"); 24 | return new Fossil.Asm.Scriptreturn.wrong_context("SuperRegistry"); 25 | } 26 | print(@"constructing: $arg = new $name()\n"); 27 | super_registry.store(arg,this.constructor_function()); 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/asm_init/about.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.About.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.About(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/bookmarks.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Bookmarks.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Registry.BookmarkRegistry(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/file.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.File.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.File(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/finger.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Finger.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.Finger(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/gemini.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Gemini.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.Gemini(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/gopher.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Gopher.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.Gopher(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/gopher.type.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Gopher.Type.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Registry.GopherTypeRegistry(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/mimeguesser.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Mimeguesser.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | object.add_asm_function(new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | () => { 6 | return new Fossil.Registry.MimetypeGuesser(); 7 | } 8 | )); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/asm_init/session.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Session.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | object.add_asm_function(new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | () => { 6 | return new Fossil.Registry.SessionRegistry(); 7 | } 8 | )); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/asm_init/store.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Store.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | object.add_asm_function(new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | () => { 6 | return new Fossil.Registry.StoreRegistry(); 7 | } 8 | )); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/asm_init/switch.store.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.Switch.Store { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | var desc = new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | constr 6 | ); 7 | object.add_asm_function(desc); 8 | } 9 | 10 | private static Object constr(){ 11 | return new Fossil.Store.Switch.default_configuration(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/asm_init/uri_autoprefix.registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.AsmInit.UriAutoprefix.Registry { 2 | public static void register_initializer(string name,Fossil.Asm.SimpleAsmObject object){ 3 | object.add_asm_function(new Fossil.Asm.SuperRegistrySimpleConstructorFunctionDescriptor( 4 | name, 5 | () => { 6 | return new Fossil.Registry.UriAutoprefix(); 7 | } 8 | )); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/downloader.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Downloader : Object { 2 | public static async Fossil.DownloaderReturnCode save_resource(Fossil.Resource resource,string filepath){ 3 | if (resource.filepath == null){ 4 | return Fossil.DownloaderReturnCode.INCOMPATIBLE_RESOURCE; 5 | } 6 | var sourcefile = File.new_for_path(resource.filepath); 7 | var file = File.new_for_path(filepath); 8 | if (file.query_exists ()) { 9 | print(@"[download][error] There alsready is a file at $filepath, not downloading\n"); 10 | return Fossil.DownloaderReturnCode.ALREADY_EXISTS; 11 | } 12 | try{ 13 | if (!sourcefile.query_exists ()) { 14 | return Fossil.DownloaderReturnCode.NO_PERMISSION; 15 | } 16 | sourcefile.copy(file,FileCopyFlags.NONE); 17 | } catch (Error e){ 18 | print(@"[download][error] Something went wrong while downloading to $filepath\n$(e.message)\n"); 19 | return Fossil.DownloaderReturnCode.ERROR; 20 | } 21 | return Fossil.DownloaderReturnCode.OK; 22 | } 23 | } 24 | 25 | public enum Fossil.DownloaderReturnCode { 26 | OK, 27 | ALREADY_EXISTS, 28 | NO_PERMISSION, 29 | ERROR, 30 | INCOMPATIBLE_RESOURCE 31 | } 32 | -------------------------------------------------------------------------------- /src/external.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.External { 2 | 3 | public static void open_uri(string uri){ 4 | try { 5 | Pid child_pid; 6 | GLib.Process.spawn_async (null, {"xdg-open",uri}, null, GLib.SpawnFlags.SEARCH_PATH, null, out child_pid); 7 | } catch (Error e){ 8 | print(@"Error while spawing xdg-open: $(e.message)\n"); 9 | } 10 | } 11 | 12 | public static void open_file(string filepath){ 13 | try { 14 | Pid child_pid; 15 | GLib.Process.spawn_async (null, {"xdg-open",filepath}, null, GLib.SpawnFlags.SEARCH_PATH, null, out child_pid); 16 | } catch (Error e){ 17 | print(@"Error while spawing xdg-open: $(e.message)\n"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/gtk_ui/interface/legacy_view.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.Interface.LegacyView : Gtk.Widget { 2 | //returns true if the view rendered successfully, false if not 3 | //only has to work once per View object 4 | //if the view has nothing special to offer as a subview it can safely igore the as_subview field 5 | public abstract bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview); 6 | //returns if the view can still handle the current resource 7 | public abstract bool canHandleCurrentResource(); 8 | //tells the View to unhook from all resource and request signal it may be hooked up to 9 | //and clean up after itself 10 | public virtual void cleanup(){} 11 | 12 | public virtual bool import(string data){ 13 | return false; 14 | } 15 | public virtual string? export(){ 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/gtk_ui/interface/theming/hypertext_theme_loader.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.Interface.Theming.HypertextThemeLoader : Object { 2 | 3 | public abstract Fossil.GtkUi.Interface.Theming.HypertextViewTheme? get_theme_by_name(string name); 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/gtk_ui/interface/theming/hypertext_theme_rule_provider.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.Interface.HypertextThemeRuleProvider : Object { 2 | 3 | public abstract void foreach_relevant_rule(string content_type, string uri, Func cb); 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/gtk_ui/interface/theming/hypertext_view_theme.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.Interface.Theming.HypertextViewTheme : Object { 2 | 3 | public abstract string? get_prefix(string name); 4 | public abstract Fossil.GtkUi.Theming.TextTagTheme? get_text_tag_theme(string name); 5 | 6 | public abstract string get_best_matching_text_tag_theme_name(string[] classes); 7 | 8 | public abstract bool is_monospaced_by_default(); 9 | 10 | public signal void theme_updated(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/gtk_ui/interface/theming/hypertext_view_theme_provider.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.Interface.Theming.HypertextViewThemeProvider : Object { 2 | 3 | public abstract Fossil.GtkUi.Interface.Theming.HypertextViewTheme? get_theme(string content_type, string uri); 4 | public abstract Fossil.GtkUi.Interface.Theming.HypertextViewTheme get_default_theme(); 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/gtk_ui/json_integration/theming/hypertext_theme_rule.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.JsonIntegration.Theming.HypertextThemeRule { 2 | 3 | public static Json.Object rule_to_json(Fossil.GtkUi.Theming.HypertextThemeRule rule){ 4 | var object = new Json.Object(); 5 | object.set_string_member("theme", rule.theme_name); 6 | if (rule.content_type != null) { 7 | object.set_string_member("content_type", rule.content_type); 8 | } 9 | if (rule.scheme != null) { 10 | object.set_string_member("scheme", rule.scheme); 11 | } 12 | if (rule.host != null) { 13 | object.set_string_member("host", rule.host); 14 | } 15 | if (rule.port != null) { 16 | object.set_string_member("port", rule.port); 17 | } 18 | if (rule.path_prefix != null) { 19 | object.set_string_member("path_prefix", rule.path_prefix); 20 | } 21 | if (rule.path_suffix != null) { 22 | object.set_string_member("path_suffix", rule.path_suffix); 23 | } 24 | return object; 25 | } 26 | 27 | public static Fossil.GtkUi.Theming.HypertextThemeRule? rule_from_json(Json.Object object){ 28 | string member; 29 | Fossil.GtkUi.Theming.HypertextThemeRule rule; 30 | member = object.get_string_member("theme"); 31 | if (member != ""){ 32 | rule = new Fossil.GtkUi.Theming.HypertextThemeRule(member); 33 | } else { 34 | return null; 35 | } 36 | member = object.get_string_member("content_type"); 37 | if (member != ""){ 38 | rule.content_type = member; 39 | } 40 | member = object.get_string_member("scheme"); 41 | if (member != ""){ 42 | rule.scheme = member; 43 | } 44 | member = object.get_string_member("host"); 45 | if (member != ""){ 46 | rule.host = member; 47 | } 48 | member = object.get_string_member("port"); 49 | if (member != ""){ 50 | rule.port = member; 51 | } 52 | member = object.get_string_member("path_prefix"); 53 | if (member != ""){ 54 | rule.path_prefix = member; 55 | } 56 | member = object.get_string_member("path_suffix"); 57 | if (member != ""){ 58 | rule.path_suffix = member; 59 | } 60 | return rule; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/gtk_ui/json_integration/theming/hypertext_view_theme.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.JsonIntegration.Theming.HypertextViewTheme { 2 | 3 | public static Json.Object hyper_text_view_theme_to_json(Fossil.GtkUi.Theming.HypertextViewTheme theme){ 4 | var theme_object = new Json.Object(); 5 | theme_object.set_boolean_member("monospaced", theme.is_monospaced_by_default()); 6 | var prefix_object = new Json.Object(); 7 | theme.foreach_prefix((name,prefix) => { 8 | prefix_object.set_string_member(name,prefix); 9 | }); 10 | var tag_theme_object = new Json.Object(); 11 | theme.foreach_text_tag_theme((name,tag_theme) => { 12 | prefix_object.set_object_member(name, Fossil.GtkUi.JsonIntegration.Theming.TextTagTheme.text_tag_theme_to_json(tag_theme)); 13 | }); 14 | theme_object.set_object_member("prefixes", prefix_object); 15 | theme_object.set_object_member("tag_themes", tag_theme_object); 16 | return theme_object; 17 | } 18 | 19 | public static Fossil.GtkUi.Theming.HypertextViewTheme hyper_text_view_theme_from_json(Json.Object theme_object){ 20 | var theme = new Fossil.GtkUi.Theming.HypertextViewTheme(); 21 | theme.monospaced_by_default = theme_object.get_boolean_member("monospaced"); 22 | var prefix_node = theme_object.get_member("prefixes"); 23 | if (prefix_node != null){ 24 | if (prefix_node.get_node_type() == OBJECT){ 25 | var prefix_object = prefix_node.get_object(); 26 | prefix_object.foreach_member((_, name, node) => { 27 | if (node.get_node_type() == VALUE) { 28 | string? prefix = node.dup_string(); 29 | if (prefix != null) { 30 | theme.set_prefix(name, prefix); 31 | } 32 | } 33 | }); 34 | } 35 | } 36 | var tag_theme_node = theme_object.get_member("tag_themes"); 37 | if (tag_theme_node != null){ 38 | if (tag_theme_node.get_node_type() == OBJECT){ 39 | var tag_theme_object = tag_theme_node.get_object(); 40 | tag_theme_object.foreach_member((_, name, node) => { 41 | if (node.get_node_type() == OBJECT) { 42 | theme.set_text_tag_theme(name, Fossil.GtkUi.JsonIntegration.Theming.TextTagTheme.text_tag_theme_from_json(node.get_object())); 43 | } 44 | }); 45 | } 46 | } 47 | return theme; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_util/default_gtk_link_icon_loader.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyUtil.DefaultGtkLinkIconLoader { 2 | 3 | //to be replaced in the near future by an icon registry 4 | public static string guess_icon_name_for_uri(string uri){ 5 | if (uri.has_prefix("http")){ //move to before gopher when implemented 6 | return "text-html-symbolic"; 7 | } else if (uri.has_prefix("mailto:")){ 8 | return "mail-message-new-symbolic"; 9 | } else if (uri.has_prefix("gopher+writet://")){ 10 | return "document-edit-symbolic"; 11 | } else if (uri.has_prefix("gopher+writef://")){ 12 | return "document-open-symbolic"; 13 | } else if (uri.has_suffix("/")){ 14 | return "folder-symbolic"; 15 | } else if (uri.has_suffix(".txt")){ 16 | return "text-x-generic-symbolic"; 17 | } else if (uri.has_suffix(".jpg")){ 18 | return "image-x-generic-symbolic"; 19 | } else if (uri.has_suffix(".jpeg")){ 20 | return "image-x-generic-symbolic"; 21 | } else if (uri.has_suffix(".png")){ 22 | return "image-x-generic-symbolic"; 23 | } else if (uri.has_suffix(".bmp")){ 24 | return "image-x-generic-symbolic"; 25 | } else if (uri.has_suffix(".gopher")){ 26 | return "folder-symbolic"; 27 | } else if (uri.has_suffix(".gemini")){ 28 | return "folder-symbolic"; 29 | } else if (uri.has_suffix(".tar")){ 30 | return "document-save-symbolic"; 31 | } else if (uri.has_suffix(".gz")){ 32 | return "document-save-symbolic"; 33 | } else if (uri.has_suffix(".xz")){ 34 | return "document-save-symbolic"; 35 | } else if (uri.has_suffix(".zip")){ 36 | return "document-save-symbolic"; 37 | } else if (uri.has_prefix("gopher://")){ 38 | var slashindex = uri.index_of_char('/',10); 39 | if (slashindex < 0 || slashindex+1 >= uri.length){ 40 | return "folder-symbolic"; 41 | } 42 | var gophertype = uri.get(slashindex+1); 43 | if (gophertype == '0'){ //file 44 | return "text-x-generic-symbolic"; 45 | } else if (gophertype == '1'){ //directory 46 | return "folder-symbolic"; 47 | } else if (gophertype == '7'){ //search 48 | return "system-search-symbolic"; 49 | } else if (gophertype == '9'){ //binary 50 | return "document-save-symbolic"; 51 | } else if (gophertype == 'g'){ //gif 52 | return "image-x-generic-symbolic"; 53 | } else if (gophertype == 'I'){ //image 54 | return "image-x-generic-symbolic"; 55 | } else if (gophertype == 'p'){ //image 56 | return "image-x-generic-symbolic"; 57 | } 58 | } 59 | return "go-jump-symbolic"; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_util/gtk_scroll_export.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyUtil.GtkScrollExport { 2 | 3 | public static bool import(Gtk.ScrolledWindow sw, string data){ 4 | var kv = new Fossil.Util.Kv(); 5 | kv.import(data); 6 | if (kv.get_value("type") != "fossil.gtk_scroll_export.0"){ 7 | return false; 8 | } 9 | string? val = kv.get_value("vscroll"); 10 | if (val != null){ 11 | uint64 vscroll; 12 | if (Fossil.Util.Intparser.try_parse_unsigned(val,out vscroll)){ 13 | Timeout.add(100,() => { 14 | sw.vadjustment.set_value(vscroll); 15 | return false; 16 | },Priority.HIGH); 17 | } 18 | } 19 | val = kv.get_value("hscroll"); 20 | if (val != null){ 21 | uint64 hscroll; 22 | if (Fossil.Util.Intparser.try_parse_unsigned(val,out hscroll)){ 23 | Timeout.add(100,() => { 24 | sw.hadjustment.set_value(hscroll); 25 | return false; 26 | },Priority.HIGH); 27 | } 28 | } 29 | return true; 30 | } 31 | 32 | public static string export(Gtk.ScrolledWindow sw){ 33 | var kv = new Fossil.Util.Kv(); 34 | kv.set_value("type","fossil.gtk_scroll_export.0"); 35 | kv.set_value("vscroll",@"$(sw.vadjustment.get_value())"); 36 | kv.set_value("hscroll",@"$(sw.hadjustment.get_value())"); 37 | return kv.export(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_util/message_view_factory.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyUtil.MessageViewFactory : Object { 2 | 3 | string unlocalized_label_text; 4 | string unlocalized_sublabel_text; 5 | string icon_name; 6 | string view_status; 7 | Fossil.Registry.TranslationRegistry? translator; 8 | 9 | public MessageViewFactory(string view_status, string icon_name, Fossil.Registry.TranslationRegistry? translator, string? unlocalized_label_text = null, string? unlocalized_sublabel_text = null) { 10 | this.unlocalized_label_text = unlocalized_label_text; 11 | if (this.unlocalized_label_text == null){ 12 | this.unlocalized_label_text = @"view.$view_status.label"; 13 | } 14 | this.unlocalized_sublabel_text = unlocalized_sublabel_text; 15 | if (this.unlocalized_sublabel_text == null){ 16 | this.unlocalized_sublabel_text = @"view.$view_status.sublabel"; 17 | } 18 | this.icon_name = icon_name; 19 | this.view_status = view_status; 20 | if (translator == null){ 21 | this.translator = new Fossil.Registry.TranslationLanguageRegistry(); 22 | } else { 23 | this.translator = translator; 24 | } 25 | } 26 | 27 | public Fossil.GtkUi.Interface.LegacyView construct_view(){ 28 | string label_text = translator.get_localized_string(unlocalized_label_text); 29 | string sublabel_text = translator.get_localized_string(unlocalized_sublabel_text); 30 | return new Fossil.GtkUi.View.Message(view_status, label_text, sublabel_text, icon_name); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/bookmark_adder.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.BookmarkAdder : Gtk.Box { 2 | 3 | public Gtk.Entry name_entry; 4 | public Gtk.Entry uri_entry; 5 | public Gtk.Box button_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,4); 6 | public Gtk.Button cancel_button = new Gtk.Button.with_label("Cancel"); 7 | public Gtk.Button add_button = new Gtk.Button.with_label("Add"); 8 | 9 | public Fossil.Registry.BookmarkRegistry bookmark_registry; 10 | 11 | public signal void bookmark_added(Fossil.Registry.BookmarkRegistryEntry bookmark); 12 | 13 | public BookmarkAdder(Fossil.Registry.BookmarkRegistry bookmark_registry){ 14 | this.bookmark_registry = bookmark_registry; 15 | this.orientation = Gtk.Orientation.VERTICAL; 16 | this.spacing = 4; 17 | name_entry = new Gtk.Entry(); 18 | uri_entry = new Gtk.Entry(); 19 | pack_start(name_entry); 20 | pack_start(uri_entry); 21 | pack_start(button_box); 22 | button_box.set_homogeneous(true); 23 | button_box.pack_start(cancel_button); 24 | button_box.pack_start(add_button); 25 | add_button.get_style_context().add_class("suggested-action"); 26 | add_button.sensitive = false; 27 | add_button.clicked.connect(on_activate); 28 | name_entry.activate.connect(on_activate); 29 | uri_entry.activate.connect(on_activate); 30 | name_entry.buffer.deleted_text.connect(update_addbutton_sensitive); 31 | name_entry.buffer.inserted_text.connect(update_addbutton_sensitive); 32 | uri_entry.buffer.deleted_text.connect(update_addbutton_sensitive); 33 | uri_entry.buffer.inserted_text.connect(update_addbutton_sensitive); 34 | set_values("",""); 35 | } 36 | 37 | public void update_addbutton_sensitive(){ 38 | this.add_button.sensitive = (name_entry.text != "" && uri_entry.text != ""); 39 | } 40 | 41 | public void set_values(string name, string uri){ 42 | this.name_entry.text = name; 43 | this.uri_entry.text = uri; 44 | } 45 | 46 | public void on_activate(){ 47 | if (name_entry.text != "" && uri_entry.text != ""){ 48 | var bookmark = bookmark_registry.add_bookmark(name_entry.text,uri_entry.text); 49 | if (bookmark != null){ 50 | bookmark_added(bookmark); 51 | } 52 | } 53 | } 54 | 55 | public Fossil.GtkUi.LegacyWidget.BookmarkAdder localize(Fossil.Registry.TranslationRegistry translation){ 56 | name_entry.placeholder_text = translation.localize("add_bookmark.name.placeholder"); 57 | uri_entry.placeholder_text = translation.localize("add_bookmark.uri.placeholder"); 58 | cancel_button.label = translation.localize("action.cancel"); 59 | add_button.label = translation.localize("add_bookmark.add_bookmark.label"); 60 | return this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/bookmark_editor.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.BookmarkEditor : Gtk.Box { 2 | 3 | public Gtk.Entry name_entry; 4 | public Gtk.Entry uri_entry; 5 | public Gtk.Box button_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,4); 6 | public Gtk.Button cancel_button = new Gtk.Button.with_label("Cancel"); 7 | public Gtk.Button save_button = new Gtk.Button.with_label("Save"); 8 | public Gtk.Button delete_button = new Gtk.Button.with_label("Delete"); 9 | 10 | public Fossil.Registry.BookmarkRegistry bookmark_registry; 11 | Fossil.Registry.BookmarkRegistryEntry? bookmark = null; 12 | 13 | public signal void done_editing(Fossil.Registry.BookmarkRegistryEntry bookmark,bool triggered_by_save=false); 14 | 15 | public BookmarkEditor(Fossil.Registry.BookmarkRegistry bookmark_registry){ 16 | this.bookmark_registry = bookmark_registry; 17 | this.orientation = Gtk.Orientation.VERTICAL; 18 | this.spacing = 4; 19 | name_entry = new Gtk.Entry(); 20 | uri_entry = new Gtk.Entry(); 21 | pack_start(name_entry); 22 | pack_start(uri_entry); 23 | pack_start(button_box); 24 | button_box.set_homogeneous(true); 25 | button_box.pack_start(delete_button); 26 | button_box.pack_start(cancel_button); 27 | button_box.pack_start(save_button); 28 | save_button.get_style_context().add_class("suggested-action"); 29 | save_button.clicked.connect(() => { 30 | save_name(); 31 | save_uri(); 32 | done_editing(bookmark,true); 33 | }); 34 | delete_button.get_style_context().add_class("destructive-action"); 35 | delete_button.clicked.connect(() => { 36 | bookmark_registry.remove_bookmark(bookmark); 37 | done_editing(bookmark); 38 | }); 39 | cancel_button.clicked.connect(() => { 40 | done_editing(bookmark); 41 | }); 42 | name_entry.activate.connect(save_name); 43 | uri_entry.activate.connect(save_uri); 44 | } 45 | 46 | public void edit_bookmark(Fossil.Registry.BookmarkRegistryEntry? bookmark){ 47 | this.bookmark = bookmark; 48 | if (bookmark == null){ 49 | this.name_entry.text = ""; 50 | this.uri_entry.text = ""; 51 | this.sensitive = false; 52 | } else { 53 | this.name_entry.text = bookmark.name; 54 | this.uri_entry.text = bookmark.uri; 55 | this.sensitive = true; 56 | } 57 | } 58 | 59 | public void save_name(){ 60 | if (bookmark != null){ 61 | bookmark.name = name_entry.text; 62 | bookmark_registry.bookmark_modified(bookmark); 63 | } 64 | } 65 | 66 | public void save_uri(){ 67 | if (bookmark != null){ 68 | bookmark.uri = uri_entry.text; 69 | bookmark_registry.bookmark_modified(bookmark); 70 | } 71 | } 72 | 73 | public Fossil.GtkUi.LegacyWidget.BookmarkEditor localize(Fossil.Registry.TranslationRegistry translation){ 74 | name_entry.placeholder_text = translation.localize("edit_bookmark.name.placeholder"); 75 | uri_entry.placeholder_text = translation.localize("edit_bookmark.uri.placeholder"); 76 | cancel_button.label = translation.localize("action.cancel"); 77 | save_button.label = translation.localize("edit_bookmark.save_bookmark.label"); 78 | delete_button.label = translation.localize("edit_bookmark.delete_bookmark.label"); 79 | return this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/dialog_view_base.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.DialogViewBase : Gtk.Box { 2 | protected Gtk.Box outer_box = new Gtk.Box(Gtk.Orientation.VERTICAL,1); 3 | protected Gtk.Box center_box = new Gtk.Box(Gtk.Orientation.VERTICAL,8); 4 | protected Gtk.ScrolledWindow scrolled_window = new Gtk.ScrolledWindow(null,null); 5 | protected Gtk.Button backbutton = new Gtk.Button.from_icon_name("go-previous-symbolic"); 6 | protected Gtk.ActionBar actionbar = new Gtk.ActionBar(); 7 | 8 | construct{ 9 | center_box.margin = 16; 10 | this.orientation = Gtk.Orientation.VERTICAL; 11 | outer_box.set_center_widget(center_box); 12 | scrolled_window.add(outer_box); 13 | actionbar.pack_start(backbutton); 14 | pack_start(actionbar); 15 | pack_start(scrolled_window); 16 | this.set_child_packing(actionbar,false,true,0,Gtk.PackType.START); 17 | this.set_child_packing(scrolled_window,true,true,0,Gtk.PackType.START); 18 | this.show(); 19 | this.scrolled_window.show_all(); 20 | this.actionbar.hide(); 21 | this.backbutton.hide(); 22 | } 23 | 24 | public void use_as_subview(Fossil.GtkUi.LegacyWidget.Tab tab){ 25 | this.actionbar.show(); 26 | this.backbutton.show(); 27 | this.backbutton.clicked.connect(tab.go_back_subview); 28 | } 29 | 30 | public override void show_all(){ 31 | outer_box.show_all(); 32 | } 33 | 34 | public void append_widget(Gtk.Widget widget){ 35 | center_box.pack_start(widget); 36 | } 37 | 38 | public Gtk.Image append_big_icon(string icon_name){ 39 | var icon = new Gtk.Image.from_icon_name(icon_name,Gtk.IconSize.DIALOG); 40 | icon.set_pixel_size(icon.scale_factor*64); 41 | append_widget(icon); 42 | return icon; 43 | } 44 | 45 | public Gtk.Label append_label(string text){ 46 | var label = new Gtk.Label(text); 47 | label.justify = Gtk.Justification.CENTER; 48 | label.wrap_mode = Pango.WrapMode.WORD_CHAR; 49 | label.wrap = true; 50 | append_widget(label); 51 | return label; 52 | } 53 | 54 | public Gtk.Label append_big_headline(string text){ 55 | var label_attr_list = new Pango.AttrList(); 56 | label_attr_list.insert(new Pango.AttrSize(48000)); 57 | var label = append_label(text); 58 | label.attributes = label_attr_list; 59 | return label; 60 | } 61 | 62 | public Gtk.Label append_small_headline(string text){ 63 | var label_attr_list = new Pango.AttrList(); 64 | label_attr_list.insert(new Pango.AttrSize(16000)); 65 | var label_font_description = new Pango.FontDescription(); 66 | label_font_description.set_style(Pango.Style.OBLIQUE); 67 | label_attr_list.insert(new Pango.AttrFontDesc(label_font_description)); 68 | var label = append_label(text); 69 | label.attributes = label_attr_list; 70 | return label; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/inline_search.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.InlineSearch : Gtk.Bin { 2 | 3 | public signal void go(string uri); 4 | private string uri_template; 5 | private Gtk.Entry entry; 6 | 7 | public InlineSearch(string htext, string uri_template){ 8 | this.uri_template = uri_template; 9 | var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,4); 10 | box.homogeneous = false; 11 | box.margin_start = 4; 12 | entry = new Gtk.Entry(); 13 | entry.placeholder_text = htext; 14 | //entry.halign = Gtk.Align.FILL; 15 | entry.activate.connect(submit); 16 | entry.expand = true; 17 | var width = 50; 18 | if (htext.char_count() > width) { width = htext.char_count()+1;} 19 | entry.set_width_chars(width); 20 | //var icon = new Gtk.Image.from_icon_name("system-search-symbolic",Gtk.IconSize.LARGE_TOOLBAR); 21 | //icon.halign = Gtk.Align.START; 22 | var button = new Gtk.Button.from_icon_name("go-next-symbolic"); 23 | button.clicked.connect(submit); 24 | button.button_press_event.connect(handle_button_press); 25 | button.halign = Gtk.Align.START; 26 | //box.pack_start(icon); 27 | box.pack_start(entry); 28 | box.pack_start(button); 29 | box.halign = Gtk.Align.FILL; 30 | add(box); 31 | set_tooltip_text(uri_template); 32 | } 33 | 34 | private bool handle_button_press(Gdk.EventButton event){ 35 | if (event.type == BUTTON_PRESS){ 36 | if (event.button == 3 && uri_template.has_suffix("/postfile%09{search}")) { //right click 37 | var popover = new Fossil.GtkUi.LegacyWidget.InlineSearchPostB64FilePopoverEasterEgg(this,uri_template.substring(0,uri_template.length-11)+"b64"); 38 | popover.set_relative_to(this); 39 | popover.popup(); 40 | popover.show_all(); 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | 47 | private void submit(){ 48 | if (entry.text != ""){ 49 | go(uri_template.replace("{search}",Uri.escape_string(entry.text))); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/inline_search_post_b64_file_popover_easter_egg.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.InlineSearchPostB64FilePopoverEasterEgg : Gtk.Popover { 2 | 3 | private string base64_buffer = ""; 4 | private string base_uri = ""; 5 | private Fossil.GtkUi.LegacyWidget.InlineSearch parent_entry; 6 | private uint64 max_file_size = 1024*1024*128; 7 | private Gtk.Label error_label = new Gtk.Label(""); 8 | private Gtk.Entry comment_entry = new Gtk.Entry(); 9 | private Gtk.FileChooserButton file_button; 10 | private Gtk.Button post_button; 11 | private bool uploading = false; 12 | 13 | public InlineSearchPostB64FilePopoverEasterEgg(Fossil.GtkUi.LegacyWidget.InlineSearch parent, string uri){ 14 | this.base_uri = uri; 15 | this.parent_entry = parent; 16 | var box = new Gtk.Box(Gtk.Orientation.VERTICAL,4); 17 | box.margin = 8; 18 | var title_label = new Gtk.Label("This will post a base64 encoded file with a space seperated comment appended to it to"); 19 | title_label.set_tooltip_text("This feataure is intended to work with the gopherboard over at gopher://khzae.net"); 20 | var uri_label = new Gtk.Label(uri); 21 | uri_label.selectable = true; 22 | //filebutton 23 | file_button = new Gtk.FileChooserButton("Select a file to upload",Gtk.FileChooserAction.OPEN); 24 | //postbutton 25 | var post_button_label = "Upload!"; //tab.translation.localize("action.upload_file"); 26 | var post_button = new Gtk.Button.with_label(post_button_label); 27 | post_button.sensitive = false; 28 | post_button.clicked.connect(this.activate_upload); 29 | //comment_entry 30 | comment_entry.placeholder_text = "Add comment (my not be supported everywhere)"; 31 | comment_entry.activate.connect(this.activate_upload); 32 | //make file button work 33 | file_button.file_set.connect(() => { 34 | post_button.sensitive = true; 35 | }); 36 | box.pack_start(title_label); 37 | box.pack_start(uri_label); 38 | box.pack_start(file_button); 39 | box.pack_start(comment_entry); 40 | box.pack_start(error_label); 41 | box.pack_start(post_button); 42 | add(box); 43 | this.set_position(Gtk.PositionType.BOTTOM); 44 | } 45 | 46 | private void activate_upload(){ 47 | if (!uploading){ 48 | var file = file_button.get_file(); 49 | if (file != null){ 50 | post_button.sensitive = false; 51 | encode_and_send_file(file); 52 | } 53 | } 54 | } 55 | 56 | private void display_error(string error){ 57 | Timeout.add(0,() => { 58 | error_label.label = error; 59 | return false; 60 | },Priority.HIGH); 61 | } 62 | 63 | private void send(){ 64 | Timeout.add(0,() => { 65 | string comment = ""; 66 | if (comment_entry.text != ""){ 67 | comment = "%20"+Uri.escape_string(comment_entry.text); 68 | } 69 | parent_entry.go(@"$base_uri%09$base64_buffer$comment"); 70 | return false; 71 | },Priority.HIGH); 72 | } 73 | 74 | private void encode_and_send_file(File file){ 75 | uploading = true; 76 | try { 77 | var input_stream = file.read(); 78 | uint64 size = 0; 79 | uint8[] readbuffer = new uint8[1024*4*3]; //make sure the size of this is dividable by 3! 80 | while (size < max_file_size){ 81 | var bytes = input_stream.read_bytes(1024*4*3); 82 | var bytes_read = (uint64) bytes.length; 83 | size += (uint64) bytes_read; 84 | base64_buffer = base64_buffer+Base64.encode(bytes.get_data()); 85 | if (bytes_read != readbuffer.length){ 86 | break; 87 | } 88 | } 89 | print(@"Read $size bytes\n"); 90 | if (size > max_file_size){ 91 | display_error("File too large (max 128MB)"); 92 | } 93 | send(); 94 | } catch (GLib.Error e) { 95 | print("[gopher.gtk][base64_file_upload][error] "+e.message); 96 | display_error(e.message); 97 | } 98 | uploading = false; 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/link_popover.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.GtkUi.LegacyWidget.LinkPopover : Gtk.Popover { 2 | public abstract void use_uri(string uri); 3 | } 4 | 5 | // an implemntation of this can be found in the linkbutton file 6 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/menu_entries.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.MenuButton : Gtk.Button { 2 | public MenuButton(string labeltext){ 3 | var label = new Gtk.Label(labeltext); 4 | label.single_line_mode = true; 5 | label.set_justify(Gtk.Justification.LEFT); 6 | label.halign = Gtk.Align.START; 7 | halign = Gtk.Align.FILL; 8 | add(label); 9 | set_css_name("modelbutton"); 10 | get_style_context().add_class("flat"); 11 | } 12 | } 13 | 14 | public class Fossil.GtkUi.LegacyWidget.MenuSwitch : Gtk.Button { 15 | 16 | public Gtk.Switch switch_widget; 17 | 18 | public MenuSwitch(string labeltext){ 19 | var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,1); 20 | var label = new Gtk.Label(labeltext); 21 | label.single_line_mode = true; 22 | label.set_justify(Gtk.Justification.LEFT); 23 | label.halign = Gtk.Align.START; 24 | box.pack_start(label); 25 | switch_widget = new Gtk.Switch(); 26 | box.pack_end(switch_widget); 27 | halign = Gtk.Align.FILL; 28 | box.set_child_packing(label,true,true,0,Gtk.PackType.START); 29 | box.set_child_packing(switch_widget,false,true,0,Gtk.PackType.END); 30 | add(box); 31 | get_style_context().add_class("flat"); 32 | set_css_name("modelbutton"); 33 | this.clicked.connect(() => { 34 | switch_widget.set_state(!switch_widget.get_state()); 35 | }); 36 | } 37 | } 38 | 39 | public class Fossil.GtkUi.LegacyWidget.MenuBigTextDisplay : Gtk.Label { 40 | 41 | public string text { 42 | get { 43 | return this.label; 44 | } 45 | set { 46 | this.label = value; 47 | } 48 | } 49 | 50 | public MenuBigTextDisplay(string text){ 51 | set_line_wrap(true); 52 | wrap_mode = Pango.WrapMode.WORD_CHAR; 53 | wrap = true; 54 | max_width_chars = 40; 55 | /*monospace = true; 56 | left_margin = 2; 57 | right_margin = 2; 58 | editable = false;*/ 59 | selectable = true; 60 | set_css_name("entry"); 61 | get_style_context().remove_class("view"); 62 | this.text = text; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/request_argument_display.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.RequestArgumentDisplay : Gtk.Box { 2 | construct { 3 | this.orientation = Gtk.Orientation.VERTICAL; 4 | this.spacing = 4; 5 | } 6 | 7 | public RequestArgumentDisplay(Fossil.Request request){ 8 | foreach (string key in request.arguments.get_keys()){ 9 | string? val = request.arguments.get(key); 10 | if (val != null){ 11 | this.pack_start(new RequestArgumentItem(key,val)); 12 | } 13 | } 14 | this.show_all(); 15 | } 16 | 17 | } 18 | 19 | public class Fossil.GtkUi.LegacyWidget.RequestArgumentItem : Gtk.Box { 20 | public RequestArgumentItem(string key, string val){ 21 | this.orientation = Gtk.Orientation.HORIZONTAL; 22 | this.spacing = 8; 23 | this.homogeneous = true; 24 | var key_label = new Gtk.Label(key); 25 | key_label.halign = Gtk.Align.END; 26 | key_label.get_style_context().add_class("dim-label"); 27 | key_label.wrap_mode = Pango.WrapMode.WORD_CHAR; 28 | key_label.wrap = true; 29 | var value_label = new Gtk.Label(val); 30 | value_label.halign = Gtk.Align.START; 31 | value_label.wrap_mode = Pango.WrapMode.WORD_CHAR; 32 | value_label.wrap = true; 33 | this.pack_start(key_label); 34 | this.pack_start(value_label); 35 | this.show_all(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/session_chooser.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.SessionChooser : Gtk.ComboBoxText { 2 | private Fossil.GtkUi.LegacyWidget.Tab? tab = null; 3 | private bool tab_changing = false; 4 | 5 | public SessionChooser(){ 6 | this.sensitive = false; 7 | this.changed.connect(on_change); 8 | } 9 | 10 | public void use_tab(Fossil.GtkUi.LegacyWidget.Tab? tab){ 11 | tab_changing = true; 12 | if (this.tab != null){ 13 | this.tab.on_session_change.disconnect(update_active_id); 14 | } 15 | this.tab = tab; 16 | if (this.tab != null){ 17 | repopulate(); 18 | this.tab.on_session_change.connect(update_active_id); 19 | } else { 20 | this.sensitive = false; 21 | } 22 | tab_changing = false; 23 | } 24 | 25 | public void repopulate(){ 26 | this.sensitive = false; 27 | this.remove_all(); 28 | if (this.tab != null){ 29 | foreach (string key in this.tab.session_registry.sessions.get_keys()){ 30 | Fossil.Interface.Session? session = this.tab.session_registry.sessions.get(key); 31 | if (session != null){ 32 | this.append(key,@"$(session.get_name()) [$key]"); 33 | } 34 | } 35 | update_active_id(); 36 | this.sensitive = true; 37 | } 38 | } 39 | 40 | public void update_active_id(){ 41 | if (this.tab != null){ 42 | this.set_active_id(this.tab.current_session_id); 43 | } 44 | } 45 | 46 | private void on_change(){ 47 | if (this.tab != null && !tab_changing){ 48 | string? id = this.get_active_id(); 49 | if (id != null && id != tab.current_session_id){ 50 | this.tab.set_tab_session(id); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/subview_base.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.SubviewBase : Gtk.Box { 2 | 3 | public Gtk.Button backbutton = new Gtk.Button.from_icon_name("go-previous-symbolic"); 4 | public Gtk.HeaderBar titlebar = new Gtk.HeaderBar(); 5 | 6 | public SubviewBase (string title = ""){ 7 | this.orientation = Gtk.Orientation.VERTICAL; 8 | titlebar.title = title; 9 | titlebar.show_close_button = false; 10 | titlebar.has_subtitle = false; 11 | titlebar.pack_start(backbutton); 12 | this.pack_start(titlebar); 13 | this.set_child_packing(titlebar,false,true,0,Gtk.PackType.START); 14 | } 15 | 16 | public void append_child(Gtk.Widget widget){ 17 | this.pack_start(widget); 18 | this.set_child_packing(widget,false,true,0,Gtk.PackType.START); 19 | } 20 | 21 | public void set_title(string title){ 22 | titlebar.title = title; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/tab_head.vala: -------------------------------------------------------------------------------- 1 | //the widget, that you can see on the top tab, where you can select wich tab to see 2 | public class Fossil.GtkUi.LegacyWidget.TabHead : Gtk.Box { 3 | private Gtk.Button close_button; 4 | private Gtk.Label title = new Gtk.Label("💫️ New Tab"); 5 | private Fossil.GtkUi.LegacyWidget.Tab tab; 6 | private Gtk.Spinner spinner = new Gtk.Spinner(); 7 | private Gtk.Image error_icon = new Gtk.Image.from_icon_name("dialog-error", Gtk.IconSize.LARGE_TOOLBAR); 8 | private int title_chars = 25; 9 | 10 | public TabHead(Fossil.GtkUi.LegacyWidget.Tab tab) { 11 | close_button = new Gtk.Button.from_icon_name("window-close-symbolic"); 12 | close_button.relief = Gtk.ReliefStyle.NONE; 13 | this.orientation = Gtk.Orientation.HORIZONTAL; 14 | pack_start(spinner); 15 | pack_start(error_icon); 16 | pack_start(title); 17 | pack_end(close_button); 18 | set_child_packing(spinner, false, true, 0, Gtk.PackType.START); 19 | set_child_packing(title, true, true, 0, Gtk.PackType.START); 20 | set_child_packing(close_button, false, true, 0, Gtk.PackType.END); 21 | show_all(); 22 | close_button.clicked.connect(tab.close); 23 | this.tab = tab; 24 | this.tab.on_cleanup.connect(this.detach); 25 | this.tab.on_title_change.connect(this.refresh_title); 26 | refresh_title(tab.title, tab.display_state); 27 | } 28 | 29 | public void refresh_title(string title, Fossil.Ui.TabDisplayState state){ 30 | spinner.active = state == LOADING; 31 | spinner.visible = state == LOADING; 32 | error_icon.visible = state == ERROR; 33 | if (title.char_count() > title_chars){ 34 | if (title == tab.uri){ 35 | var startcut = title.index_of_nth_char(title_chars/2); 36 | var endcut = title.index_of_nth_char(title.char_count()-(title_chars/2)); 37 | this.title.label = title[0:startcut]+"…"+title.slice(endcut,title.length); 38 | } else { 39 | var cutat = title.index_of_nth_char(title_chars); 40 | this.title.label = title[0:cutat]+"…"; 41 | } 42 | } else { 43 | this.title.label = title; 44 | } 45 | if (title == tab.uri){ 46 | this.tooltip_text = title; 47 | } else { 48 | this.tooltip_markup = ""+Markup.escape_text(title)+"\n"+Markup.escape_text(tab.uri); 49 | } 50 | } 51 | 52 | public void detach(){ 53 | tab.on_cleanup.disconnect(detach); 54 | tab.on_title_change.disconnect(refresh_title); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/text_content.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.TextContent : Gtk.ScrolledWindow { 2 | public Gtk.TextView textview = null; 3 | 4 | construct { 5 | textview = new Gtk.TextView(); 6 | textview.editable = false; 7 | textview.wrap_mode = Gtk.WrapMode.WORD; 8 | textview.set_monospace(true); 9 | textview.set_left_margin(30); 10 | textview.set_top_margin(20); 11 | add(textview); 12 | } 13 | 14 | public void append_text(string text){ 15 | Gtk.TextIter end_iter; 16 | textview.buffer.get_end_iter(out end_iter); 17 | textview.buffer.insert(ref end_iter,text,text.length); 18 | } 19 | 20 | public void append_widget(Gtk.Widget widget){ 21 | append_widget_inline(widget); 22 | append_text("\n"); 23 | } 24 | 25 | public void append_widget_inline(Gtk.Widget widget){ 26 | Gtk.TextIter end_iter; 27 | textview.buffer.get_end_iter(out end_iter); 28 | var anchor = textview.buffer.create_child_anchor(end_iter); 29 | textview.add_child_at_anchor(widget,anchor); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/text_entry.vala: -------------------------------------------------------------------------------- 1 | private class Fossil.GtkUi.LegacyWidget.TextEntrySingleLine : Gtk.Bin { 2 | 3 | public signal void submit(string text); 4 | public Gtk.Entry entry; 5 | public Gtk.Button button; 6 | 7 | public TextEntrySingleLine(string placeholder, string text, string icon_name, int min_width = 0){ 8 | var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL,4); 9 | box.homogeneous = false; 10 | box.margin_start = 4; 11 | entry = new Gtk.Entry(); 12 | entry.placeholder_text = placeholder; 13 | entry.activate.connect(on_activate); 14 | entry.text = text; 15 | if (min_width < 0) { 16 | min_width = -min_width; 17 | if (placeholder.char_count() > min_width) { min_width = placeholder.char_count()+1;} 18 | } 19 | if (min_width > 0){ 20 | entry.set_width_chars(min_width); 21 | } 22 | button = new Gtk.Button.from_icon_name(icon_name); 23 | button.clicked.connect(on_activate); 24 | button.halign = Gtk.Align.START; 25 | box.pack_start(entry); 26 | box.pack_start(button); 27 | box.halign = Gtk.Align.FILL; 28 | add(box); 29 | } 30 | 31 | private void on_activate(){ 32 | submit(entry.text); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/gtk_ui/legacy_widget/view_chooser.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.LegacyWidget.ViewChooser : Gtk.ComboBoxText { 2 | 3 | private Fossil.GtkUi.LegacyWidget.Tab? tab = null; 4 | private bool tab_changing = false; 5 | private bool id_changing = false; 6 | 7 | public ViewChooser(){ 8 | this.sensitive = false; 9 | this.changed.connect(on_change); 10 | } 11 | 12 | public void use_tab(Fossil.GtkUi.LegacyWidget.Tab? tab){ 13 | tab_changing = true; 14 | if (this.tab != null){ 15 | this.tab.view_chooser.scores_changed.disconnect(repopulate); 16 | this.tab.on_view_change.disconnect(update_active_id); 17 | } 18 | this.tab = tab; 19 | if (this.tab != null){ 20 | repopulate(); 21 | this.tab.view_chooser.scores_changed.connect(repopulate); 22 | this.tab.on_view_change.connect(update_active_id); 23 | } else { 24 | this.sensitive = false; 25 | } 26 | tab_changing = false; 27 | } 28 | 29 | public void repopulate(){ 30 | this.sensitive = false; 31 | this.remove_all(); 32 | if (this.tab != null){ 33 | foreach (string key in this.tab.view_chooser.matches.get_keys()){ 34 | uint32 score = this.tab.view_chooser.matches.get(key); 35 | this.append(key,@"$key [$score]"); 36 | } 37 | update_active_id(); 38 | this.sensitive = true; 39 | } 40 | } 41 | 42 | public void update_active_id(){ 43 | if (this.tab != null){ 44 | id_changing = true; 45 | this.set_active_id(this.tab.current_view_id); 46 | id_changing = false; 47 | } 48 | } 49 | 50 | private void on_change(){ 51 | if (this.tab != null && !tab_changing && !id_changing){ 52 | string? id = this.get_active_id(); 53 | if (id != null){ 54 | this.tab.update_view(id,"view chooser"); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/gtk_ui/settings_integration/settings_hypertext_json_theme_loader.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeLoader : Fossil.GtkUi.Interface.Theming.HypertextThemeLoader, Object { 2 | 3 | public string module_name = "Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeLoader"; 4 | 5 | private Fossil.Interface.Settings.Provider settings_provider; 6 | private string prefix; 7 | private HashTable theme_cache = new HashTable(str_hash, str_equal); 8 | 9 | public SettingsHypertextJsonThemeLoader(Fossil.Interface.Settings.Provider settings_provider, string prefix){ 10 | this.settings_provider = settings_provider; 11 | this.prefix = prefix; 12 | this.settings_provider.settings_updated.connect(on_settings_updated); 13 | } 14 | 15 | ~SettingsHypertextJsonThemeLoader(){ 16 | this.settings_provider.settings_updated.disconnect(on_settings_updated); 17 | } 18 | 19 | private void on_settings_updated(string path){ 20 | lock(theme_cache){ 21 | if (path.has_prefix(prefix)){ 22 | if (path == prefix || path+"." == prefix){ 23 | theme_cache.remove_all(); 24 | } else { 25 | theme_cache.remove(path.substring(prefix.length)); 26 | } 27 | } 28 | } 29 | } 30 | 31 | private Fossil.GtkUi.Interface.Theming.HypertextViewTheme? load_theme_by_name(string name){ 32 | print(@"[Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeLoader] Loading theme $name at $prefix$name.json\n"); 33 | string path = @"$prefix$name.json"; 34 | var theme_json = settings_provider.read_object(path); 35 | if (theme_json != null) { 36 | try { 37 | Json.Parser parser = new Json.Parser(); 38 | parser.load_from_data(theme_json); 39 | var root_node = parser.get_root(); 40 | if (root_node != null){ 41 | if (root_node.get_node_type() == OBJECT) { 42 | var theme_object = root_node.get_object(); 43 | return Fossil.GtkUi.JsonIntegration.Theming.HypertextViewTheme.hyper_text_view_theme_from_json(theme_object); 44 | } 45 | } 46 | settings_provider.submit_client_report(new Fossil.Settings.Report(module_name, path, null, null, @"Imported theme")); 47 | } catch (Error e) { 48 | settings_provider.submit_client_report(new Fossil.Settings.Report(module_name, path, e.message, null, "Error while decoding json")); 49 | //print("[Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeLoader] Error while parsing theme json: "+e.message+"\n"); 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | ////////////////////////////////////////////////////////////// 56 | // Fossil.GtkUi.Interface.Theming.HypertextThemeLoader // 57 | ////////////////////////////////////////////////////////////// 58 | 59 | public Fossil.GtkUi.Interface.Theming.HypertextViewTheme? get_theme_by_name(string name){ 60 | lock (theme_cache) { 61 | Fossil.GtkUi.Interface.Theming.HypertextViewTheme? theme = theme_cache.get(@"$name.json"); 62 | if (theme != null){ 63 | return theme; 64 | } 65 | theme = load_theme_by_name(name); 66 | theme_cache.set(@"$name.json", theme); 67 | return theme; 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/gtk_ui/settings_integration/settings_hypertext_json_theme_rule_provider.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeRuleProvider : Fossil.GtkUi.Interface.HypertextThemeRuleProvider, Object { 2 | 3 | private List rules = new List(); 4 | private Fossil.Interface.Settings.Provider settings_provider; 5 | private string path; 6 | public string module_name = "Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeRuleProvider"; 7 | 8 | public SettingsHypertextJsonThemeRuleProvider(Fossil.Interface.Settings.Provider settings_provider, string path){ 9 | this.settings_provider = settings_provider; 10 | this.path = path; 11 | reload(); 12 | settings_provider.settings_updated.connect(update_listener); 13 | } 14 | 15 | ~SettingsHypertextJsonThemeRuleProvider(){ 16 | settings_provider.settings_updated.disconnect(update_listener); 17 | } 18 | 19 | private void update_listener(string path_prefix){ 20 | if (path.has_prefix(path_prefix)) { 21 | reload(); 22 | } 23 | } 24 | 25 | public void reload(){ 26 | lock(rules) { 27 | if (rules.length()>0){ 28 | rules = new List(); 29 | } 30 | var rules_json = settings_provider.read_object(path); 31 | if (rules_json == null) { 32 | return; 33 | } 34 | try { 35 | uint counter = 0; 36 | Json.Parser parser = new Json.Parser(); 37 | parser.load_from_data(rules_json); 38 | var root_node = parser.get_root(); 39 | if (root_node != null){ 40 | if (root_node.get_node_type() == ARRAY) { 41 | var rules_array = root_node.get_array(); 42 | foreach (unowned Json.Node item in rules_array.get_elements()) { 43 | if (item.get_node_type() == OBJECT) { 44 | var rule = Fossil.GtkUi.JsonIntegration.Theming.HypertextThemeRule.rule_from_json(item.get_object()); 45 | if (rule != null) { 46 | rules.append(rule); 47 | counter++; 48 | } 49 | } 50 | } 51 | } 52 | } 53 | settings_provider.submit_client_report(new Fossil.Settings.Report(module_name, path, null, null, @"Imported $counter rules")); 54 | } catch (Error e) { 55 | settings_provider.submit_client_report(new Fossil.Settings.Report(module_name, path, e.message, null, "Error while decoding json")); 56 | //print("[Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeRuleProvider] Error while parsing json "+e.message+"\n"); 57 | } 58 | } 59 | } 60 | 61 | //////////////////////////////////////////////////////////// 62 | // Fossil.GtkUi.Interface.HypertextThemeRuleProvider // 63 | //////////////////////////////////////////////////////////// 64 | 65 | public void foreach_relevant_rule(string content_type, string uri, Func cb){ 66 | rules.foreach(cb); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/gtk_ui/theming/default_hypertext_view_theme_provider.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.Theming.DefaultHypertextViewThemeProvider : Fossil.GtkUi.Interface.Theming.HypertextViewThemeProvider, Object { 2 | 3 | private Fossil.GtkUi.Interface.Theming.HypertextThemeLoader? theme_loader = null; 4 | private Fossil.GtkUi.Interface.HypertextThemeRuleProvider? rule_provider = null; 5 | private Fossil.GtkUi.Interface.Theming.HypertextViewTheme default_theme; 6 | 7 | public DefaultHypertextViewThemeProvider(Fossil.GtkUi.Interface.Theming.HypertextViewTheme default_theme){ 8 | this.default_theme = default_theme; 9 | } 10 | 11 | public void set_theme_loader(Fossil.GtkUi.Interface.Theming.HypertextThemeLoader theme_loader){ 12 | this.theme_loader = theme_loader; 13 | } 14 | 15 | public void set_rule_provider(Fossil.GtkUi.Interface.HypertextThemeRuleProvider rule_provider){ 16 | this.rule_provider = rule_provider; 17 | } 18 | 19 | //////////////////////////////////////////////////////////////////// 20 | // Fossil.GtkUi.Interface.Theming.HypertextViewThemeProvider // 21 | //////////////////////////////////////////////////////////////////// 22 | 23 | public Fossil.GtkUi.Interface.Theming.HypertextViewTheme? get_theme(string content_type, string uri){ 24 | print(@"[Fossil.GtkUi.Theming.DefaultHypertextViewThemeProvider] Looking up theme for $uri $content_type\n"); 25 | if (theme_loader == null && rule_provider == null){ 26 | return null; 27 | } 28 | var parsed_uri = new Fossil.Util.ParsedUri(uri); 29 | Fossil.GtkUi.Interface.Theming.HypertextViewTheme? best_theme = null; 30 | int best_score = 0; 31 | int score = 0; 32 | rule_provider.foreach_relevant_rule(content_type, uri, (rule) => { 33 | score = rule.calculate_score(content_type, parsed_uri.scheme, parsed_uri.host, parsed_uri.port, parsed_uri.path); 34 | if (score > best_score){ 35 | print(@"\t$(rule.theme_name) @ $score\n"); 36 | //load the theme to make sure it can be loaded 37 | var theme = theme_loader.get_theme_by_name(rule.theme_name); 38 | if (theme != null) { 39 | print("\t\tloaded successfully!\n"); 40 | best_score = score; 41 | best_theme = theme; 42 | } 43 | } 44 | }); 45 | return best_theme; 46 | } 47 | 48 | public Fossil.GtkUi.Interface.Theming.HypertextViewTheme get_default_theme(){ 49 | return default_theme; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/gtk_ui/theming/hypertext_theme_rule.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.Theming.HypertextThemeRule : Object { 2 | 3 | public string theme_name; 4 | public string? content_type = null; 5 | public string? scheme = null; 6 | public string? host = null; 7 | public string? port = null; 8 | public string? path_prefix = null; 9 | public string? path_suffix = null; 10 | 11 | public HypertextThemeRule(string theme_name){ 12 | this.theme_name = theme_name; 13 | } 14 | 15 | public int calculate_score(string content_type, string? scheme, string? host, string? port, string? path){ 16 | int score = 0; 17 | bool all_null = true; 18 | if (this.content_type != null) { 19 | all_null = false; 20 | if (content_type.has_prefix(this.content_type)) { 21 | score += 100000; 22 | } else { 23 | return 0; 24 | } 25 | } 26 | if (this.scheme != null) { 27 | all_null = false; 28 | if (scheme != null) { 29 | if (scheme == this.scheme){ 30 | score += 5000; 31 | score += scheme.length*10; 32 | } else if (scheme.has_prefix(this.scheme+"+")) { 33 | score += 5000; 34 | score += scheme.length*10; 35 | } else { 36 | return 0; 37 | } 38 | } else { 39 | return 0; 40 | } 41 | } 42 | if (this.host != null) { 43 | all_null = false; 44 | if (host != null) { 45 | if (host == this.host){ 46 | score += this.host.length*2; 47 | score += 1000; 48 | } else if (host.has_suffix("."+this.host)) { 49 | score += this.host.length*2; 50 | } else { 51 | return 0; 52 | } 53 | } else { 54 | return 0; 55 | } 56 | } 57 | if (this.port != null) { 58 | all_null = false; 59 | if (this.port == port) { 60 | score += 1000; 61 | } else { 62 | return 0; 63 | } 64 | } 65 | if (this.path_prefix != null) { 66 | all_null = false; 67 | if (path != null) { 68 | if (path.has_prefix(this.path_prefix)) { 69 | score += this.path_prefix.length; 70 | score += 100; 71 | } else { 72 | return 0; 73 | } 74 | } else { 75 | return 0; 76 | } 77 | } 78 | if (this.path_suffix != null) { 79 | all_null = false; 80 | if (path != null) { 81 | if (path.has_suffix(this.path_suffix)) { 82 | score += this.path_suffix.length; 83 | } else { 84 | return 0; 85 | } 86 | } else { 87 | return 0; 88 | } 89 | } 90 | if (all_null) { 91 | score++; 92 | } 93 | return score; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/gtk_ui/theming/hypertext_view_theme.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.Theming.HypertextViewTheme : Fossil.GtkUi.Interface.Theming.HypertextViewTheme, Object { 2 | 3 | public bool monospaced_by_default = true; 4 | 5 | private HashTable prefixes = new HashTable(str_hash, str_equal); 6 | private HashTable text_tag_themes = new HashTable(str_hash, str_equal); 7 | 8 | public void set_prefix(string name, string? prefix){ 9 | if (prefix != null) { 10 | prefixes.set(name, prefix); 11 | } else { 12 | prefixes.remove(name); 13 | } 14 | } 15 | 16 | public void set_text_tag_theme(string name, Fossil.GtkUi.Theming.TextTagTheme? theme){ 17 | if (theme != null) { 18 | text_tag_themes.set(name, theme); 19 | } else { 20 | text_tag_themes.remove(name); 21 | } 22 | } 23 | 24 | public void foreach_prefix(HFunc cb){ 25 | prefixes.foreach(cb); 26 | } 27 | 28 | public void foreach_text_tag_theme(HFunc cb){ 29 | text_tag_themes.foreach(cb); 30 | } 31 | 32 | //////////////////////////////////////////////////////////// 33 | // Fossil.GtkUi.Interface.Theming.HypertextViewTheme // 34 | //////////////////////////////////////////////////////////// 35 | 36 | public string? get_prefix(string name){ 37 | return prefixes.get(name); 38 | } 39 | 40 | public Fossil.GtkUi.Theming.TextTagTheme? get_text_tag_theme(string name){ 41 | return text_tag_themes.get(name); 42 | } 43 | 44 | public bool is_monospaced_by_default(){ 45 | return monospaced_by_default; 46 | } 47 | 48 | public string get_best_matching_text_tag_theme_name(string[] classes){ 49 | if (classes.length == 0) { return "*"; } 50 | int best_score = 0; 51 | string best_theme_name = "*"; 52 | foreach_text_tag_theme((name, _) => { 53 | string[] name_tokens = name.split(" "); 54 | if (name_tokens.length <= classes.length){ 55 | if (name_tokens[0].has_prefix(":") || name_tokens[0] == classes[0]) { 56 | int score = -name_tokens.length; 57 | foreach(string token in name_tokens) { 58 | bool found = false; 59 | foreach (string clazz in classes) { 60 | if (token == clazz) { 61 | score += 100; 62 | found = true; 63 | break; 64 | } 65 | } 66 | if (!found) { 67 | score = -1000; 68 | break; 69 | } 70 | } 71 | if (score > best_score){ 72 | best_score = score; 73 | best_theme_name = name; 74 | } 75 | } 76 | } 77 | }); 78 | return best_theme_name; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/gtk_ui/theming/text_tag_theme.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.Theming.TextTagTheme : Object { 2 | 3 | public Gdk.RGBA? foreground_color = null; 4 | public Gdk.RGBA? background_color = null; 5 | public Gdk.RGBA? paragraph_background_color = null; 6 | public Gdk.RGBA? underline_color = null; 7 | 8 | public double? scale = null; 9 | public int? indent = null; 10 | public int? left_margin = null; 11 | public int? right_margin = null; 12 | public Pango.FontDescription? font_description = null; 13 | public Gtk.WrapMode? wrap_mode = null; 14 | public Pango.Underline? underline = null; 15 | public bool? invisible = null; 16 | 17 | 18 | public void apply_theme(Gtk.TextTag text_tag){ 19 | if (foreground_color != null) { text_tag.foreground_rgba = foreground_color; } 20 | if (background_color != null) { text_tag.background_rgba = background_color; } 21 | if (paragraph_background_color != null) { text_tag.paragraph_background_rgba = paragraph_background_color; } 22 | if (underline_color != null) { text_tag.underline_rgba = underline_color; } 23 | 24 | if (scale != null) { text_tag.scale = scale; } 25 | if (indent != null) { text_tag.indent = indent; } 26 | if (left_margin != null) { text_tag.left_margin = left_margin; } 27 | if (right_margin != null) { text_tag.right_margin = right_margin; } 28 | if (invisible != null) { text_tag.invisible = invisible; } 29 | if (font_description != null) { text_tag.font_desc = font_description; } 30 | if (wrap_mode != null) { text_tag.wrap_mode = wrap_mode; } 31 | if (underline != null) { text_tag.underline = underline; } 32 | 33 | //Manually apply default theme, because we yeeted the theme engine 34 | text_tag.left_margin = 20; 35 | text_tag.right_margin = 20; 36 | 37 | } 38 | 39 | public static void untheme(Gtk.TextTag text_tag){ 40 | text_tag.foreground_set = false; 41 | text_tag.background_set = false; 42 | text_tag.paragraph_background_set = false; 43 | text_tag.underline_set = false; 44 | text_tag.scale_set = false; 45 | text_tag.wrap_mode_set = false; 46 | text_tag.indent_set = false; 47 | text_tag.left_margin_set = false; 48 | text_tag.right_margin_set = false; 49 | text_tag.invisible_set = false; 50 | 51 | text_tag.family_set = false; 52 | text_tag.font_features_set = false; 53 | text_tag.style_set = false; 54 | text_tag.variant_set = false; 55 | text_tag.weight_set = false; 56 | text_tag.size_set = false; 57 | text_tag.stretch_set = false; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/gtk_ui/views/cache.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Cache : Gtk.Bin, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request? request = null; 4 | private Fossil.GtkUi.LegacyWidget.Tab? tab = null; 5 | private Fossil.Registry.TranslationRegistry? translation = null; 6 | private Fossil.GtkUi.LegacyWidget.CacheView? cacheview = null; 7 | 8 | public Cache(Fossil.Registry.TranslationRegistry? translation){ 9 | this.translation = translation; 10 | } 11 | 12 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 13 | if (!(request.status == "interactive/cache")) {return false;} 14 | this.request = request; 15 | this.tab = tab; 16 | var icache = tab.session.get_cache() as Fossil.Store.Cache; 17 | var cache = tab.session.get_cache(); 18 | 19 | if (icache != null){ 20 | cacheview = new Fossil.GtkUi.LegacyWidget.CacheView(icache,tab,translation); 21 | add(cacheview); 22 | cacheview.show(); 23 | show(); 24 | } else if (cache != null){ 25 | add(new Gtk.Label("This session has a cache, but this view currently has no way to show its content")); 26 | show_all(); 27 | } else { 28 | add(new Gtk.Label("It seems like, this session does not have a cache.\nSession name: "+tab.session.get_name())); 29 | show_all(); 30 | } 31 | return true; 32 | } 33 | 34 | public bool canHandleCurrentResource(){ 35 | if (request == null){ 36 | return false; 37 | }else{ 38 | return request.status == "interactive/cache"; 39 | } 40 | } 41 | 42 | public void cleanup(){ 43 | if (cacheview != null){ 44 | cacheview.cleanup(); 45 | } 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/gtk_ui/views/download.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Download : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Label name_label = new Gtk.Label(""); 5 | private Gtk.Button save_button = new Gtk.Button.with_label("Save"); 6 | private Gtk.Button open_button = new Gtk.Button.with_label("Open in external viewer"); 7 | private string title = "Downloaded!"; 8 | 9 | public Download(Fossil.Registry.TranslationRegistry? translation = null) { 10 | if (translation != null){ 11 | save_button.label = translation.localize("view.fossil.download.save_button.label"); 12 | open_button.label = translation.localize("view.fossil.download.open_button.label"); 13 | this.title = translation.localize("view.fossil.download.title"); 14 | } 15 | save_button.get_style_context().add_class("suggested-action"); 16 | this.append_big_icon("document-save-symbolic"); 17 | this.append_big_headline(this.title); 18 | name_label = this.append_small_headline(""); 19 | this.append_widget(save_button); 20 | this.append_widget(open_button); 21 | 22 | } 23 | 24 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 25 | if (!(request.status == "success")) {return false;} 26 | this.request = request; 27 | name_label.label = Fossil.Util.Uri.get_filename(request.uri); 28 | save_button.clicked.connect(() => { 29 | tab.download(); 30 | }); 31 | open_button.clicked.connect(() => { 32 | tab.open_resource_externally(); 33 | }); 34 | show_all(); 35 | return true; 36 | } 37 | 38 | public bool canHandleCurrentResource(){ 39 | if (request == null){ 40 | return false; 41 | }else{ 42 | return request.status == "success"; 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/gtk_ui/views/error_generic.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Error.Generic : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Label statuslabel; 5 | private Gtk.Label sublabel; 6 | private Gtk.Label headline; 7 | private string view_status = null; 8 | 9 | public Generic() { 10 | //this.append_big_icon("dialog-error-symbolic"); 11 | headline = this.append_big_headline("- ERROR -"); 12 | statuslabel = this.append_small_headline("---"); 13 | sublabel = this.append_label("..."); 14 | 15 | } 16 | 17 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 18 | //if (!(request.status.has_prefix("error/") || request.status == "error")) {return false;} 19 | view_status = request.status; 20 | this.request = request; 21 | headline.label = tab.translation.localize("view.error.label"); 22 | statuslabel.label = request.status; 23 | sublabel.label = request.substatus; 24 | var argument_display = new Fossil.GtkUi.LegacyWidget.RequestArgumentDisplay(request); 25 | append_widget(argument_display); 26 | //nameLabel.label = request.name; 27 | show_all(); 28 | if (as_subview){ use_as_subview(tab); } 29 | return true; 30 | } 31 | 32 | public bool canHandleCurrentResource(){ 33 | if (request == null){ 34 | return false; 35 | }else{ 36 | return request.status == view_status; //refresh when status changes 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/gtk_ui/views/geminiInput.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.GeminiInput : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | 5 | private Fossil.GtkUi.View.GeminiInputInput? input = null; 6 | private bool confidential = false; 7 | 8 | construct { 9 | this.append_big_headline(">_"); 10 | } 11 | 12 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 13 | if (!(request.status == "success" && request.resource.mimetype == "gemini/input")) {return false;} 14 | this.request = request; 15 | this.append_small_headline(request.resource.name); 16 | input = new Fossil.GtkUi.View.GeminiInputInput("",tab.uri); 17 | input.go.connect((s,uri) => {tab.go_to_uri(uri);}); 18 | if (request.arguments.get("gemini.statuscode") == "11") { 19 | confidential = true; 20 | input.entry.input_purpose = Gtk.InputPurpose.PASSWORD; 21 | input.entry.set_visibility(false); 22 | } 23 | this.append_widget(input); 24 | this.center_box.set_child_packing(input,true,true,0,Gtk.PackType.START); 25 | show_all(); 26 | input.entry.grab_focus(); 27 | return true; 28 | } 29 | 30 | public bool canHandleCurrentResource(){ 31 | if (request == null){ 32 | return false; 33 | }else{ 34 | return (request.status == "success" && request.resource.mimetype == "gemini/input"); 35 | } 36 | } 37 | 38 | public bool import(string data){ 39 | if (confidential || input == null){ 40 | return false; 41 | } 42 | var kv = new Fossil.Util.Kv(); 43 | kv.import(data); 44 | if (kv.get_value("view_type") != "fossil.gemini_input.0"){ 45 | return false; 46 | } 47 | string? val = kv.get_value("input"); 48 | if (val != null){ 49 | input.entry.text = val; 50 | } 51 | return true; 52 | } 53 | 54 | public string? export(){ 55 | if (confidential || input == null){ 56 | return null; 57 | } 58 | var kv = new Fossil.Util.Kv(); 59 | kv.set_value("view_type","fossil.gemini_input.0"); 60 | kv.set_value("input",input.entry.text); 61 | return kv.export(); 62 | } 63 | 64 | } 65 | 66 | private class Fossil.GtkUi.View.GeminiInputInput : Gtk.Box { 67 | 68 | public signal void go(string uri); 69 | private string base_uri; 70 | public Gtk.Entry entry; 71 | 72 | public GeminiInputInput(string htext,string uri){ 73 | var indexofqm = uri.index_of_char('?'); 74 | if (indexofqm < 0){ 75 | base_uri = uri; 76 | } else { 77 | base_uri = uri.substring(0,indexofqm); 78 | } 79 | this.homogeneous = false; 80 | this.orientation = Gtk.Orientation.HORIZONTAL; 81 | this.spacing = 4; 82 | entry = new Gtk.Entry(); 83 | entry.placeholder_text = htext; 84 | entry.activate.connect(submit); 85 | entry.expand = true; 86 | var button = new Gtk.Button.from_icon_name("go-next-symbolic"); 87 | button.get_style_context().add_class("suggested-action"); 88 | button.clicked.connect(submit); 89 | pack_start(entry); 90 | pack_start(button); 91 | set_child_packing(button,false,false,0,Gtk.PackType.START); 92 | set_child_packing(entry,true,true,0,Gtk.PackType.START); 93 | expand = true; 94 | } 95 | 96 | private void submit(){ 97 | if (entry.text != ""){ 98 | var searchstring = Uri.escape_string(entry.text); 99 | go(@"$base_uri?$searchstring"); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/gtk_ui/views/hypertext.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Hypertext : Gtk.Bin, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Fossil.GtkUi.LegacyWidget.Tab tab; 5 | 6 | private Fossil.Interface.Document.TokenParserFactory token_parser_factory; 7 | private Fossil.GtkUi.Interface.Theming.HypertextViewThemeProvider theme_provider; 8 | private Fossil.GtkUi.LegacyWidget.HypertextContent? hypertext = null; 9 | 10 | public Hypertext(Fossil.Interface.Document.TokenParserFactory token_parser_factory, Fossil.GtkUi.Interface.Theming.HypertextViewThemeProvider theme_provider){ 11 | this.token_parser_factory = token_parser_factory; 12 | this.theme_provider = theme_provider; 13 | } 14 | 15 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 16 | this.tab = tab; 17 | if (request.status == "success" && token_parser_factory.has_parser_for(request.resource.mimetype)){ 18 | var theme = theme_provider.get_theme(request.resource.mimetype, request.uri); 19 | if (theme == null){ 20 | theme = theme_provider.get_default_theme(); 21 | } 22 | hypertext = new Fossil.GtkUi.LegacyWidget.HypertextContent(theme, new Fossil.GtkUi.LegacyWidget.LinkButtonPopover(tab)); 23 | hypertext.go.connect(on_go_event); 24 | add(hypertext); 25 | var input_stream = tab.get_file_content_stream(); 26 | if (input_stream == null) { 27 | hypertext.append_styled_text("The cache file for this resource does not exist!\nReloading the page should help,\nif not please contact the developer!", "exception", true, false, 0); 28 | } else { 29 | print("hypertext: rendering content\n"); 30 | var parser = token_parser_factory.get_token_parser(request.resource.mimetype); 31 | parser.set_input_stream(input_stream); 32 | bool first_title = true; 33 | while (true) { 34 | var token = parser.next_token(); 35 | if (token == null) { break; } 36 | if (first_title && token.token_type == TITLE){ 37 | first_title = false; 38 | tab.set_title(token.text); 39 | } 40 | hypertext.append_token(token); 41 | } 42 | } 43 | } else { 44 | return false; 45 | } 46 | show_all(); 47 | this.request = request; 48 | return true; 49 | } 50 | 51 | public bool canHandleCurrentResource(){ 52 | if (request == null) { 53 | return false; 54 | } else { 55 | return request.status == "success" && token_parser_factory.has_parser_for(request.resource.mimetype); 56 | } 57 | } 58 | 59 | protected void on_go_event(string uri, bool alt){ 60 | if (this.tab != null) { 61 | if (alt) { 62 | tab.open_uri_in_new_tab(uri); 63 | } else { 64 | tab.go_to_uri(uri); 65 | } 66 | } 67 | } 68 | 69 | public bool import(string data){ 70 | if (hypertext == null) { return false; } 71 | var kv = new Fossil.Util.Kv(); 72 | kv.import(data); 73 | if (kv.get_value("view_type") != "fossil.hyper_text.0") { 74 | return false; 75 | } 76 | string? val = kv.get_value("scroll"); 77 | if (val != null) { 78 | Fossil.GtkUi.LegacyUtil.GtkScrollExport.import(hypertext, val); 79 | } 80 | return true; 81 | } 82 | 83 | public string? export(){ 84 | if (hypertext == null) { return null; } 85 | var kv = new Fossil.Util.Kv(); 86 | kv.set_value("view_type","fossil.hyper_text.0"); 87 | kv.set_value("scroll",Fossil.GtkUi.LegacyUtil.GtkScrollExport.export(hypertext)); 88 | return kv.export(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/gtk_ui/views/image.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Image : Gtk.ScrolledWindow, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Image image; 5 | private Gdk.Pixbuf pixbuf; 6 | 7 | public float factor = 1; 8 | 9 | public bool autoscale = true; 10 | 11 | construct {} 12 | 13 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 14 | if ((request.status == "success") && request.resource.mimetype.has_prefix("image/")){ 15 | try { 16 | var input_stream = tab.get_file_content_stream(); 17 | if (input_stream == null) { 18 | return false; 19 | } 20 | pixbuf = new Gdk.Pixbuf.from_stream(input_stream); 21 | image = new Gtk.Image.from_pixbuf(pixbuf); 22 | add(image); 23 | size_allocate.connect(trigger_autoscale); 24 | } catch (GLib.Error e) { 25 | print(@"[image] Error while loading image ($(request.uri))\n$(e.message)"); 26 | return false; 27 | } 28 | } else { 29 | return false; 30 | } 31 | this.request = request; 32 | 33 | set_events(Gdk.EventMask.ALL_EVENTS_MASK); 34 | 35 | this.scroll_event.connect((event) => { 36 | if((event.state & Gdk.ModifierType.CONTROL_MASK) > 0){ 37 | float new_factor = factor-((float) event.delta_y)/10; 38 | if (new_factor > 0 && new_factor < 1000){ 39 | scale(new_factor); 40 | } 41 | autoscale = false; 42 | return true; 43 | } 44 | return false; 45 | }); 46 | show_all(); 47 | return true; 48 | } 49 | 50 | public void trigger_autoscale(Gtk.Allocation rect){ 51 | if(autoscale){ 52 | scale_to_window(false); 53 | } 54 | } 55 | 56 | public void scale_to_window(bool do_not_magnify = true){ 57 | float ph = pixbuf.get_height(); 58 | float pw = pixbuf.get_width(); 59 | float wh = get_allocated_height(); 60 | float ww = get_allocated_width(); 61 | float hr = ph/wh; 62 | float wr = pw/ww; 63 | float new_factor = factor; 64 | if (hr > 1 || wr > 1 || !do_not_magnify){ 65 | new_factor = 1/float.max(hr,wr); 66 | //print(@"[image] rect.width=$(rect.width) rect.height=$(rect.height)\n"); 67 | //print(@"[image] width: $pw,$ww rat: $wr | height: $ph,$wh rat: $hr\n"); 68 | //print(@"[image] factor: $factor\n"); 69 | } 70 | scale(new_factor); 71 | } 72 | 73 | public void scale(float factor){ 74 | float off = this.factor/factor; 75 | this.factor = factor; 76 | int ph = pixbuf.get_height(); 77 | int pw = pixbuf.get_width(); 78 | //int wh = get_allocated_height(); 79 | //int ww = get_allocated_width(); 80 | int w = (int) (pw*factor); 81 | int h = (int) (ph*factor); 82 | if (off > 1.0001 || off < 0.9999){ 83 | var scaled_pixbuf = pixbuf.scale_simple(w, h, Gdk.InterpType.BILINEAR); 84 | if (scaled_pixbuf != null){ 85 | image.set_from_pixbuf( (owned) scaled_pixbuf); 86 | } 87 | } 88 | image.set_padding(0,0); 89 | } 90 | 91 | public bool canHandleCurrentResource(){ 92 | if (request == null){ 93 | return false; 94 | }else{ 95 | return (request.status == "success") && request.resource.mimetype.has_prefix("image/"); 96 | } 97 | } 98 | 99 | public void cleanup(){ 100 | this.image.clear(); 101 | size_allocate.disconnect(trigger_autoscale); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/gtk_ui/views/message.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Message : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Label error_label = new Gtk.Label(""); 5 | private string status; 6 | 7 | public Message(string status, string label_text = "Something went wrong ...", string sublabel_text = "...", string icon_name = "dialog-error-symbolic") { 8 | this.status = status; 9 | this.append_big_icon(icon_name); 10 | this.append_big_headline(label_text); 11 | this.append_small_headline(sublabel_text); 12 | this.append_widget(error_label); 13 | show_all(); 14 | } 15 | 16 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 17 | if (!(request.status.has_prefix(status))) {return false;} 18 | this.request = request; 19 | error_label.label = request.status+"\n"+request.substatus; 20 | //nameLabel.label = request.name; 21 | return true; 22 | } 23 | 24 | public bool canHandleCurrentResource(){ 25 | if (request == null){ 26 | return false; 27 | }else{ 28 | return request.status.has_prefix(status); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gtk_ui/views/plaintext.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Plaintext : Gtk.ScrolledWindow, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.TextView textview; 5 | 6 | construct { 7 | textview = new Gtk.TextView(); 8 | textview.editable = false; 9 | textview.wrap_mode = Gtk.WrapMode.WORD; 10 | textview.set_monospace(true); 11 | textview.set_left_margin(4); 12 | add(textview); 13 | } 14 | 15 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 16 | if ((request.status == "success") && request.resource.mimetype.has_prefix("text/")){ 17 | string text = ""; 18 | var input_stream = tab.get_file_content_stream(); 19 | if (input_stream == null) { 20 | textview.buffer.text = "The cache file for this resource does not exist!\nReloading the page should help,\nif not please contact the developer!"; 21 | } else { 22 | try { 23 | // Open file for reading and wrap returned FileInputStream into a 24 | // DataInputStream, so we can read line by line 25 | var dis = new DataInputStream (input_stream); 26 | string line; 27 | // Read lines until end of file (null) is reached 28 | while ((line = dis.read_line (null)) != null) { 29 | text = text+line+"\n"; 30 | } 31 | } catch (GLib.Error e) { 32 | text = "ERROR WHILE READING FILE:\n"+e.message; 33 | } 34 | } 35 | textview.buffer.text = text; 36 | 37 | } else { 38 | return false; 39 | } 40 | this.request = request; 41 | show_all(); 42 | return true; 43 | } 44 | 45 | public bool canHandleCurrentResource(){ 46 | if (request == null){ 47 | return false; 48 | }else{ 49 | return (request.status == "success") && request.resource.mimetype.has_prefix("text/"); 50 | } 51 | } 52 | 53 | public bool import(string data){ 54 | var kv = new Fossil.Util.Kv(); 55 | kv.import(data); 56 | if (kv.get_value("view_type") != "fossil.plain_text.0"){ 57 | return false; 58 | } 59 | string? val = kv.get_value("scroll"); 60 | if (val != null){ 61 | Fossil.GtkUi.LegacyUtil.GtkScrollExport.import(this,val); 62 | } 63 | return true; 64 | } 65 | 66 | public string? export(){ 67 | var kv = new Fossil.Util.Kv(); 68 | kv.set_value("view_type","fossil.plain_text.0"); 69 | kv.set_value("scroll",Fossil.GtkUi.LegacyUtil.GtkScrollExport.export(this)); 70 | return kv.export(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/gtk_ui/views/redirect.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.Redirect : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Button redirbutton = new Gtk.Button(); 5 | private Gtk.Label buttonlabel = new Gtk.Label(""); 6 | private string title = "Redirect to"; 7 | 8 | public Redirect(Fossil.Registry.TranslationRegistry? translation = null) { 9 | if(translation != null){ 10 | this.title = translation.localize("view.fossil.redirect.title"); 11 | } 12 | redirbutton.get_style_context().add_class("suggested-action"); 13 | redirbutton.add(buttonlabel); 14 | buttonlabel.wrap_mode = Pango.WrapMode.WORD_CHAR; 15 | buttonlabel.wrap = true; 16 | this.append_big_icon("media-playlist-shuffle-symbolic"); 17 | this.append_big_headline(title); 18 | this.append_widget(redirbutton); 19 | show_all(); 20 | } 21 | 22 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 23 | if (!(request.status.has_prefix("redirect"))) {return false;} 24 | this.request = request; 25 | buttonlabel.label = request.substatus; 26 | redirbutton.clicked.connect(() => { 27 | tab.redirect(this.request.substatus); 28 | }); 29 | return true; 30 | } 31 | 32 | public bool canHandleCurrentResource(){ 33 | if (request == null){ 34 | return false; 35 | }else{ 36 | return request.status.has_prefix("redirect"); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/gtk_ui/views/unknown_uri_scheme.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.UnknownUriScheme : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | private Gtk.Button open_externally_button = new Gtk.Button.with_label("Open in external Browser"); 5 | private string title = "Unknown Uri Scheme"; 6 | 7 | public UnknownUriScheme(Fossil.Registry.TranslationRegistry? translation = null) { 8 | if(translation != null){ 9 | this.title = translation.localize("view.fossil.unknown_uri_scheme.title"); 10 | this.open_externally_button.label = translation.localize("action.open_uri_externally"); 11 | } 12 | open_externally_button.get_style_context().add_class("suggested-action"); 13 | 14 | this.append_big_icon("dialog-question-symbolic"); 15 | this.append_big_headline(title); 16 | this.append_widget(open_externally_button); 17 | 18 | show_all(); 19 | } 20 | 21 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 22 | if (request.status != "error/uri/unknownScheme") {return false;} 23 | this.request = request; 24 | open_externally_button.clicked.connect(() => { 25 | tab.open_uri_externally(this.request.uri); 26 | }); 27 | return true; 28 | } 29 | 30 | public bool canHandleCurrentResource(){ 31 | if (request == null){ 32 | return false; 33 | }else{ 34 | return request.status == "error/uri/unknownScheme"; 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/gtk_ui/views/uri_error_generic.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.UriError.Generic : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | private Fossil.Request request = null; 4 | 5 | public Generic(Fossil.Registry.TranslationRegistry? translation = null) { 6 | string title = "Invalid Uri"; 7 | string subtitle = "Something went wrong while parsin an URL/URI"; 8 | if (translation != null){ 9 | title = translation.localize("view.uri_error_generic.title"); 10 | subtitle = translation.localize("view.uri_error_generic.subtitle"); 11 | } 12 | this.append_big_headline(title); 13 | this.append_small_headline(subtitle); 14 | 15 | show_all(); 16 | } 17 | 18 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 19 | if (!(request.status.has_prefix("error/uri"))) {return false;} 20 | this.request = request; 21 | show_all(); 22 | return true; 23 | } 24 | 25 | public bool canHandleCurrentResource(){ 26 | if (request == null){ 27 | return false; 28 | }else{ 29 | return request.status.has_prefix("error/uri"); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/gtk_ui/views/uri_merge_test.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.GtkUi.View.UriMergeInternal : Fossil.GtkUi.LegacyWidget.DialogViewBase, Fossil.GtkUi.Interface.LegacyView { 2 | 3 | public UriMergeInternal(){ 4 | var outlabel = new Gtk.Label(""); 5 | var baseurientry = new Gtk.Entry(); 6 | baseurientry.placeholder_text = "Base Uri"; 7 | var relativeurientry = new Gtk.Entry(); 8 | relativeurientry.placeholder_text = "Relative Uri"; 9 | baseurientry.activate.connect(() => { 10 | outlabel.label = Fossil.Util.Uri.join(baseurientry.text,relativeurientry.text); 11 | }); 12 | relativeurientry.activate.connect(() => { 13 | outlabel.label = Fossil.Util.Uri.join(baseurientry.text,relativeurientry.text); 14 | }); 15 | this.append_big_headline("Uri Merger Internal"); 16 | this.append_widget(baseurientry); 17 | this.append_widget(relativeurientry); 18 | this.append_widget(outlabel); 19 | add_fossil("file:///","/","file:///"); 20 | add_fossil("file:","","file:"); 21 | show_all(); 22 | } 23 | 24 | private void add_fossil(string baseuri,string relativeuri,string result){ 25 | var joined = Fossil.Util.Uri.join(baseuri,relativeuri); 26 | var res = "[passed]"; 27 | if(joined != result){ 28 | res = "[failed] "+result; 29 | } 30 | this.append_widget(new Gtk.Label(@"$baseuri + $relativeuri = $joined $res")); 31 | } 32 | 33 | public bool display_resource(Fossil.Request request, Fossil.GtkUi.LegacyWidget.Tab tab, bool as_subview){ 34 | return true; 35 | } 36 | 37 | public bool canHandleCurrentResource(){ 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/interface/cache.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Cache : Fossil.Interface.ResourceStore { 2 | 3 | public abstract bool can_serve_request(string uri,int64 maxage = 0); 4 | 5 | public abstract void put_resource(Fossil.Resource resource); 6 | 7 | public abstract void invalidate_for_uri(string uri); 8 | 9 | public abstract void erase(); 10 | } 11 | -------------------------------------------------------------------------------- /src/interface/document/token_parser.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Document.TokenParser : Object { 2 | 3 | public abstract void set_input_stream(InputStream input_stream); 4 | 5 | //returns null when finished can possibly hang because of the input stream 6 | public abstract Fossil.Ui.Document.Token? next_token(); 7 | 8 | public abstract void reset(); 9 | } 10 | -------------------------------------------------------------------------------- /src/interface/document/token_parser_factory.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Document.TokenParserFactory : Object { 2 | 3 | public abstract Fossil.Interface.Document.TokenParser? get_token_parser(string content_type); 4 | public abstract bool has_parser_for(string content_type); 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/interface/document/token_renderer.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Document.TokenRenderer : Object { 2 | 3 | public abstract void append_token(Fossil.Ui.Document.Token token); 4 | 5 | public abstract void reset_renderer(); 6 | } 7 | -------------------------------------------------------------------------------- /src/interface/page/page.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Page : Object { 2 | 3 | // Returns a settings provider, that contains the persistant configuration for pages, overlayed with local changes 4 | // Writing will only make local changes that won't be saved 5 | public abstract Fossil.Interface.Settings.Provider get_page_settings_provider(); 6 | 7 | // Returns the persistant configuration for pages wich will be used for all pages in the same configuration space 8 | // Use this to implement settings pages otherwise don't use it 9 | public abstract Fossil.Interface.Settings.Provider? get_persistant_page_settings_provider(); 10 | 11 | //Core services 12 | public abstract Fossil.Interface.Page.Service.Metadata get_metadata_service(); 13 | public abstract Fossil.Interface.Page.Service.Syncronisation get_syncronisation_service(); 14 | public abstract Fossil.Interface.Page.Service.InternalNavigation get_internal_navigation_service(); 15 | public abstract Fossil.Interface.Page.Service.ExternalNavigation get_external_navigation_service(); 16 | 17 | //optinal services 18 | public abstract Fossil.Interface.Page.Service.LinearHistory? get_linear_history_service(); 19 | 20 | // Temporary legacy services to make it easier to migrate to the new ones 21 | // All of the blow functionality is DEPRECATED 22 | 23 | public virtual FileInputStream? get_content_stream(){ return null; } 24 | public virtual string get_content_mimetype(){ return ""; } 25 | public virtual string? get_legacy_status(){ return null; } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/interface/page/service/external_navigation.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Service.ExternalNavigation : Object { 2 | 3 | /* 4 | This interface is for navigation to other pages, the requests will be passed on to the tab 5 | or an other appropriate handler 6 | */ 7 | 8 | /* 9 | Well known target names include: 10 | this_tab 11 | new_tab 12 | new_window 13 | */ 14 | 15 | // The module_name is used for logging. 16 | 17 | public abstract void go_to_uri(string uri, string target, string module_name); 18 | 19 | public abstract string get_primary_target(); 20 | public abstract string? get_secodary_target(); 21 | 22 | //Lower numbers mean higher prioritys 23 | public abstract uint? get_target_priority(string target); 24 | //Will iterate over all possible targets, higher prioritys first 25 | public abstract void foreach_target(Func cb); 26 | // The name you would use in a configuration menu 27 | public abstract string? get_target_name(string target); 28 | // What should be displayed in a menu if there is no localized name avaiable 29 | public abstract string? get_default_target_menu_label(string target); 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/interface/page/service/internal_navigation.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Service.InternalNavigation : Object { 2 | 3 | /* 4 | This interface is for navigating withing a page, 5 | that is all navigation that can be done without creating a second page. 6 | */ 7 | 8 | // The module_name is used for logging. 9 | 10 | // Where we are now 11 | public signal void current_uri_changed(string uri); 12 | public abstract string get_current_uri(); 13 | 14 | // redirecting 15 | public abstract void redirect(string uri, string module_name); 16 | public abstract uint get_redirect_counter(); 17 | public abstract bool may_autoredirect_to(string uri); 18 | 19 | // reloading 20 | public abstract void reload(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/interface/page/service/linear_history.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Service.LinearHistory : Object { 2 | 3 | public abstract void go_back(); 4 | public abstract bool can_go_back(); 5 | public virtual string? get_next_past_uri(){ return null; } 6 | //will iterate over the past uris in reverse cronological order 7 | public virtual void foreach_past_uri(Func cb){ return; } 8 | 9 | public abstract void go_forward(); 10 | public abstract bool can_go_forward(); 11 | public virtual string? get_next_future_uri(){ return null; } 12 | // will iterate over the future uris in cronological order 13 | public virtual void foreach_future_uri(Func cb){ return; } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/interface/page/service/metadata.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Service.Metadata : Object { 2 | 3 | /* 4 | This interface is supposed to store page wide metadata 5 | that is usually inferred from downloaded resources. 6 | The metadata in this service will be used for eye candy, 7 | indexing sites and user information. 8 | The metadata may also come from information derived 9 | from a previously visited resource, i.e. the label of a link leading to an image. 10 | It should not be related to user input! 11 | */ 12 | 13 | /* 14 | Well known metadata keys are: 15 | title 16 | icon_uri 17 | search_uri_template 18 | */ 19 | 20 | // The module_name is used for logging. 21 | 22 | public signal void on_page_metadata_change(string key, string? val); 23 | 24 | public abstract void set_page_metadata(string key, string? val, string module_name); 25 | public abstract string? get_page_metadata(string key, string module_name); 26 | public abstract void foreach_page_metadata_key(Func cb, string module_name); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/interface/page/service/syncronisation.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Page.Service.Syncronisation : Object { 2 | 3 | /* 4 | This service stores data from widgets, so that it can be used by other widgets or to persist state. 5 | There are public and private keys, private keys start with a . 6 | This can for example be used to persist scroll position on view change 7 | or letting other widgets handle some functionality. 8 | */ 9 | 10 | /* 11 | Well known public keys are 12 | 13 | page_search.query 14 | page_search.num_results 15 | page_search.at_result 16 | */ 17 | 18 | // The module_name is used for logging. 19 | 20 | //if the key is null it means that all syncronised information should be reloaded 21 | public signal void on_syncronisation_update(string? key, string? val); 22 | 23 | public abstract void syncronisation_write(string key, string? val, string module_name); 24 | public abstract string? syncronisation_read(string key, string module_name); 25 | public abstract void foreach_syncronisation_key(Func cb, string module_name); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/interface/resource_store.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.ResourceStore : Object { 2 | 3 | // A resource store is basically a protocol adapter, that takes request 4 | // objects and fullfills the requests 5 | // To download a resource add a request with an uri. 6 | // If the reload flag is set to true, this means, that the resource should 7 | // not be fetched from cache. 8 | // the filepath argument should point to a non existant file, that the store 9 | // may use to store the blob part of the response, however if the file 10 | // already exists elsewhere in the filesystem it should use that path and 11 | // set the is_temporary flag on the resource to false 12 | 13 | // To upload a resource, do as if you were downloading but set the 14 | // upload_resource in the request and and the request argument to true 15 | // the store will then attempt to upload the resource, and if successful 16 | // download the servers response 17 | 18 | public abstract void request(Fossil.Request request, string? filepath = null, bool upload = false); 19 | } 20 | -------------------------------------------------------------------------------- /src/interface/session.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Session : Object { 2 | 3 | public virtual Fossil.Interface.Cache? get_cache() { return null; } 4 | public abstract bool set_default_backend(Fossil.Interface.ResourceStore store); //returns true on success 5 | public abstract Fossil.Interface.ResourceStore? get_default_backend(); 6 | 7 | public abstract Fossil.Request make_download_request(string uri, bool reload=false); 8 | public abstract Fossil.Request make_upload_request(string uri, Fossil.Resource resource, out string upload_urn = null); 9 | 10 | public virtual void erase_cache() {} 11 | 12 | public abstract void set_name(string name); 13 | public abstract string get_name(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/interface/settings/provider.i.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Interface.Settings.Provider : Object { 2 | 3 | public signal void settings_updated(string path_prefix); 4 | 5 | public abstract void request_index(string path_prefix, Func cb); 6 | 7 | public abstract bool has_object(string path); 8 | public abstract string? read_object(string path); 9 | 10 | public abstract bool can_write_object(string path); 11 | // writing null content will be the equivalent of a delete 12 | public abstract bool write_object(string path, string? content); 13 | 14 | //those who use this settings provider can use this one to publish their 15 | //reports when something settings related happend 16 | public signal void submit_client_report(Fossil.Settings.Report report); 17 | 18 | // this signal is where reports from settings porivders are supposed to be propagated upwards 19 | public signal void provider_report(Fossil.Settings.Report report); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main.vala: -------------------------------------------------------------------------------- 1 | 2 | 3 | public static int main(string[] args) { 4 | var fossil = new Fossil.GtkUi.Application(); 5 | return fossil.run(args); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/page/page.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Page.Page : Object, Fossil.Interface.Page.Page { 2 | 3 | protected Fossil.Interface.Settings.Provider? persistant_settings = null; 4 | protected Fossil.Settings.RamProvider local_settings = new Fossil.Settings.RamProvider(); 5 | protected Fossil.Settings.Context.Fallback settings_context = new Fossil.Settings.Context.Fallback(); 6 | protected Fossil.Interface.Page.Service.Metadata metadata; 7 | protected Fossil.Interface.Page.Service.Syncronisation syncronisation = new Fossil.Page.Service.Syncronisation(); 8 | protected Fossil.Interface.Page.Service.InternalNavigation internal_navigation; 9 | protected Fossil.Interface.Page.Service.ExternalNavigation external_navigation; 10 | protected Fossil.Interface.Page.Service.LinearHistory? linear_history = null; 11 | 12 | public Fossil.GtkUi.LegacyWidget.Tab? legacy_tab = null; 13 | public Fossil.Request? legacy_request = null; 14 | 15 | public Page(Fossil.Interface.Page.Service.InternalNavigation internal_navigation, Fossil.Interface.Page.Service.ExternalNavigation external_navigation, Fossil.Interface.Settings.Provider? persistant_settings = null, Fossil.Interface.Page.Service.LinearHistory? linear_history = null, Fossil.Interface.Page.Service.Metadata? metadata = null){ 16 | this.internal_navigation = internal_navigation; 17 | this.external_navigation = external_navigation; 18 | this.persistant_settings = persistant_settings; 19 | this.linear_history = linear_history; 20 | if (metadata != null){ 21 | this.metadata = metadata; 22 | } else { 23 | this.metadata = new Fossil.Page.Service.Metadata(); 24 | } 25 | this.settings_context.add_fallback(local_settings); 26 | if (persistant_settings != null) { 27 | this.settings_context.add_fallback(persistant_settings); 28 | } 29 | } 30 | 31 | ///////////////////////////////////// 32 | // Fossil.Interface.Page.Page // 33 | ///////////////////////////////////// 34 | 35 | public virtual Fossil.Interface.Settings.Provider get_page_settings_provider(){ 36 | return settings_context; 37 | } 38 | 39 | public virtual Fossil.Interface.Settings.Provider? get_persistant_page_settings_provider(){ 40 | return persistant_settings; 41 | } 42 | 43 | //Core services 44 | public virtual Fossil.Interface.Page.Service.Metadata get_metadata_service(){ 45 | return metadata; 46 | } 47 | 48 | public virtual Fossil.Interface.Page.Service.Syncronisation get_syncronisation_service(){ 49 | return syncronisation; 50 | } 51 | 52 | public virtual Fossil.Interface.Page.Service.InternalNavigation get_internal_navigation_service(){ 53 | return internal_navigation; 54 | } 55 | 56 | public virtual Fossil.Interface.Page.Service.ExternalNavigation get_external_navigation_service(){ 57 | return external_navigation; 58 | } 59 | 60 | //optinal services 61 | public virtual Fossil.Interface.Page.Service.LinearHistory? get_linear_history_service(){ 62 | return linear_history; 63 | } 64 | 65 | // Temporary legacy services to make it easier to migrate to the new ones 66 | // All of the blow functionality is DEPRECATED 67 | 68 | public virtual FileInputStream? get_content_stream(){ 69 | if (legacy_tab == null) { 70 | return null; 71 | } else { 72 | return legacy_tab.get_file_content_stream(); 73 | } 74 | } 75 | 76 | public virtual string get_content_mimetype(){ 77 | if (legacy_request != null){ 78 | if (legacy_request.resource != null){ 79 | return legacy_request.resource.mimetype; 80 | } 81 | } 82 | return ""; 83 | } 84 | 85 | public virtual string? get_legacy_status(){ 86 | if (legacy_request != null){ 87 | return legacy_request.status; 88 | } 89 | return null; 90 | } 91 | 92 | public virtual Fossil.GtkUi.LegacyWidget.Tab? get_legacy_tab(){ return legacy_tab; } 93 | public virtual Fossil.Request? get_legacy_request(){ return legacy_request; } 94 | } 95 | -------------------------------------------------------------------------------- /src/page/service/metadata.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Page.Service.Metadata : Object, Fossil.Interface.Page.Service.Metadata { 2 | 3 | private HashTable metadata = new HashTable(str_hash, str_equal); 4 | 5 | ///////////////////////////////////////////////// 6 | // Fossil.Interface.Page.Service.Metadata // 7 | ///////////////////////////////////////////////// 8 | 9 | public void set_page_metadata(string key, string? val, string module_name){ 10 | if (val == null){ 11 | metadata.remove(key); 12 | } else { 13 | metadata.set(key, val); 14 | } 15 | on_page_metadata_change(key, val); 16 | } 17 | 18 | public string? get_page_metadata(string key, string module_name){ 19 | return metadata.get(key); 20 | } 21 | 22 | public void foreach_page_metadata_key(Func cb, string module_name){ 23 | metadata.foreach((k,_) => { 24 | cb(k); 25 | }); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/page/service/syncronisation.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Page.Service.Syncronisation : Object, Fossil.Interface.Page.Service.Syncronisation { 2 | 3 | private HashTable objects = new HashTable(str_hash, str_equal); 4 | 5 | /////////////////////////////////////////////////////// 6 | // Fossil.Interface.Page.Service.Syncronisation // 7 | /////////////////////////////////////////////////////// 8 | 9 | public void syncronisation_write(string key, string? val, string module_name){ 10 | if (val == null){ 11 | objects.remove(key); 12 | } else { 13 | objects.set(key, val); 14 | } 15 | on_syncronisation_update(key, val); 16 | } 17 | 18 | public string? syncronisation_read(string key, string module_name){ 19 | return objects.get(key); 20 | } 21 | 22 | public void foreach_syncronisation_key(Func cb, string module_name){ 23 | objects.foreach((k,_) => { 24 | cb(k); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/registries/bookmark_registry.vala: -------------------------------------------------------------------------------- 1 | //return true to keep going 2 | public delegate bool Fossil.Registry.BookmarkRegistryIterator(BookmarkRegistryEntry entry); 3 | 4 | public class Fossil.Registry.BookmarkRegistry : Object { 5 | protected List entries = new List(); 6 | 7 | public signal void bookmark_added(BookmarkRegistryEntry bookmark); 8 | public signal void bookmark_modified(BookmarkRegistryEntry bookmark); 9 | public signal void bookmark_removed(BookmarkRegistryEntry bookmark); 10 | 11 | public BookmarkRegistryEntry? add_bookmark(string name, string uri){ 12 | string uid = GLib.Uuid.string_random(); 13 | while (get_bookmark_by_uid(uid) != null){ 14 | uid = GLib.Uuid.string_random(); 15 | } 16 | var entry = new BookmarkRegistryEntry(uid, name, uri); 17 | entries.append(entry); 18 | bookmark_added(entry); 19 | return entry; 20 | } 21 | 22 | public void remove_bookmark(BookmarkRegistryEntry bookmark){ 23 | entries.remove_all(bookmark); 24 | bookmark_removed(bookmark); 25 | } 26 | 27 | public void iterate_over_all_bookmarks(Fossil.Registry.BookmarkRegistryIterator callback){ 28 | foreach (var entry in entries){ 29 | if (!callback(entry)){ 30 | break; 31 | } 32 | } 33 | } 34 | 35 | public BookmarkRegistryEntry? get_bookmark_by_uid(string uid){ 36 | foreach (var entry in entries){ 37 | if (entry.uid == uid){ 38 | return entry; 39 | } 40 | } 41 | return null; 42 | } 43 | 44 | public BookmarkRegistryEntry? get_bookmark_with_name(string name, uint skip = 0){ 45 | foreach (var entry in entries){ 46 | if (entry.name == name){ 47 | if (skip > 0){ 48 | skip--; 49 | return entry; 50 | } 51 | } 52 | } 53 | return null; 54 | } 55 | 56 | public BookmarkRegistryEntry? get_bookmark_with_uri(string uri, uint skip = 0){ 57 | foreach (var entry in entries){ 58 | if (entry.uri == uri){ 59 | if (skip > 0){ 60 | skip--; 61 | return entry; 62 | } 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | } 69 | 70 | public class Fossil.Registry.BookmarkRegistryEntry : Object { 71 | public string name; 72 | public string uri; 73 | public string uid; 74 | 75 | public BookmarkRegistryEntry(string uid, string name, string uri){ 76 | this.uid = uid; 77 | this.name = name; 78 | this.uri = uri; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/registries/session_registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Registry.SessionRegistry : Object { 2 | public HashTable sessions = new HashTable(str_hash, str_equal); 3 | 4 | public void register_session(string session_id, Fossil.Interface.Session session){ 5 | sessions.set(session_id,session); 6 | } 7 | 8 | public Fossil.Interface.Session? get_session_by_id(string session_id){ 9 | return sessions.get(session_id); 10 | } 11 | 12 | public void erase_all_caches(){ 13 | foreach (Fossil.Interface.Session session in sessions.get_values()){ 14 | session.erase_cache(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/registries/store_registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Registry.StoreRegistry : Object { 2 | private List stores = new List(); 3 | 4 | public StoreRegistry.default_configuration(){ 5 | this.add_resource_store("fossil://",new Fossil.Store.Internal()); 6 | } 7 | 8 | public void add_resource_store(string prefix,Fossil.Interface.ResourceStore store){ 9 | stores.append(new Fossil.Registry.StoreRegistryEntry(prefix,store)); 10 | } 11 | 12 | public Fossil.Interface.ResourceStore? get_closest_match(string uri){ 13 | Fossil.Interface.ResourceStore best_match = null; 14 | uint closest_match_length = 0; 15 | foreach(Fossil.Registry.StoreRegistryEntry entry in stores){ 16 | if (uri.has_prefix(entry.prefix) && entry.prefix.length > closest_match_length){ 17 | best_match = entry.store; 18 | closest_match_length = entry.prefix.length; 19 | } 20 | } 21 | return best_match; 22 | } 23 | 24 | } 25 | 26 | private class Fossil.Registry.StoreRegistryEntry { 27 | public string prefix; 28 | public Fossil.Interface.ResourceStore store; 29 | 30 | public StoreRegistryEntry(string prefix,Fossil.Interface.ResourceStore store){ 31 | this.prefix = prefix; 32 | this.store = store; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/registries/super_registry.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.SuperRegistry : Object, Fossil.Asm.ObjectProvider { 2 | 3 | private HashTable objects = new HashTable(str_hash, str_equal); 4 | 5 | public void store(string key,Object val){ 6 | objects.set(key,val); 7 | } 8 | 9 | public Object retrieve(string key){ 10 | return objects.get(key); 11 | } 12 | 13 | public void @foreach(HFunc cb){ 14 | objects.@foreach(cb); 15 | } 16 | 17 | public void foreach_asm_object(HFunc cb){ 18 | objects.@foreach((k,v) => { 19 | if (v is Fossil.Asm.AsmObject){ 20 | cb(k,(Fossil.Asm.AsmObject) v); 21 | } 22 | }); 23 | } // iterates over all object names in this object store 24 | 25 | public Fossil.Asm.AsmObject? get_asm_object(string name){ 26 | return (Fossil.Asm.AsmObject) objects.get(name); 27 | } 28 | } 29 | 30 | /* 31 | errordomain Fossil.SuperRegistryError { 32 | MISSING_ENTRY 33 | }*/ 34 | -------------------------------------------------------------------------------- /src/registries/translation.vala: -------------------------------------------------------------------------------- 1 | public interface Fossil.Registry.TranslationRegistry : Object { 2 | 3 | //returns "?_"+text_id if no translation is known 4 | public abstract string get_localized_string(string text_id); 5 | 6 | public string localize(string text_id){ 7 | return this.get_localized_string(text_id); 8 | } 9 | 10 | public virtual string get_language_name(){ return ""; } 11 | 12 | } 13 | 14 | public class Fossil.Registry.TranslationMultiplexerRegistry : Object, Fossil.Registry.TranslationRegistry { 15 | 16 | private HashTable languages = new HashTable(str_hash, str_equal); 17 | 18 | public List active_languages = new List(); //used for setting the active languages 19 | 20 | public void add_language(string id,TranslationRegistry registry){ 21 | languages.set(id,registry); 22 | } 23 | 24 | public string get_localized_string(string text_id){ 25 | string unknown_translation = "?_"+text_id; 26 | foreach(string language_name in active_languages){ 27 | var language = languages.get(language_name); 28 | if (language != null){ 29 | string translation = language.get_localized_string(text_id); 30 | if (translation != unknown_translation){ 31 | return translation; 32 | } 33 | } 34 | } 35 | return unknown_translation; 36 | } 37 | 38 | } 39 | 40 | public class Fossil.Registry.TranslationLanguageRegistry : Object, Fossil.Registry.TranslationRegistry { 41 | 42 | private HashTable texts = new HashTable(str_hash, str_equal); 43 | 44 | public void set_text(string id,string text){ 45 | texts.set(id,text); 46 | } 47 | 48 | public string get_localized_string(string text_id){ 49 | string? translation = texts.get(text_id); 50 | if (translation != null){ 51 | return translation; 52 | } else { 53 | return "?_"+text_id; 54 | } 55 | } 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/registries/uri_autocorrect.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Registry.UriAutoprefix : Object { 2 | 3 | public List entries = new List(); 4 | 5 | public void add(string prefix, string replacement){ 6 | entries.append(new Fossil.Util.UriAutoprefixEntry(prefix, replacement)); 7 | } 8 | 9 | public string try_autoprefix(string uri){ 10 | //print(@"[try_autoprefix] $uri\n"); 11 | string best_match = uri; 12 | uint closest_match_length = 0; 13 | foreach(Fossil.Util.UriAutoprefixEntry entry in entries){ 14 | //print(@"[try_autoprefix] '$uri'.has_prefix('$(entry.prefix)') = ?\n"); 15 | if (uri.has_prefix(entry.prefix) && entry.prefix.length > closest_match_length){ 16 | //print(@"[try_autoprefix] match: $(entry.prefix)\n"); 17 | best_match = entry.replacement+uri.substring(entry.prefix.length); 18 | closest_match_length = entry.prefix.length; 19 | } 20 | } 21 | //print(@"[try_autoprefix] best match: $best_match\n"); 22 | return best_match; 23 | } 24 | 25 | } 26 | 27 | public class Fossil.Util.UriAutoprefixEntry { 28 | public string prefix; 29 | public string replacement; 30 | 31 | public UriAutoprefixEntry(string prefix,string replacement){ 32 | this.prefix = prefix; 33 | this.replacement = replacement; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/resource.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Resource : Object { 2 | //requester 3 | public string uri { get; protected set; default = "";} //where did you find that one? 4 | public string filepath { get; protected set; default = "";} //the file, that contains the information 5 | //store 6 | public string? mimetype { get; protected set; default = null;} //what does it contain? 7 | public int64 timestamp { get; set; default = 0;} //The unix time in milliseconds when the resource was fully loaded 8 | public string? origin { get; protected set; default = null;} //who made it? 9 | public string? metadata { get; protected set; default = null;} //cookies, certificates, identifiers 10 | public string name { get; protected set; default = "";} 11 | //cache 12 | public int64 valid_until { get; set; default = 0;} //The unix time in milliseconds when the resource is invlid in cache, do not cache if 0, valid for forever if int64.MAX 13 | public bool is_temporary { get; protected set; default = false;} //only for cache, if the file may be deleted 14 | public int users { get; protected set; default=0; } //how many systems use this resource 15 | public bool is_locked { get; protected set; default = false; } //if the resource is not locked, its contents can be altered 16 | private HashTable user_ids = new HashTable(str_hash, str_equal); 17 | 18 | public Resource(string? uri,string filepath,bool is_temporary,bool is_locked = true){ 19 | string uri_; 20 | if (uri == null){ 21 | uri_ = "file://"+Uri.escape_string(filepath); 22 | } else { 23 | uri_ = uri; 24 | } 25 | Object( 26 | uri:uri_, 27 | filepath:filepath, 28 | is_temporary:is_temporary, 29 | is_locked:is_locked 30 | ); 31 | } 32 | 33 | ~Resource(){ 34 | this.delete_file(); 35 | } 36 | 37 | public void lock_resource(){ 38 | is_locked = true; 39 | } 40 | 41 | public bool derive_uri_from_filepath(){ 42 | if (!is_locked){ 43 | this.uri = "file://"+Uri.escape_string(filepath); 44 | } 45 | return is_locked; 46 | } 47 | 48 | public bool update_filepath(string filepath, bool is_temporary=false){ 49 | if (!is_locked){ 50 | this.filepath = filepath; 51 | this.is_temporary = is_temporary; 52 | } 53 | return is_locked; 54 | } 55 | 56 | public void increment_users(string user){ 57 | print(@"[res] Users increment: URI:$(this.uri) FILEPATH:$(this.filepath) {$user}\n"); 58 | if (!user_ids.contains(user)){ 59 | this.user_ids.set(user,true); 60 | this.users++; 61 | } 62 | } 63 | 64 | public void decrement_users(string user){ 65 | print(@"[res] Users decrement: URI:$(this.uri) FILEPATH:$(this.filepath) {$user}\n"); 66 | if (user_ids.contains(user)){ 67 | this.user_ids.remove(user); 68 | this.users--; 69 | } 70 | if (this.users <= 0){ 71 | this.delete_file(); 72 | } 73 | } 74 | 75 | private void delete_file(){ 76 | if (this.is_temporary){ 77 | var file = File.new_for_path(this.filepath); 78 | if (file.query_exists()){ 79 | try{ 80 | file.delete(); 81 | }catch( Error e ){ 82 | print(@"[res][error] Failed to delete file for resource $(this.uri) | $(e.message)\n"); 83 | } 84 | print(@"[res] Resource free: URI:$(this.uri) FILEPATH:$(this.filepath)\n"); 85 | } 86 | } 87 | } 88 | 89 | public void add_metadata(string mimetype, string name, string metadata=""){ 90 | if (this.timestamp != 0){return;} 91 | this.mimetype = mimetype; 92 | this.name = name; 93 | this.metadata = metadata; 94 | this.timestamp = (GLib.get_real_time()/1000); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/sessions/default.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Session.Default : Fossil.Interface.Session, Object { 2 | private Fossil.Interface.ResourceStore backend; 3 | private Fossil.Store.Cache cache = new Fossil.Store.Cache(); 4 | private string _name = "Default"; 5 | 6 | public Default(Fossil.Interface.ResourceStore backend){ 7 | this.backend = backend; 8 | } 9 | 10 | public Fossil.Request make_download_request(string uri, bool reload=false){ 11 | if (uri == "about:cache"){ 12 | var request = new Fossil.Request(uri,reload); 13 | request.setStatus("interactive/cache"); 14 | request.finish(); 15 | return request; 16 | } 17 | print(@"[session.default] making request to $uri\n"); 18 | Fossil.Request? request = null; 19 | request = new Fossil.Request(uri,reload); 20 | if (!reload){ 21 | print("[session.default] checking cache\n"); 22 | if (cache.can_serve_request(request.uri)){ 23 | print(@"[session.default] Serving from cache!\n"); 24 | cache.request(request); 25 | return request; 26 | } 27 | } 28 | print("[session.default] making request to outside world\n"); 29 | request.finished.connect(reqest_finished_cachehook); 30 | backend.request(request); 31 | return request; 32 | } 33 | 34 | public Fossil.Request make_upload_request(string uri, Fossil.Resource resource, out string upload_urn = null){ 35 | upload_urn = "urn:upload:"+GLib.Uuid.string_random(); 36 | var request = new Fossil.Request(uri).upload(resource,upload_urn); 37 | request.finished.connect(reqest_finished_cachehook); 38 | backend.request(request,null,true); 39 | return request; 40 | } 41 | 42 | private void reqest_finished_cachehook(Fossil.Request outrequest){ 43 | if (outrequest.resource != null){ 44 | if (outrequest.resource.valid_until != 0){ 45 | cache.put_resource(outrequest.resource); 46 | } 47 | outrequest.finished.disconnect(reqest_finished_cachehook); 48 | } 49 | } 50 | 51 | public bool set_default_backend(Fossil.Interface.ResourceStore store){ 52 | backend = store; 53 | return true; 54 | } 55 | 56 | public Fossil.Interface.ResourceStore? get_default_backend(){ 57 | return backend; 58 | } 59 | 60 | public Fossil.Interface.Cache? get_cache() { 61 | return cache; 62 | } 63 | 64 | public void erase_cache() { 65 | cache.erase(); 66 | } 67 | 68 | public void set_name(string name){ _name = name; } 69 | public string get_name(){ return _name; } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/sessions/dummy.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Session.Dummy : Fossil.Interface.Session, Object { 2 | private string _name = "Dummy"; 3 | 4 | public Fossil.Request make_download_request(string uri, bool reload=false){ 5 | var request = new Fossil.Request(uri,reload); 6 | request.setStatus("error/dummySession"); 7 | return request; 8 | } 9 | 10 | public Fossil.Request make_upload_request(string uri, Fossil.Resource resource, out string upload_urn = null){ 11 | upload_urn = "urn:upload:"+GLib.Uuid.string_random(); 12 | var request = new Fossil.Request(uri).upload(resource,upload_urn);; 13 | request.setStatus("error/dummySession"); 14 | return request; 15 | } 16 | 17 | public bool set_default_backend(Fossil.Interface.ResourceStore store){ 18 | return false; 19 | } 20 | 21 | public Fossil.Interface.ResourceStore? get_default_backend(){ 22 | return null; 23 | } 24 | 25 | public Fossil.Interface.Cache? get_cache() { 26 | return null; 27 | } 28 | 29 | public void erase_cache() {} 30 | 31 | public void set_name(string name){ _name = name; } 32 | public string get_name(){ return _name; } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/sessions/tls.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Session.Tls : Fossil.Interface.Session, Object { 2 | private Fossil.Interface.ResourceStore backend; 3 | private Fossil.Store.Cache cache = new Fossil.Store.Cache(); 4 | private string _name = "Tls (experimental)"; 5 | 6 | //key and certificate pems appended to reach other 7 | public string? tls_certificate_pems = null; 8 | public bool use_cache = true; 9 | 10 | public Tls(Fossil.Interface.ResourceStore backend){ 11 | this.backend = backend; 12 | } 13 | 14 | public Fossil.Request make_download_request(string uri, bool reload=false){ 15 | var request = new Fossil.Request(uri,reload); 16 | if (uri == "about:cache"){ 17 | request.setStatus("interactive/cache"); 18 | request.finish(); 19 | return request; 20 | } 21 | if (uri == "session://"){ 22 | request.setStatus("interactive/tls_session"); 23 | request.finish(); 24 | return request; 25 | } 26 | /* 27 | if (uri.has_prefix("session://upload_certificate_pem?")){ 28 | var parsed_uri = new Fossil.Util.ParsedUri(uri); 29 | this.tls_certificate_pems = Uri.unescape_string(parsed_uri.query); 30 | request.setStatus("success/uploaded"); 31 | return request; 32 | } 33 | if (uri == "session://generate_certificate"){ 34 | var pems = Fossil.Util.TlsCertficateGenerator.generate_signed_certificate_key_pair_pem(); 35 | if (pems != null){ 36 | this.tls_certificate_pems = pems; 37 | request.setStatus("error/internal","Certificate successfully generated (not an error)"); 38 | } else { 39 | request.setStatus("error/internal","Something went wrong while generating the tls certificate"); 40 | } 41 | return request; 42 | } 43 | */ 44 | print(@"[session.tls] making request to $uri\n"); 45 | if (this.tls_certificate_pems != null){ 46 | request.arguments.set("tls.client.certificate",this.tls_certificate_pems); 47 | } 48 | if (!reload && use_cache){ 49 | print("[session.tls] checking cache\n"); 50 | if (cache.can_serve_request(request.uri)){ 51 | print(@"[session.tls] Serving from cache!\n"); 52 | cache.request(request); 53 | return request; 54 | } 55 | } 56 | print("[session.tls] making request to outside world\n"); 57 | backend.request(request); 58 | if (use_cache){ 59 | request.finished.connect(reqest_finished_cachehook); 60 | } 61 | return request; 62 | } 63 | 64 | public Fossil.Request make_upload_request(string uri, Fossil.Resource resource, out string upload_urn = null){ 65 | upload_urn = "urn:upload:"+GLib.Uuid.string_random(); 66 | var request = new Fossil.Request(uri).upload(resource,upload_urn); 67 | if (this.tls_certificate_pems != null){ 68 | request.arguments.set("tls.client.certificate",this.tls_certificate_pems); 69 | } 70 | request.finished.connect(reqest_finished_cachehook); 71 | backend.request(request,null,true); 72 | return request; 73 | } 74 | 75 | //used when requestcache is diabled 76 | private void reqest_finished_cachehook(Fossil.Request outrequest){ 77 | if (outrequest.resource != null){ 78 | if (outrequest.resource.valid_until != 0){ 79 | cache.put_resource(outrequest.resource); 80 | } 81 | outrequest.finished.disconnect(reqest_finished_cachehook); 82 | } 83 | } 84 | 85 | public bool set_default_backend(Fossil.Interface.ResourceStore store){ 86 | backend = store; 87 | return true; 88 | } 89 | 90 | public Fossil.Interface.ResourceStore? get_default_backend(){ 91 | return backend; 92 | } 93 | 94 | public Fossil.Interface.Cache? get_cache() { 95 | return cache; 96 | } 97 | 98 | public void erase_cache() { 99 | cache.erase(); 100 | } 101 | 102 | public void set_name(string name){ _name = name; } 103 | public string get_name(){ return _name; } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/sessions/uncached.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Session.Uncached : Fossil.Interface.Session, Object { 2 | private Fossil.Interface.ResourceStore backend; 3 | private string _name = "Uncached"; 4 | 5 | public Uncached(Fossil.Interface.ResourceStore backend){ 6 | this.backend = backend; 7 | } 8 | 9 | public Fossil.Request make_download_request(string uri, bool reload=false){ 10 | print(@"[session.uncached] making request to $uri\n"); 11 | var request = new Fossil.Request(uri,reload); 12 | backend.request(request); 13 | return request; 14 | } 15 | 16 | public Fossil.Request make_upload_request(string uri, Fossil.Resource resource, out string upload_urn = null){ 17 | upload_urn = "urn:upload:"+GLib.Uuid.string_random(); 18 | var request = new Fossil.Request(uri).upload(resource,upload_urn); 19 | backend.request(request,null,true); 20 | return request; 21 | } 22 | 23 | public bool set_default_backend(Fossil.Interface.ResourceStore store){ 24 | backend = store; 25 | return true; 26 | } 27 | 28 | public Fossil.Interface.ResourceStore? get_default_backend(){ 29 | return backend; 30 | } 31 | 32 | public void set_name(string name){ _name = name; } 33 | public string get_name(){ return _name; } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/settings/bridge/bookmark_settings_bridge.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Settings.Bridge.Bookmarks : Object { 2 | 3 | public string path; 4 | private Fossil.Registry.BookmarkRegistry bookmark_registry; 5 | private Fossil.Interface.Settings.Provider settings_provider; 6 | 7 | private bool dirty = false; 8 | //uri<\t>name 9 | 10 | public Bookmarks(Fossil.Interface.Settings.Provider settings_provider, string path, Fossil.Registry.BookmarkRegistry bookmark_registry){ 11 | this.settings_provider = settings_provider; 12 | this.path = path; 13 | this.bookmark_registry = bookmark_registry; 14 | import(); 15 | bookmark_registry.bookmark_added.connect(make_dirty); 16 | bookmark_registry.bookmark_modified.connect(make_dirty); 17 | bookmark_registry.bookmark_removed.connect(make_dirty); 18 | } 19 | 20 | ~Bookmarks(){ 21 | unhook(); 22 | } 23 | 24 | public bool import(){ 25 | string input = settings_provider.read_object(path); 26 | if (input == null){ return false; } 27 | string[] lines = input.split("\n"); 28 | foreach (string line in lines){ 29 | string[] tokens = line.strip().split("\t",2); 30 | if (tokens.length == 2){ 31 | bookmark_registry.add_bookmark(tokens[1].strip() ,tokens[0].strip()); 32 | } 33 | } 34 | dirty = false; 35 | return true; 36 | } 37 | 38 | //very naive, to be improved 39 | public bool export(){ 40 | dirty = false; 41 | string output = ""; 42 | bookmark_registry.iterate_over_all_bookmarks((entry) => { 43 | output = output+@"$(entry.uri)\t$(entry.name)\n"; 44 | return true; 45 | }); 46 | settings_provider.write_object(this.path, output); 47 | return true; 48 | } 49 | 50 | public bool is_dirty(){ 51 | return dirty; 52 | } 53 | 54 | public void make_dirty(){ 55 | lock (dirty) { 56 | if (!dirty) { 57 | dirty = true; 58 | Timeout.add(5000,() => { 59 | export(); 60 | return false; 61 | }); 62 | } 63 | } 64 | } 65 | 66 | public void unhook(){ 67 | bookmark_registry.bookmark_added.disconnect(make_dirty); 68 | bookmark_registry.bookmark_modified.disconnect(make_dirty); 69 | bookmark_registry.bookmark_removed.disconnect(make_dirty); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/settings/bridge/kv_settings_bridge.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Settings.Bridge.KV : Object { 2 | 3 | public string path; 4 | public HashTable values = new HashTable(str_hash,str_equal); 5 | 6 | public signal void updated_key(string key); 7 | 8 | private bool dirty = false; 9 | private Fossil.Interface.Settings.Provider settings_provider; 10 | 11 | public KV(Fossil.Interface.Settings.Provider settings_provider, string path){ 12 | this.settings_provider = settings_provider; 13 | this.path = path; 14 | import(); 15 | } 16 | 17 | public bool import(){ 18 | string? input = settings_provider.read_object(path); 19 | if (input == null){ return false; } 20 | string[] lines = input.split("\n"); 21 | foreach (string line in lines){ 22 | string[] tokens = line.strip().split(":",2); 23 | if (tokens.length == 2){ 24 | values.set(tokens[0].strip(),tokens[1].strip()); 25 | } 26 | } 27 | dirty = false; 28 | return true; 29 | } 30 | 31 | //very naive, to be improved 32 | public bool export(){ 33 | dirty = false; 34 | string output = ""; 35 | foreach (string key in values.get_keys()){ 36 | string? val = values.get(key); 37 | if (val != null){ 38 | output = output+@"$key: $val\n"; 39 | } 40 | } 41 | dirty = false; 42 | settings_provider.write_object(this.path, output); 43 | return true; 44 | } 45 | 46 | public bool is_dirty(){ 47 | return dirty; 48 | } 49 | 50 | public bool set_if_null(string key, string val){ 51 | if (values.get(key) == null){ 52 | values.set(key,val); 53 | updated_key(key); 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | public void set_value(string key, string val){ 60 | values.set(key,val); 61 | updated_key(key); 62 | make_dirty(); 63 | } 64 | 65 | private void make_dirty(){ 66 | lock (dirty) { 67 | if (!dirty) { 68 | dirty = true; 69 | Timeout.add(5000,() => { 70 | export(); 71 | return false; 72 | }); 73 | } 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/settings/context/fallback.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Settings.Context.Fallback : Fossil.Interface.Settings.Provider, Object { 2 | 3 | private List fallbacks = new List(); 4 | public bool deepwrite = false; 5 | 6 | public Fallback() { 7 | this.submit_client_report.connect(on_submit_client_report); 8 | } 9 | 10 | //first added fallbacks are first used 11 | public void add_fallback(Fossil.Interface.Settings.Provider provider){ 12 | lock(fallbacks){ 13 | if (fallbacks.index(provider) < 0) { 14 | fallbacks.append(provider); 15 | provider.settings_updated.connect(on_settings_updated); 16 | provider.provider_report.connect(on_provider_report); 17 | this.settings_updated(""); 18 | } 19 | } 20 | } 21 | 22 | public void remove_fallback(Fossil.Interface.Settings.Provider provider){ 23 | lock(fallbacks){ 24 | if (fallbacks.index(provider) >= 0) { 25 | provider.settings_updated.disconnect(on_settings_updated); 26 | provider.provider_report.disconnect(on_provider_report); 27 | fallbacks.remove(provider); 28 | this.settings_updated(""); 29 | } 30 | } 31 | } 32 | 33 | private void on_settings_updated(string path){ 34 | this.settings_updated(path); 35 | } 36 | 37 | private void on_provider_report(Fossil.Settings.Report report){ 38 | this.provider_report(report); 39 | } 40 | 41 | public void on_submit_client_report(Fossil.Settings.Report report){ 42 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 43 | provider.submit_client_report(report); 44 | } 45 | } 46 | 47 | ///////////////////////////////////////////// 48 | // Fossil.Interface.Settings.Provider // 49 | ///////////////////////////////////////////// 50 | 51 | public void request_index(string path_prefix, Func cb){ 52 | GenericSet alredy_seen = new GenericSet(str_hash, str_equal); 53 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 54 | provider.request_index(path_prefix, (path) => { 55 | if (!alredy_seen.contains(path)) { 56 | alredy_seen.add(path); 57 | cb(path); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | public bool has_object(string path){ 64 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 65 | if (provider.has_object(path)) { 66 | return true; 67 | } 68 | } 69 | return false; 70 | } 71 | 72 | public string? read_object(string path){ 73 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 74 | string? content = provider.read_object(path); 75 | if (content != null) { 76 | return content; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | public bool can_write_object(string path){ 83 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 84 | if (provider.can_write_object(path)) { 85 | return true; 86 | } 87 | } 88 | return false; 89 | } 90 | 91 | // writing null content will be the equivalent of a delete 92 | public bool write_object(string path, string? content){ 93 | bool success = false; 94 | foreach(Fossil.Interface.Settings.Provider provider in fallbacks){ 95 | if (provider.write_object(path, content)) { 96 | if (deepwrite) { 97 | return true; 98 | } 99 | success = true; 100 | } 101 | } 102 | return success; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/settings/ram_settings_provider.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Settings.RamProvider : Fossil.Interface.Settings.Provider, Object { 2 | 3 | public HashTable objects = new HashTable(str_hash, str_equal); 4 | public bool writable = true; 5 | 6 | ///////////////////////////////////////////// 7 | // Fossil.Interface.Settings.Provider // 8 | ///////////////////////////////////////////// 9 | 10 | public void request_index(string path_prefix, Func cb){ 11 | objects.foreach((k,_) => { 12 | cb(k); 13 | }); 14 | } 15 | 16 | public bool has_object(string path){ 17 | return objects.get(path) != null; 18 | } 19 | 20 | public string? read_object(string path){ 21 | return objects.get(path); 22 | } 23 | 24 | public bool can_write_object(string path){ 25 | return writable; 26 | } 27 | 28 | public bool write_object(string path, string? content){ 29 | if (writable) { 30 | if (content == null){ 31 | objects.remove(path); 32 | } else { 33 | objects.set(path, content); 34 | } 35 | settings_updated(path); 36 | } 37 | return writable; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/settings/report.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Settings.Report : Object { 2 | public string module_name { get; private set; } 3 | public string path { get; private set; } 4 | public string? error { get; private set; } 5 | public string? warning { get; private set; } 6 | public string? info { get; private set; } 7 | public string? debug { get; private set; } 8 | public int64? timestamp; //number of milliseconds since 1970-01-01 UTC 9 | 10 | public Report(string module_name, string path, string? error = null, string? warning = null, string? info = null, string? debug = null){ 11 | this.module_name = module_name; 12 | this.path = path; 13 | this.error = error; 14 | this.warning = warning; 15 | this.info = info; 16 | this.debug = debug; 17 | this.timestamp = (GLib.get_real_time()/1000); 18 | } 19 | 20 | public Report.with_updated_path(Fossil.Settings.Report report, string new_path, string module_name_prefix = ""){ 21 | this.module_name = module_name_prefix+report.module_name; 22 | this.path = new_path; 23 | this.error = report.error; 24 | this.warning = report.warning; 25 | this.info = report.info; 26 | this.debug = report.debug; 27 | this.timestamp = report.timestamp; 28 | } 29 | 30 | public string to_string(){ 31 | string message = @"[$timestamp][$module_name]($path)"; 32 | if (error != null){ 33 | message += @" [ERROR] $error"; 34 | } 35 | if (warning != null){ 36 | message += @" [WARNING] $warning"; 37 | } 38 | if (info != null){ 39 | message += @" [INFO] $info"; 40 | } 41 | if (debug != null){ 42 | message += @" [DEBUG] $debug"; 43 | } 44 | return message; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/startup/about.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.About.Backend { 2 | public static void setup_store(Fossil.SuperRegistry super_registry){ 3 | print("[startup][about] setup_store\n"); 4 | var store = new Fossil.Store.About(); 5 | super_registry.store("core.stores.about",store); 6 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 7 | if (store_registry != null){ 8 | store_registry.add_resource_store("about:",store); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/startup/bookmarks.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Bookmarks.Backend { 2 | public static void setup_about_page(Fossil.SuperRegistry super_registry){ 3 | print("[startup][bookmarks] setup_about_page\n"); 4 | var about = (super_registry.retrieve("core.stores.about") as Fossil.Store.About); 5 | if (about != null) { 6 | about.set_sub_store("bookmarks",new Fossil.Store.AboutStore.FixedStatus("interactive/bookmarks")); 7 | } else { 8 | print("[startup][bookmarks] setup_about_page failed! No core.stores.about .\n"); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/startup/bookmarks.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Bookmarks.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | var translation = (super_registry.retrieve("localization.translation") as Fossil.Registry.TranslationRegistry); 5 | var bookmark_registry = (super_registry.retrieve("core.bookmarks") as Fossil.Registry.BookmarkRegistry); 6 | if (view_registry != null && bookmark_registry != null){ 7 | print("[startup][bookmarks] setup_views\n"); 8 | view_registry.add_view("fossil.bookmarks",() => { 9 | return new Fossil.GtkUi.View.Bookmarks(bookmark_registry,translation); 10 | }); 11 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/bookmarks","fossil.bookmarks")); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/startup/bookmarks.settings.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Bookmarks.Settings { 2 | public static void register_settings_bridge(Fossil.SuperRegistry super_registry, Fossil.Interface.Settings.Provider settings_provider){ 3 | print("[startup][bookmarks][settings] register_settings_bridge()\n"); 4 | var bookmark_registry = (super_registry.retrieve("core.bookmarks") as Fossil.Registry.BookmarkRegistry); 5 | if (bookmark_registry == null){ 6 | print("[startup][bookmarks][settings][error] No bookmark registry found!\n"); 7 | return; 8 | } 9 | var bookmarks_settings_bride = new Fossil.Settings.Bridge.Bookmarks(settings_provider, "settings.bookmarks", bookmark_registry); 10 | //store in super_registry for now to prevent it from getting unloaded 11 | super_registry.store("bookmarks.settings_bridge",bookmarks_settings_bride); 12 | } 13 | 14 | public static void register_default_settings(Fossil.Interface.Settings.Provider settings_provider){ 15 | print("[startup][bookmarks][settings] register_default_settings()\n"); 16 | settings_provider.write_object("settings.bookmarks"," 17 | fossil:// The builtin Homepage 18 | gemini://gemini.conman.org/ The first ever gemini server 19 | gopher://khzae.net/ An awesome gopher server 20 | "); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/startup/cache.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Cache.Backend { 2 | public static void setup_store(Fossil.SuperRegistry super_registry){ 3 | print("[startup][cache] setup_store\n"); 4 | super_registry.store("core.stores.cache",(new Fossil.Store.Cache())); 5 | } 6 | 7 | public static void setup_about_page(Fossil.SuperRegistry super_registry){ 8 | print("[startup][cache] setup_about_page\n"); 9 | var about = (super_registry.retrieve("core.stores.about") as Fossil.Store.About); 10 | if (about != null) { 11 | about.set_sub_store("cache",new Fossil.Store.AboutStore.FixedStatus("interactive/cache")); 12 | } else { 13 | print("[startup][cache] setup_about_page failed! No core.stores.about .\n"); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/startup/cache.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Cache.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | var translation = (super_registry.retrieve("localization.translation") as Fossil.Registry.TranslationRegistry); 5 | if (view_registry != null){ 6 | print("[startup][cache] setup_views\n"); 7 | view_registry.add_view("fossil.cacheview",() => { 8 | return new Fossil.GtkUi.View.Cache(translation); 9 | }); 10 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/cache","fossil.cacheview")); 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/startup/file.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.File.Backend { 2 | 3 | public static void setup_store(Fossil.SuperRegistry super_registry){ 4 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 5 | var mimeguesser = (super_registry.retrieve("core.mimeguesser") as Fossil.Registry.MimetypeGuesser); 6 | if (store_registry != null){ 7 | store_registry.add_resource_store("file://",new Fossil.Store.File.with_mimeguesser(mimeguesser)); 8 | } 9 | } 10 | 11 | public static void setup_uri_autocompletion(Fossil.SuperRegistry super_registry){ 12 | var uri_autoprefixer = (super_registry.retrieve("core.uri_autoprefixer") as Fossil.Registry.UriAutoprefix); 13 | if (uri_autoprefixer != null){ 14 | uri_autoprefixer.add("file:","file://"); 15 | uri_autoprefixer.add("file://","file://"); 16 | uri_autoprefixer.add("/","file:///"); 17 | uri_autoprefixer.add("~/","file://"+GLib.Environment.get_home_dir()+"/"); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/startup/file.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.File.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | var translation = (super_registry.retrieve("localization.translation") as Fossil.Registry.TranslationRegistry); 5 | if (view_registry != null){ 6 | view_registry.add_view("fossil.directory",() => { return new Fossil.GtkUi.View.Directory(translation); }); 7 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("text/fossil-directory","fossil.directory")); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/startup/finger.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Finger.Backend { 2 | 3 | public static void setup_store(Fossil.SuperRegistry super_registry){ 4 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 5 | print("[startup][finger][backend] setup_store()\n"); 6 | if (store_registry != null){ 7 | print("[startup][finger][backend] adding finger store\n"); 8 | var store = new Fossil.Store.Finger(); 9 | store_registry.add_resource_store("finger://",store); 10 | } 11 | } 12 | 13 | public static void setup_uri_autocompletion(Fossil.SuperRegistry super_registry){ 14 | var uri_autoprefixer = (super_registry.retrieve("core.uri_autoprefixer") as Fossil.Registry.UriAutoprefix); 15 | if (uri_autoprefixer != null){ 16 | uri_autoprefixer.add("finger:","finger://"); 17 | uri_autoprefixer.add("finger://","finger://"); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/startup/frontend.settings.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Frontend.Settings { 2 | 3 | public static void register_settings_object(Fossil.SuperRegistry super_registry, Fossil.Interface.Settings.Provider settings_provider){ 4 | print("[startup][frontend][settings] register_settings_object()\n"); 5 | var settings_object = new Fossil.Settings.Bridge.KV(settings_provider, "settings.frontend.kv"); 6 | super_registry.store("settings.frontend",settings_object); 7 | } 8 | 9 | public static void register_default_settings(Fossil.Interface.Settings.Provider settings_provider){ 10 | print("[startup][frontend][settings] register_default_settings()\n"); 11 | settings_provider.write_object("settings.frontend.kv"," 12 | new_tab_uri: fossil:// 13 | "); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/startup/gemini.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Gemini.Backend { 2 | 3 | public static void setup_mimetypes(Fossil.SuperRegistry super_registry){ 4 | var mimeguesser = (super_registry.retrieve("core.mimeguesser") as Fossil.Registry.MimetypeGuesser); 5 | if (mimeguesser != null){ 6 | mimeguesser.add_type(".gmi","text/gemini"); 7 | mimeguesser.add_type(".gemini","text/gemini"); 8 | } 9 | } 10 | 11 | public static void setup_store(Fossil.SuperRegistry super_registry){ 12 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 13 | print("[startup][gemini][backend] setup_store()\n"); 14 | if (store_registry != null){ 15 | print("[startup][gemini][backend] adding gemini store\n"); 16 | var store = new Fossil.Store.Gemini(); 17 | store_registry.add_resource_store("gemini://",store); 18 | } 19 | } 20 | 21 | public static void setup_uri_autocompletion(Fossil.SuperRegistry super_registry){ 22 | var uri_autoprefixer = (super_registry.retrieve("core.uri_autoprefixer") as Fossil.Registry.UriAutoprefix); 23 | if (uri_autoprefixer != null){ 24 | uri_autoprefixer.add("gemini.","gemini://gemini."); 25 | uri_autoprefixer.add("gemini:","gemini://"); 26 | uri_autoprefixer.add("gemini://","gemini://"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/startup/gemini.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Gemini.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | if (view_registry != null){ 5 | view_registry.add_view("gemini.input",() => { return new Fossil.GtkUi.View.GeminiInput(); }); 6 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("gemini/input","gemini.input")); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/startup/geminiupload.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.GeminiUpload.Backend { 2 | 3 | public static void setup_store(Fossil.SuperRegistry super_registry){ 4 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 5 | if (store_registry != null){ 6 | var store = new Fossil.Store.GeminiUpload(); 7 | store_registry.add_resource_store("gemini+upload://",store); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/startup/geminiwrite.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.GeminiWrite.Backend { 2 | 3 | public static void setup_store(Fossil.SuperRegistry super_registry){ 4 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 5 | if (store_registry != null){ 6 | var store = new Fossil.Store.GeminiWrite(); 7 | store_registry.add_resource_store("gemini+write://",store); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/startup/gopher.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Gopher.Backend { 2 | 3 | public static void setup_gophertypes(Fossil.SuperRegistry super_registry){ 4 | var gopher_type_registry = (super_registry.retrieve("gopher.types") as Fossil.Registry.GopherTypeRegistry); 5 | if (gopher_type_registry == null){ 6 | super_registry.store("gopher.types",new Fossil.Registry.GopherTypeRegistry.default_configuration()); 7 | } 8 | } 9 | 10 | public static void setup_mimetypes(Fossil.SuperRegistry super_registry){ 11 | var mimeguesser = (super_registry.retrieve("core.mimeguesser") as Fossil.Registry.MimetypeGuesser); 12 | if (mimeguesser != null){ 13 | mimeguesser.add_type(".gopher","text/gopher"); 14 | mimeguesser.add_type(".gph","text/gopher"); 15 | } 16 | } 17 | 18 | public static void setup_store(Fossil.SuperRegistry super_registry){ 19 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 20 | if (store_registry != null){ 21 | var mimeguesser = (super_registry.retrieve("core.mimeguesser") as Fossil.Registry.MimetypeGuesser); 22 | var gopher_type_registry = (super_registry.retrieve("gopher.types") as Fossil.Registry.GopherTypeRegistry); 23 | Fossil.Store.Gopher store; 24 | if (mimeguesser == null){ 25 | store = new Fossil.Store.Gopher(); 26 | } else { 27 | store = new Fossil.Store.Gopher.with_mimeguesser(mimeguesser,gopher_type_registry); 28 | } 29 | store_registry.add_resource_store("gopher://",store); 30 | } 31 | } 32 | 33 | public static void setup_uri_autocompletion(Fossil.SuperRegistry super_registry){ 34 | var uri_autoprefixer = (super_registry.retrieve("core.uri_autoprefixer") as Fossil.Registry.UriAutoprefix); 35 | if (uri_autoprefixer != null){ 36 | uri_autoprefixer.add("gopher.","gopher://gopher."); 37 | uri_autoprefixer.add("gopher:","gopher://"); 38 | uri_autoprefixer.add("gopher://","gopher://"); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/startup/gopherwrite.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.GopherWrite.Backend { 2 | 3 | public static void setup_gophertypes(Fossil.SuperRegistry super_registry){ 4 | var gopher_type_registry = (super_registry.retrieve("gopher.types") as Fossil.Registry.GopherTypeRegistry); 5 | if (gopher_type_registry == null){ 6 | gopher_type_registry = new Fossil.Registry.GopherTypeRegistry.default_configuration(); 7 | super_registry.store("gopher.types",gopher_type_registry); 8 | } 9 | gopher_type_registry.add(new Fossil.Registry.GopherTypeRegistryEntry('w',null,"gopher+writet://{host}:{port}/{selector}")); 10 | } 11 | 12 | public static void setup_store(Fossil.SuperRegistry super_registry){ 13 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 14 | if (store_registry != null){ 15 | var store = new Fossil.Store.GopherWrite(); 16 | store_registry.add_resource_store("gopher+writet://",store); 17 | store_registry.add_resource_store("gopher+writef://",store); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/startup/hypertext.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Hypertext.Gtk { 2 | 3 | public static void setup_views(Fossil.SuperRegistry super_registry, Fossil.Interface.Settings.Provider? settings_provider){ 4 | print("[startup][hypertext][gtk] setup_views()\n"); 5 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 6 | if (view_registry != null){ 7 | // Get get all the theming stuff set up 8 | var theme_loader = new Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeLoader(settings_provider, "themes."); 9 | Fossil.GtkUi.Interface.Theming.HypertextViewTheme? default_theme = theme_loader.get_theme_by_name("default"); 10 | if(default_theme == null) { //fall back to an empty theme 11 | default_theme = new Fossil.GtkUi.Theming.HypertextViewTheme(); 12 | } 13 | var theme_provider = new Fossil.GtkUi.Theming.DefaultHypertextViewThemeProvider(default_theme); 14 | //setup the theme rule provider 15 | var theme_rule_provider = new Fossil.GtkUi.SettingsIntegration.SettingsHypertextJsonThemeRuleProvider(settings_provider, "settings.theme_rules.json"); 16 | theme_provider.set_theme_loader(theme_loader); 17 | theme_provider.set_rule_provider(theme_rule_provider); 18 | // Get a gophertype registry gfot the gopher token parser in the DefaultTokenParserFactory 19 | var gopher_type_registry = (super_registry.retrieve("gopher.types") as Fossil.Registry.GopherTypeRegistry); 20 | if (gopher_type_registry == null) { 21 | gopher_type_registry = new Fossil.Registry.GopherTypeRegistry.default_configuration(); 22 | } 23 | var parser_factory = new Fossil.Document.DefaultTokenParserFactory(gopher_type_registry); 24 | //register the hypertext view 25 | view_registry.add_view("hypertext",() => { 26 | return new Fossil.GtkUi.View.Hypertext(parser_factory, theme_provider); 27 | }); 28 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("text/gemini","hypertext")); 29 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("text/gopher","hypertext")); 30 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("text/", "hypertext")); 31 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("application/xml", "hypertext")); 32 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule.resource_view("application/json", "hypertext")); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/startup/hypertext.settings.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Hypertext.Settings { 2 | 3 | 4 | public static void register_default_settings(Fossil.Interface.Settings.Provider settings_provider){ 5 | string default_style_json = """ 6 | { 7 | "prefixes":{ 8 | "link":"{{{link_icon}}}", 9 | "link :inline":" ", 10 | "list_item":"▶ ", 11 | "parser_error":"[PARSER_ERROR] ", 12 | "link_without_uri":"[PARSER MISTAKE] Link without uri: ", 13 | "search_without_uri":"[PARSER MISTAKE] Search without uri: ", 14 | "exception":"[INTERNAL ERROR]" 15 | }, 16 | "tag_themes":{ 17 | "link":{ 18 | "scale":1.1, 19 | "font":"italic" 20 | }, 21 | "link :hover":{ 22 | "underline":"single", 23 | "scale":1.15 24 | }, 25 | "link :prefix":{ 26 | "scale":1.15 27 | }, 28 | "link_icon":{ 29 | "scale":1.5 30 | }, 31 | "title +0":{ 32 | "scale":1.7 33 | }, 34 | "title +1":{ 35 | "scale":1.5 36 | }, 37 | "title":{ 38 | "scale":1.2 39 | }, 40 | "quote":{ 41 | "font":"oblique" 42 | }, 43 | "description":{ 44 | "scale":0.9, 45 | "font":"italic", 46 | "indent":10 47 | }, 48 | "paragraph :preformatted":{ 49 | "wrap_mode":"none", 50 | "indent":10 51 | }, 52 | "error":{ 53 | "foreground":"#FB3934" 54 | }, 55 | "parser_error":{ 56 | "foreground":"#FB3934", 57 | "font":"italic" 58 | }, 59 | "exception":{ 60 | "foreground":"#FB3934", 61 | "font":"italic" 62 | }, 63 | "*:preformatted":{ 64 | "font":"monospace" 65 | }, 66 | "*":{ 67 | "wrap_mode":"word_char" 68 | } 69 | } 70 | } 71 | """; 72 | settings_provider.write_object("themes.default.json", default_style_json); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/startup/localization.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.LocalizationRegistry { 2 | public static void setup_translation_registry(SuperRegistry super_registry){ 3 | super_registry.store("localization.translation",new Fossil.Registry.TranslationMultiplexerRegistry()); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/startup/sessions.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Sessions.Backend { 2 | public static void register_core_sessions(Fossil.SuperRegistry super_registry){ 3 | print("[startup][sessions] Adding core sessions... \n"); 4 | var session_registry = (super_registry.retrieve("core.sessions") as Fossil.Registry.SessionRegistry); 5 | var main_store = (super_registry.retrieve("core.stores.main") as Fossil.Interface.ResourceStore); 6 | if (session_registry == null){ 7 | print("[startup][sessions][error] No session registry found ...\n"); 8 | return; 9 | } 10 | if (main_store == null){ 11 | print("[startup][sessions][error] No main resource store found ...\n"); 12 | return; 13 | } 14 | session_registry.register_session("core.default",new Fossil.Session.Default(main_store)); 15 | session_registry.register_session("core.uncached",new Fossil.Session.Uncached(main_store)); 16 | session_registry.register_session("fossil.tls_0",new Fossil.Session.Tls(main_store)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/startup/sessions.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Sessions.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | var translation = (super_registry.retrieve("localization.translation") as Fossil.Registry.TranslationRegistry); 5 | if (view_registry != null){ 6 | print("[startup][sessions] setup_views"); 7 | view_registry.add_view("fossil.tls_session",() => { 8 | return new Fossil.GtkUi.View.TlsSession(translation); 9 | }); 10 | var no_session_configuration_view_factory = new Fossil.GtkUi.LegacyUtil.MessageViewFactory("error/uri/unknownScheme","action-unavailable-symbolic",translation,"view.no_session_panel.label","view.no_session_panel.sublabel"); 11 | view_registry.add_view("fossil.no_session_panel",no_session_configuration_view_factory.construct_view); 12 | 13 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/tls_session","fossil.tls_session")); 14 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("error/uri/unknownScheme","fossil.no_session_panel").prefix("session://")); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/startup/settings.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Settings.Backend { 2 | 3 | public static Fossil.Interface.Settings.Provider? get_file_settings_provider(string subdirectory, string prefix, string name){ 4 | string settingsdir = GLib.Environment.get_user_config_dir(); 5 | settingsdir = settingsdir+"/fossil/"+subdirectory; 6 | GLib.DirUtils.create_with_parents(settingsdir,16832); 7 | var provider = new Fossil.Settings.FileProvider(settingsdir, name, prefix); 8 | return provider; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/startup/store_switch.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.StoreSwitch { 2 | 3 | public static void setup_store(Fossil.SuperRegistry super_registry){ 4 | var store_registry = (super_registry.retrieve("core.stores") as Fossil.Registry.StoreRegistry); 5 | var cache = (super_registry.retrieve("core.stores.cache") as Fossil.Interface.Cache); 6 | if (store_registry != null){ 7 | string cachedir = GLib.Environment.get_user_cache_dir(); 8 | super_registry.store("core.stores.main",new Fossil.Store.Switch(cachedir+"/fossil",store_registry,cache)); 9 | } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/startup/upload.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Upload.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | var translation = (super_registry.retrieve("localization.translation") as Fossil.Registry.TranslationRegistry); 5 | var mimeguesser = (super_registry.retrieve("core.mimeguesser") as Fossil.Registry.MimetypeGuesser); 6 | var tempfilebase = GLib.Environment.get_tmp_dir()+"/"; 7 | if (view_registry != null && translation != null){ 8 | print("[startup][upload][gtk] setup_views()\n"); 9 | view_registry.add_view("upload.file",() => { return new Fossil.GtkUi.View.UploadFile(translation,mimeguesser); }); 10 | view_registry.add_view("upload.text",() => { 11 | var tempfilepath = tempfilebase+"fossil_upload_"+GLib.Uuid.string_random(); 12 | return new Fossil.GtkUi.View.UploadText(tempfilepath,translation,mimeguesser); 13 | }); 14 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/upload","upload.file")); 15 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/upload/text","upload.text")); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/startup/utiltest.backend.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Utilfossil.Backend { 2 | public static void setup_about_page(Fossil.SuperRegistry super_registry){ 3 | print("[startup][utilfossil] setup_about_page()\n"); 4 | var about = (super_registry.retrieve("core.stores.about") as Fossil.Store.About); 5 | if (about != null) { 6 | about.set_sub_store("ruri",new Fossil.Store.AboutStore.FixedStatus("interactive/uri_merge_fossil")); 7 | } else { 8 | print("[startup][utilfossil] setup_about_page failed! No core.stores.about .\n"); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/startup/utiltest.gtk.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Startup.Utilfossil.Gtk { 2 | public static void setup_views(Fossil.SuperRegistry super_registry){ 3 | var view_registry = (super_registry.retrieve("gtk.views") as Fossil.GtkUi.LegacyViewRegistry); 4 | if (view_registry != null){ 5 | print("[startup][utilfossil] setup_views()\n"); 6 | view_registry.add_view("uri_merge_fossil",() => { return new Fossil.GtkUi.View.UriMergeInternal(); }); 7 | view_registry.add_rule(new Fossil.GtkUi.LegacyViewRegistryRule("interactive/uri_merge_fossil","uri_merge_fossil")); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/stores/about.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Store.About : Object, Fossil.Interface.ResourceStore { 2 | 3 | private HashTable substores = new HashTable(str_hash, str_equal); 4 | 5 | construct { 6 | this.set_sub_store("blank",new Fossil.Store.AboutStore.FixedText("")); 7 | } 8 | 9 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 10 | var substore = substores.get(request.uri); 11 | if (substore != null) { 12 | substore.request(request,filepath,upload); 13 | } else { 14 | request.setStatus("error/resourceUnavaiable"); 15 | request.finish(); 16 | } 17 | } 18 | 19 | public void set_sub_store(string about_what, Fossil.Interface.ResourceStore? substore){ 20 | if(substore != null) { 21 | print("[about] registred about:"+about_what+"\n"); 22 | substores.set("about:"+about_what,substore); 23 | } else { 24 | substores.remove("about:"+about_what); 25 | } 26 | } 27 | 28 | } 29 | 30 | public class Fossil.Store.AboutStore.FixedText : Object, Fossil.Interface.ResourceStore { 31 | 32 | public string text; 33 | public string mimetype; 34 | public string name; 35 | 36 | public FixedText(string text, string mimetype = "text/plain", string name = ""){ 37 | this.text = text; 38 | this.mimetype = mimetype; 39 | this.name = name; 40 | } 41 | 42 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 43 | if (filepath == null){ 44 | request.setStatus("error/internal","Filepath required!"); 45 | request.finish(); 46 | return; 47 | } 48 | if (upload){ 49 | request.setStatus("error/noupload","Uploding not supported"); 50 | request.finish(); 51 | return; 52 | } 53 | var helper = new Fossil.Util.ResourceFileWriteHelper(request,filepath,0); 54 | helper.appendString(this.text); 55 | if (helper.error){return;} 56 | helper.close(); 57 | var resource = new Fossil.Resource(request.uri,filepath,true); 58 | resource.add_metadata(this.mimetype,this.name); 59 | request.setResource(resource,"about"); 60 | request.finish(true); 61 | } 62 | 63 | } 64 | 65 | public class Fossil.Store.AboutStore.FixedStatus : Object, Fossil.Interface.ResourceStore { 66 | 67 | public string status; 68 | public string substatus; 69 | 70 | public FixedStatus(string status, string substatus=""){ 71 | this.status = status; 72 | this.substatus = substatus; 73 | } 74 | 75 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 76 | request.setStatus(this.status,this.substatus); 77 | request.finish(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/stores/cache.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Store.Cache : Object, Fossil.Interface.ResourceStore, Fossil.Interface.Cache{ 2 | 3 | public HashTable cached_resources { get; protected set; } 4 | 5 | construct { 6 | cached_resources = new HashTable(str_hash, str_equal); 7 | Timeout.add(1000*60*5,() => { //clean cache 8 | print("[cache] Cleaning Up!\n"); 9 | clean(); 10 | return true; 11 | },Priority.LOW); 12 | } 13 | 14 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 15 | if (upload){ 16 | request.setStatus("error/noupload","Uploding not supported"); 17 | request.finish(); 18 | return; 19 | } 20 | var resource = cached_resources.get(request.uri); //let's hope this is threadsafe 21 | if(resource == null) { 22 | request.setStatus("error/resourceUnavaiable"); 23 | request.finish(); 24 | } 25 | request.setResource(resource,"cache"); 26 | } 27 | 28 | //if maxage is 0 assume the age doesn't matter 29 | //maxge is in milliseconds 30 | public bool can_serve_request(string uri,int64 maxage = 0){ 31 | print(@"[cache] can serve request? URI:$uri MAXAGE:$maxage\n"); 32 | var resource = cached_resources.get(uri); //let's hope this is threadsafe 33 | if(resource == null) { print("[cache] No, beacause resource is not cached.\n"); return false; } 34 | if(resource.filepath == null) { print("[cache] No, beacause resource is not cached anymore.\n"); return false; } 35 | //No, I'm not a LISP developer. 36 | if (((resource.valid_until - (GLib.get_real_time()/1000)) < 0) && (resource.valid_until != int64.MAX)){ print("[cache] No, beacause resource is not valid anymore.\n"); return false; } 37 | if(maxage > 0){ return resource.timestamp+maxage >= (GLib.get_real_time()/1000); } 38 | return true; 39 | } 40 | 41 | public void put_resource(Fossil.Resource resource){ 42 | if (will_cache_resource(resource)){ 43 | print(@"[cache] put resource $(resource.uri) fully loaded at $(resource.timestamp) valid until $(resource.valid_until)\n"); 44 | resource.increment_users("cache"); 45 | lock (cached_resources){ 46 | var old_resource = cached_resources.get(resource.uri); 47 | if (old_resource != null){ old_resource.decrement_users("cache"); } 48 | cached_resources.set(resource.uri,resource); 49 | } 50 | } 51 | } 52 | 53 | private bool will_cache_resource(Fossil.Resource resource){ 54 | if(resource.valid_until == 0){ return false; } 55 | if(!resource.is_temporary){ return false; } //permanent resoureces are already in the filesystem and fetched fast 56 | if(resource.filepath == null){ return false; } 57 | return true; 58 | } 59 | 60 | public void erase(){ 61 | lock (cached_resources){ 62 | foreach (Fossil.Resource resource in cached_resources.get_values()){ 63 | resource.decrement_users("cache"); 64 | } 65 | cached_resources.remove_all(); 66 | } 67 | } 68 | 69 | public void clean(){ 70 | //print(@"[cache] $(GLib.get_real_time()) | current time\n"); 71 | lock (cached_resources){ 72 | foreach (string uri in cached_resources.get_keys()){ 73 | var resource = cached_resources.get(uri); 74 | bool clean = false; 75 | if (resource.filepath == null){ clean = true; } 76 | //print(@"[cache] $(resource.valid_until) | $uri"); 77 | if (resource.valid_until < (GLib.get_real_time()/1000)){ clean = true; } 78 | //print(@" [$clean]\n"); 79 | if (clean){ 80 | resource.decrement_users("cache"); 81 | cached_resources.remove(uri); 82 | } 83 | } 84 | } 85 | } 86 | 87 | public void invalidate_for_uri(string uri){ 88 | lock (cached_resources){ 89 | var resource = cached_resources.get(uri); 90 | if (resource != null){ 91 | resource.decrement_users("cache"); 92 | cached_resources.remove(uri); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/stores/file.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Store.File : Object, Fossil.Interface.ResourceStore { 2 | 3 | public Fossil.Registry.MimetypeGuesser mimeguesser = null; 4 | 5 | 6 | public File(){ 7 | mimeguesser = new Fossil.Registry.MimetypeGuesser.default_configuration(); 8 | } 9 | 10 | public File.with_mimeguesser(Fossil.Registry.MimetypeGuesser mimeguesser){ 11 | this.mimeguesser = mimeguesser; 12 | } 13 | 14 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 15 | if (upload){ 16 | request.setStatus("error/noupload","Uploding not supported"); 17 | request.finish(); 18 | return; 19 | } 20 | string path = null; 21 | // parse uri 22 | if(request.uri.has_prefix("file://")){ 23 | path = request.uri.slice(7,request.uri.length); 24 | } else if (request.uri.has_prefix("/")) { 25 | path = request.uri; 26 | } else { 27 | request.setStatus("error/uri/unknownScheme","File only knows file:// or /"); 28 | request.finish(); 29 | return; 30 | } 31 | 32 | if (path == ""){path = Environment.get_home_dir();} 33 | 34 | path = GLib.Uri.unescape_string(path); 35 | 36 | var file = GLib.File.new_for_path(path); 37 | if (!file.query_exists()){ 38 | request.setStatus("error/resourceUnavaiable","No Such File or directory"); 39 | request.finish(); 40 | return; 41 | } 42 | 43 | var basename = file.get_basename(); 44 | if (basename == null) {basename = request.uri;} 45 | 46 | if(FileUtils.test(path, FileTest.IS_DIR)){ 47 | var helper = new Fossil.Util.ResourceFileWriteHelper(request,filepath,0); 48 | try { 49 | Dir dir = Dir.open (path, 0); 50 | string? name = null; 51 | helper.appendString("HOME\tfile://"+GLib.Uri.escape_string(Environment.get_home_dir(),"/")+"\n"); 52 | helper.appendString("ROOT\tfile:///\n"); 53 | helper.appendString("\n"); 54 | helper.appendString("THIS\t"+path+"\n"); 55 | var parent = file.get_parent(); 56 | if (parent != null){ 57 | helper.appendString("PARENT\t"+parent.get_uri()+"\n"); 58 | } 59 | helper.appendString("\n"); 60 | while ((name = dir.read_name ()) != null) { 61 | if (request.cancelled) { 62 | helper.cancel(); 63 | break; 64 | } 65 | string fpath = Path.build_filename (path, name); 66 | string type = "FILE"; 67 | if (FileUtils.test (fpath, FileTest.IS_DIR)) { 68 | type = "DIRECTORY"; 69 | } 70 | if (!name.has_prefix(".")){ 71 | helper.appendString(type+"\tfile://"+GLib.Uri.escape_string(fpath,"/")+"\t"+name+"\n"); 72 | } 73 | } 74 | } catch (FileError e) { 75 | helper.cancel(); 76 | request.setStatus("error/internal",e.message); 77 | request.finish(); 78 | } 79 | if (helper.closed) { return; } 80 | helper.close(); 81 | var resource = new Fossil.Resource(request.uri,filepath,true); 82 | resource.add_metadata("text/fossil-directory",basename); 83 | request.setResource(resource,"file"); 84 | return; 85 | } 86 | 87 | var resource = new Fossil.Resource(request.uri,path,false); 88 | resource.add_metadata(mimeguesser.get_closest_match(basename,"text/plain"),basename); 89 | request.setResource(resource,"file"); 90 | return; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/stores/switch.vala: -------------------------------------------------------------------------------- 1 | class Fossil.Store.Switch : Object, Fossil.Interface.ResourceStore { 2 | 3 | private string cacheDirectory = "/tmp"; 4 | private Fossil.Registry.StoreRegistry storeRegistry; 5 | private Fossil.Interface.Cache? cache = null; 6 | //private List hooked_requests = new Listy(); 7 | 8 | public Switch(string? cacheDirectory,Fossil.Registry.StoreRegistry storeRegistry,Fossil.Interface.Cache? cache = null){ 9 | this.cache = cache; 10 | this.storeRegistry = storeRegistry; 11 | if (cacheDirectory != null){ 12 | this.cacheDirectory = cacheDirectory; 13 | GLib.DirUtils.create_with_parents(this.cacheDirectory,16832); 14 | } 15 | } 16 | 17 | public Switch.default_configuration(){ 18 | this.storeRegistry = new Fossil.Registry.StoreRegistry.default_configuration(); 19 | string cachedir = GLib.Environment.get_user_cache_dir(); 20 | this.cacheDirectory = cachedir+"/fossil"; 21 | GLib.DirUtils.create_with_parents(this.cacheDirectory,16832); 22 | } 23 | 24 | public void request(Fossil.Request request,string? filepath = null, bool upload = false){ 25 | print(@"[switch] Loading uri: '$(request.uri)'\n"); 26 | if (cache != null && (!request.reload) && (!upload)){ 27 | if (cache.can_serve_request(request.uri)){ 28 | print(@"[switch] Serving from cache!\n"); 29 | cache.request(request); 30 | return; 31 | } 32 | } 33 | load_from_store(request, filepath, upload); 34 | } 35 | 36 | private void load_from_store(Fossil.Request request,string? filepath = null, bool upload = false){ 37 | var store = storeRegistry.get_closest_match(request.uri); 38 | if (store != null){ 39 | string filepathx = filepath; 40 | if (filepathx == null){filepathx=this.cacheDirectory+"/temp_"+GLib.Uuid.string_random();} 41 | store.request(request,filepathx,upload); 42 | } else { 43 | request.setStatus("error/uri/unknownScheme"); 44 | request.finish(); 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/ui/document/default_token_parser_factory.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Document.DefaultTokenParserFactory : Fossil.Interface.Document.TokenParserFactory, Object { 2 | 3 | private Fossil.Registry.GopherTypeRegistry gopher_type_registry; 4 | 5 | public DefaultTokenParserFactory(Fossil.Registry.GopherTypeRegistry gopher_type_registry){ 6 | this.gopher_type_registry = gopher_type_registry; 7 | } 8 | 9 | /////////////////////////////////////////////////////// 10 | // Fossil.Interface.Document.TokenParserFactory // 11 | /////////////////////////////////////////////////////// 12 | 13 | public Fossil.Interface.Document.TokenParser? get_token_parser(string content_type){ 14 | //Make sure we don't forget to add the parser to the has_parser_for function 15 | if (!has_parser_for(content_type)) { 16 | return null; 17 | } 18 | if (content_type.has_prefix("text/gopher")) { return new Fossil.Ui.Document.TokenParser.Gopher(gopher_type_registry); } 19 | if (content_type.has_prefix("application/gopher")) { return new Fossil.Ui.Document.TokenParser.Gopher(gopher_type_registry); } 20 | if (content_type.has_prefix("text/gemini")) { return new Fossil.Ui.Document.TokenParser.Gemini(); } 21 | if (content_type.has_prefix("text/")) { return new Fossil.Ui.Document.TokenParser.Plaintext(); } 22 | return null; 23 | } 24 | 25 | public bool has_parser_for(string content_type){ 26 | if (content_type.has_prefix("text/gopher")) { return true; } 27 | if (content_type.has_prefix("application/gopher")) { return true; } 28 | if (content_type.has_prefix("text/gemini")) { return true; } 29 | if (content_type.has_prefix("text/")) { return true; } 30 | return false; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/ui/document/token.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Ui.Document.Token : Object { 2 | 3 | public string text = ""; 4 | public string? uri = null; 5 | public bool preformatted = false; 6 | public bool inlined = false; //don't start a new paragraph if possible 7 | public Fossil.Ui.Document.TokenType token_type; 8 | public uint level = 0; 9 | 10 | 11 | public Token(Fossil.Ui.Document.TokenType token_type, uint level, string text, string? uri = null, bool preformatted = false, bool inlined = false){ 12 | this.token_type = token_type; 13 | this.level = level; 14 | this.text = text; 15 | this.uri = uri; 16 | this.preformatted = preformatted; 17 | this.inlined; 18 | } 19 | 20 | public Token.parser_error(uint level, string text){ 21 | this.token_type = Fossil.Ui.Document.TokenType.PARSER_ERROR; 22 | this.level = level; 23 | this.text = text; 24 | } 25 | } 26 | 27 | /* 28 | Example: The following geminitext will be pared into 29 | 30 | ------------------------------------------------- 31 | v 32 | # Some Example 33 | A paragraph below the first headline 34 | ## A second Headline 35 | ```With a preformatted block 36 | With a 37 | preformatted 38 | block 39 | ``` 40 | => gemini://example.org and a link 41 | 42 | # List 43 | 44 | * one item 45 | * two items 46 | * 11 items 47 | 48 | The above is a list example, 49 | below is a quote 50 | 51 | > Someone once said something 52 | - By someone else 53 | 54 | ------------------------------------------------- 55 | 56 | type level uri preformatted inlined 57 | text 58 | 59 | PARAGRAPH 0 60 | A paragraph above the first headline 61 | TITLE 0 62 | Some Example 63 | PARAGRAPH 0 64 | A paragraph below the first headline 65 | TITLE 1 66 | A second Headline 67 | PARAGRAPH 1 null true 68 | With a 69 | preformatted 70 | block 71 | DESCRIPTION 1 72 | With a preformatted block 73 | LINK 1 gemini://example.org 74 | and a link 75 | EMPTY_LINE 1 76 | HEADLINE 0 77 | List 78 | EMPTY_LINE 0 79 | LIST_ITEM 0 80 | one item 81 | LIST_ITEM 0 82 | two items 83 | LIST_ITEM 0 84 | 11 items 85 | EMPTY_LINE 0 86 | PARAGRAPH 0 87 | The above is a list example, 88 | below is a quote 89 | EMPTY_LINE 0 90 | QUOTE 0 91 | Someone once said something 92 | PARAGRAPH 0 93 | - By someone else 94 | 95 | */ 96 | -------------------------------------------------------------------------------- /src/ui/document/token_parser/plaintext.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Ui.Document.TokenParser.Plaintext : Fossil.Interface.Document.TokenParser, Object { 2 | 3 | private DataInputStream? input_stream = null; 4 | 5 | private uint level = 0; 6 | 7 | //////////////////////////////////////////////// 8 | // Fossil.Interface.Document.TokenParser // 9 | //////////////////////////////////////////////// 10 | 11 | public void set_input_stream(InputStream input_stream){ 12 | this.input_stream = new DataInputStream(input_stream); 13 | } 14 | 15 | //returns null when finished can possibly hang because of the input stream 16 | public Fossil.Ui.Document.Token? next_token(){ 17 | if (input_stream == null) { return null; } 18 | string? line = null; 19 | Fossil.Ui.Document.Token? token = null; 20 | try { 21 | if ((line = input_stream.read_line (null)) != null) { 22 | if (line == "") { 23 | token = new Fossil.Ui.Document.Token(EMPTY_LINE, 0, ""); 24 | } else { 25 | token = new Fossil.Ui.Document.Token(PARAGRAPH, level, line+"\n", null, true); 26 | } 27 | } 28 | if (token == null && input_stream != null) { 29 | input_stream.close(); 30 | } 31 | } catch (Error e) { 32 | return new Fossil.Ui.Document.Token.parser_error(level, e.message); 33 | } 34 | return token; 35 | } 36 | 37 | public void reset(){ 38 | if (input_stream != null) { 39 | try { 40 | input_stream.close(); 41 | } catch (Error e) { 42 | //ignore 43 | } 44 | } 45 | this.input_stream = null; 46 | this.level = 0; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/ui/document/token_type.vala: -------------------------------------------------------------------------------- 1 | public enum Fossil.Ui.Document.TokenType { 2 | PARAGRAPH, 3 | TITLE, 4 | LINK, 5 | QUOTE, 6 | SEARCH, //the uri will be a pattern containg a '{search}' wich will be replaced by the actual search 7 | ERROR, //Gophertype 3 8 | PARSER_ERROR, //when the parser encounters an error 9 | DESCRIPTION, //of what came above 10 | LIST_ITEM, 11 | EMPTY_LINE; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/ui/tab_display_state.vala: -------------------------------------------------------------------------------- 1 | public enum Fossil.Ui.TabDisplayState { 2 | LOADING, 3 | ERROR, 4 | CONTENT, 5 | BLANK; 6 | } 7 | -------------------------------------------------------------------------------- /src/util/flaglist.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Util.Flaglist : Object { 2 | public List flags = new List(); 3 | 4 | public void set_flag(string flag, bool state = true){ 5 | if (state) { 6 | lock(flags) { 7 | if (!has_flag(flag)){ 8 | flags.append(flag); 9 | } 10 | } 11 | } else { 12 | this.clear_flag(flag); 13 | } 14 | } 15 | 16 | public void clear_flag(string flag){ 17 | if (has_flag(flag)){ 18 | unowned List? link = flags.find_custom(flag,(a,b) => { 19 | if(a==b){ return 0; } 20 | return 1; 21 | }); 22 | flags.delete_link(link); 23 | } 24 | } 25 | 26 | public bool has_flag(string flag){ 27 | foreach( string f in flags ) { 28 | if (f==flag) {return true;} 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/util/intparser.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Util.Intparser { 2 | 3 | public static bool try_parse_unsigned(string input,out uint64 integer){ 4 | bool success = false; 5 | integer = 0; 6 | for (int i = 0; i values = new HashTable(str_hash,str_equal); 3 | 4 | public static bool is_valid_key(string key){ 5 | return !(key.contains("\n") || key.has_prefix("\t")); 6 | } 7 | 8 | public void import(string raw){ 9 | string[] lines = raw.split("\n"); 10 | string? multiline_key = null; 11 | string? multiline_value = null; 12 | foreach (string line in lines){ 13 | string stripped_line = line.strip(); 14 | if (line.has_prefix("\t")) { 15 | if (multiline_key != null){ 16 | if (multiline_value != null){ 17 | multiline_value += "\n"+line.substring(1); 18 | } else { 19 | multiline_value = line.substring(1); 20 | } 21 | } 22 | } else { 23 | if(stripped_line == ""){ 24 | if (multiline_key != null){ 25 | if (multiline_value != null){ 26 | multiline_value += "\n"; 27 | } else { 28 | multiline_value = ""; 29 | } 30 | } 31 | } else { 32 | if (multiline_value != null){ 33 | values.set(multiline_key,multiline_value); 34 | } 35 | multiline_key = stripped_line; 36 | multiline_value = null; 37 | } 38 | } 39 | } 40 | if (multiline_value != null){ 41 | values.set(multiline_key,multiline_value); 42 | } 43 | } 44 | 45 | public string export(){ 46 | string? export = null; 47 | values.foreach((key,val) => { 48 | if (is_valid_key(key)){ 49 | if (export == null){ 50 | export = ""; 51 | } else { 52 | export += "\n"; 53 | } 54 | export += key+"\n"; 55 | export += "\t"+val.replace("\n","\n\t"); 56 | } 57 | }); 58 | if (export == null){ 59 | export = ""; 60 | } 61 | return export; 62 | } 63 | 64 | public bool set_if_null(string key, string val){ 65 | if (!is_valid_key(key)){ return false; } 66 | if (values.get(key) == null){ 67 | values.set(key,val); 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | public bool set_value(string key, string val){ 74 | if (!is_valid_key(key)){ return false; } 75 | values.set(key,val); 76 | return true; 77 | } 78 | 79 | public string? get_value(string key){ 80 | if (!is_valid_key(key)){ return null; } 81 | return values.get(key); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/util/resource_file_helper.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Util.ResourceFileWriteHelper : Object { 2 | 3 | private Fossil.Request request = null; 4 | private File file = null; 5 | private FileOutputStream outstream = null; 6 | public bool error = false; 7 | public bool cancelled = false; 8 | public bool closed = false; 9 | public int64 size = 0; 10 | public int64 progress = 0; 11 | public string filepath = null; 12 | 13 | public ResourceFileWriteHelper(Fossil.Request request, string filepath, int64 size){ 14 | this.request = request; 15 | this.size = size; 16 | this.filepath = filepath; 17 | try { 18 | this.file = File.new_for_path(filepath); 19 | this.outstream = file.create(FileCreateFlags.PRIVATE); 20 | }catch(Error e){ 21 | print(e.message); 22 | request.setStatus("error/internalError","Something went wrong wile opening "+filepath+":\n"+e.message); 23 | error = true; 24 | closed = true; 25 | return; 26 | } 27 | request.setStatus("loading","0/"+size.to_string("%x")); 28 | } 29 | 30 | public void append(uint8[] buffer){ 31 | if (this.closed) {return;} 32 | try{ 33 | this.outstream.write(buffer); 34 | this.progress += buffer.length; 35 | request.setStatus("loading",this.progress.to_string("%x")+"/"+this.size.to_string("%x")); 36 | }catch(Error e){ 37 | request.setStatus("error/internalError","Something went wrong wile writing to "+filepath+":\n"+e.message); 38 | error = true; 39 | this.close(); 40 | } 41 | } 42 | 43 | public void appendString(string buffer){ 44 | this.append(buffer.data); 45 | } 46 | 47 | public void cancel(){ 48 | if (this.closed) {return;} 49 | close(); 50 | cancelled = true; 51 | request.setStatus("cancelled"); 52 | try{ 53 | file.delete(); 54 | }catch(Error e){ 55 | error = true; 56 | request.setStatus("error/internalError","Something went wrong wile deleting "+filepath+":\n"+e.message); 57 | } 58 | } 59 | 60 | public void close(){ 61 | if (this.closed) {return;} 62 | this.closed = true; 63 | try { 64 | this.outstream.close(); 65 | }catch(Error e){ 66 | error = true; 67 | request.setStatus("error/internalError","Something went wrong wile closing "+filepath+":\n"+e.message); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/util/stack.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Util.Stack : Object{ 2 | 3 | public List list = new List(); 4 | 5 | public void push(G thingy){ 6 | list.append(thingy); 7 | } 8 | 9 | public G pop(){ 10 | unowned List entry = list.last(); 11 | if(entry == null){ return null; } 12 | list.remove_link (entry); 13 | return entry.data; 14 | } 15 | 16 | public void clear(){ 17 | if (list.length()>0){ 18 | list = new List(); 19 | } 20 | } 21 | 22 | public uint size(){ 23 | return list.length(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/util/tls_certificate_generator.vala: -------------------------------------------------------------------------------- 1 | public class Fossil.Util.TlsCertficateGenerator { 2 | 3 | public static GnuTLS.X509.PrivateKey generate_private_key() { 4 | var key = GnuTLS.X509.PrivateKey.create(); 5 | key.generate(GnuTLS.PKAlgorithm.RSA, 4096); 6 | return key; 7 | } 8 | 9 | public static GnuTLS.X509.Certificate? generate_client_certificate(GnuTLS.X509.PrivateKey key){ 10 | var certificate = GnuTLS.X509.Certificate.create(); 11 | var activation_time = new DateTime.now_local(); 12 | var expiration_time = activation_time.add_years(15); 13 | 14 | certificate.set_key(key); 15 | certificate.set_version(1); 16 | certificate.set_activation_time((time_t) activation_time.to_unix()); 17 | certificate.set_expiration_time((time_t) expiration_time.to_unix()); 18 | uint32 serial = (uint32) 0xFFFFFFFF; 19 | certificate.set_serial(&serial, sizeof(uint32)); 20 | 21 | var error = certificate.sign(certificate, key); 22 | if(error == GnuTLS.ErrorCode.SUCCESS){ 23 | return certificate; 24 | } else { 25 | print(@"[util.TlsCertficateGenerator] Error while signing certificate ($error)\n"); 26 | return null; 27 | } 28 | } 29 | 30 | public static string? certificate_to_pem(GnuTLS.X509.Certificate certificate){ 31 | var buffer = new uint8[16384]; 32 | size_t size = buffer.length; 33 | 34 | var error = certificate.export (GnuTLS.X509.CertificateFormat.PEM, buffer, ref size); 35 | if (error == GnuTLS.ErrorCode.SUCCESS){ 36 | return (string) buffer; 37 | } else { 38 | print(@"[util.TlsCertficateGenerator] Error while encoding certificate ($error)\n"); 39 | return null; 40 | } 41 | } 42 | 43 | public static string? private_key_to_pem(GnuTLS.X509.PrivateKey key){ 44 | var buffer = new uint8[16384]; 45 | size_t size = buffer.length; 46 | 47 | var error = key.export_pkcs8 (GnuTLS.X509.CertificateFormat.PEM, "", GnuTLS.X509.PKCSEncryptFlags.PLAIN, buffer, ref size); 48 | if (error == GnuTLS.ErrorCode.SUCCESS){ 49 | return (string) buffer; 50 | } else { 51 | print(@"[util.TlsCertficateGenerator] Error while encoding private key ($error)\n"); 52 | return null; 53 | } 54 | } 55 | 56 | public static string? generate_signed_certificate_key_pair_pem(){ 57 | var key = Fossil.Util.TlsCertficateGenerator.generate_private_key(); 58 | var certificate = Fossil.Util.TlsCertficateGenerator.generate_client_certificate(key); 59 | if (certificate != null) { 60 | var certificate_pem = certificate_to_pem(certificate); 61 | var key_pem = private_key_to_pem(key); 62 | if (certificate_pem != null && key_pem != null){ 63 | return certificate_pem+key_pem; 64 | } 65 | } 66 | return null; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /tools/bulksed.lua: -------------------------------------------------------------------------------- 1 | sed = ... 2 | 3 | if not sed then 4 | print("Usage:") 5 | print(" bulksed ") 6 | print(" Handle with care and have a backup ready!") 7 | end 8 | 9 | while true do 10 | local i = io.read(); 11 | if not i then break end 12 | os.execute("sed -i '"..sed.."' '"..i.."'") 13 | end 14 | -------------------------------------------------------------------------------- /update_src_build_files: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #This script automatically finds all .vala files in the src/ folder and adds them to the src/meson.build file 4 | #If you didn't add or (re)move files you don't need this script 5 | 6 | lua update_src_build_files.lua > src/meson.build.new 7 | mv -f src/meson.build src/meson.build.old 8 | mv -f src/meson.build.new src/meson.build 9 | 10 | -------------------------------------------------------------------------------- /update_src_build_files.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua5.3 2 | 3 | files = {} 4 | 5 | p = io.popen("find src/ -name \"*.vala\" | cut -d/ -f2-") 6 | 7 | while true do 8 | local i = p:read() 9 | if not i then break end 10 | files[#files+1] = i 11 | end 12 | 13 | p:close() 14 | 15 | function compare_files(a,b) 16 | local as = not not a:find("/",1,true) 17 | local bs = not not b:find("/",1,true) 18 | if not (as == bs) then 19 | return not as 20 | end 21 | return a < b 22 | end 23 | 24 | table.sort(files,compare_files) 25 | 26 | function print_files() 27 | for _,f in pairs(files) do 28 | print("\t'"..f:gsub("'","\\'").."', #AUTOUPDATE_FILE") 29 | end 30 | end 31 | 32 | 33 | p = io.popen("cat src/meson.build | grep -v -F \"#AUTOUPDATE_FILE\"") 34 | while true do 35 | local i = p:read() 36 | if not i then break end 37 | print(i) 38 | if i:find("##_FILES_##",1,true) then 39 | print_files(); 40 | end 41 | end 42 | 43 | p:close() 44 | --------------------------------------------------------------------------------