Bombabillo is a non-web client for the terminal, supporting Gopher, Gemini and much more.
https://bombadillo.colorfield.space
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
6.3 KiB
252 lines
6.3 KiB
package main |
|
|
|
// Bombadillo is an internet client for the terminal of unix or |
|
// unix-like systems. |
|
// |
|
// Copyright (C) 2019 Brian Evans |
|
// |
|
// This program is free software: you can redistribute it and/or modify |
|
// it under the terms of the GNU General Public License as published by |
|
// the Free Software Foundation, either version 3 of the License, or |
|
// (at your option) any later version. |
|
// |
|
// This program is distributed in the hope that it will be useful, |
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
// GNU General Public License for more details. |
|
// |
|
// You should have received a copy of the GNU General Public License |
|
// along with this program. If not, see <https://www.gnu.org/licenses/>. |
|
|
|
import ( |
|
"flag" |
|
"fmt" |
|
"io/ioutil" |
|
"os" |
|
"os/signal" |
|
"path/filepath" |
|
"strconv" |
|
"strings" |
|
"syscall" |
|
"time" |
|
|
|
"tildegit.org/sloum/bombadillo/config" |
|
"tildegit.org/sloum/bombadillo/cui" |
|
"tildegit.org/sloum/bombadillo/gemini" |
|
) |
|
|
|
var version string = "2.3.3" |
|
|
|
var bombadillo *client |
|
var helplocation string = "gopher://bombadillo.colorfield.space:70/1/user-guide.map" |
|
var settings config.Config |
|
|
|
func saveConfig() error { |
|
var opts strings.Builder |
|
bkmrks := bombadillo.BookMarks.IniDump() |
|
certs := bombadillo.Certs.IniDump() |
|
|
|
opts.WriteString("\n[SETTINGS]\n") |
|
for k, v := range bombadillo.Options { |
|
opts.WriteString(k) |
|
opts.WriteRune('=') |
|
opts.WriteString(v) |
|
opts.WriteRune('\n') |
|
} |
|
|
|
opts.WriteString(bkmrks) |
|
|
|
opts.WriteString(certs) |
|
|
|
return ioutil.WriteFile(filepath.Join(bombadillo.Options["configlocation"], ".bombadillo.ini"), []byte(opts.String()), 0644) |
|
} |
|
|
|
func validateOpt(opt, val string) bool { |
|
var validOpts = map[string][]string{ |
|
"webmode": []string{"none", "gui", "lynx", "w3m", "elinks"}, |
|
"theme": []string{"normal", "inverse", "color"}, |
|
"defaultscheme": []string{"gopher", "gemini", "http", "https"}, |
|
"showimages": []string{"true", "false"}, |
|
"geminiblocks": []string{"block", "neither", "alt", "both"}, |
|
} |
|
|
|
opt = strings.ToLower(opt) |
|
val = strings.ToLower(val) |
|
|
|
if _, ok := validOpts[opt]; ok { |
|
for _, item := range validOpts[opt] { |
|
if item == val { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
if opt == "timeout" { |
|
_, err := strconv.Atoi(val) |
|
if err != nil { |
|
return false |
|
} |
|
} |
|
|
|
return true |
|
} |
|
|
|
func lowerCaseOpt(opt, val string) string { |
|
switch opt { |
|
case "webmode", "theme", "defaultscheme", "showimages", "geminiblocks": |
|
return strings.ToLower(val) |
|
default: |
|
return val |
|
} |
|
} |
|
|
|
func loadConfig() { |
|
err := os.MkdirAll(bombadillo.Options["configlocation"], 0755) |
|
if err != nil { |
|
exitMsg := fmt.Sprintf("Error creating 'configlocation' directory: %s", err.Error()) |
|
cui.Exit(3, exitMsg) |
|
} |
|
|
|
fp := filepath.Join(bombadillo.Options["configlocation"], ".bombadillo.ini") |
|
file, err := os.Open(fp) |
|
if err != nil { |
|
err = saveConfig() |
|
if err != nil { |
|
exitMsg := fmt.Sprintf("Error writing config file during bootup: %s", err.Error()) |
|
cui.Exit(4, exitMsg) |
|
} |
|
} |
|
|
|
confparser := config.NewParser(file) |
|
settings, _ = confparser.Parse() |
|
_ = file.Close() |
|
for _, v := range settings.Settings { |
|
lowerkey := strings.ToLower(v.Key) |
|
if lowerkey == "configlocation" { |
|
// Read only |
|
continue |
|
} |
|
|
|
if _, ok := bombadillo.Options[lowerkey]; ok { |
|
if validateOpt(lowerkey, v.Value) { |
|
bombadillo.Options[lowerkey] = v.Value |
|
if lowerkey == "geminiblocks" { |
|
gemini.BlockBehavior = v.Value |
|
} else if lowerkey == "timeout" { |
|
updateTimeouts(v.Value) |
|
} |
|
} else { |
|
bombadillo.Options[lowerkey] = defaultOptions[lowerkey] |
|
} |
|
} |
|
} |
|
|
|
for i, v := range settings.Bookmarks.Titles { |
|
_, _ = bombadillo.BookMarks.Add([]string{v, settings.Bookmarks.Links[i]}) |
|
} |
|
|
|
for _, v := range settings.Certs { |
|
// Remove expired certs |
|
vals := strings.SplitN(v.Value, "|", -1) |
|
if len(vals) < 2 { |
|
continue |
|
} |
|
now := time.Now() |
|
ts, err := strconv.ParseInt(vals[1], 10, 64) |
|
if err != nil || now.Unix() > ts { |
|
continue |
|
} |
|
// Satisfied that the cert is not expired |
|
// or malformed: add to the current client |
|
// instance |
|
bombadillo.Certs.Add(v.Key, vals[0], ts) |
|
} |
|
} |
|
|
|
func initClient() { |
|
bombadillo = MakeClient(" ((( Bombadillo ))) ") |
|
loadConfig() |
|
} |
|
|
|
// In the event of specific signals, ensure the display is shown correctly. |
|
// Accepts a signal, blocking until it is received. Once not blocked, corrects |
|
// terminal display settings as appropriate for that signal. Loops |
|
// indefinitely, does not return. |
|
func handleSignals(c <-chan os.Signal) { |
|
for { |
|
switch <-c { |
|
case syscall.SIGTSTP: |
|
cui.CleanupTerm() |
|
_ = syscall.Kill(syscall.Getpid(), syscall.SIGSTOP) |
|
case syscall.SIGCONT: |
|
cui.InitTerm() |
|
bombadillo.Draw() |
|
case syscall.SIGINT: |
|
cui.Exit(130, "") |
|
} |
|
} |
|
} |
|
|
|
//printHelp produces a nice display message when the --help flag is used |
|
func printHelp() { |
|
art := `Bombadillo - a non-web browser |
|
|
|
Syntax: bombadillo [options] [url] |
|
|
|
Examples: bombadillo gopher://bombadillo.colorfield.space |
|
bombadillo -t |
|
bombadillo -v |
|
|
|
Options: |
|
` |
|
_, _ = fmt.Fprint(os.Stdout, art) |
|
flag.PrintDefaults() |
|
} |
|
|
|
func main() { |
|
getVersion := flag.Bool("v", false, "Display version information and exit") |
|
addTitleToXWindow := flag.Bool("t", false, "Set the window title to 'Bombadillo'. Can be used in a GUI environment, however not all terminals support this feature.") |
|
flag.Usage = printHelp |
|
flag.Parse() |
|
if *getVersion { |
|
fmt.Printf("Bombadillo %s\n", version) |
|
os.Exit(0) |
|
} |
|
args := flag.Args() |
|
|
|
cui.InitTerm() |
|
|
|
if *addTitleToXWindow { |
|
fmt.Print("\033[22;0t") // Store window title on terminal stack |
|
fmt.Print("\033]0;Bombadillo\007") // Update window title |
|
} |
|
|
|
defer cui.Exit(0, "") |
|
initClient() |
|
|
|
// watch for signals, send them to be handled |
|
c := make(chan os.Signal, 1) |
|
signal.Notify(c, syscall.SIGTSTP, syscall.SIGCONT, syscall.SIGINT) |
|
go handleSignals(c) |
|
|
|
// Start polling for terminal size changes |
|
go bombadillo.GetSize() |
|
|
|
if len(args) > 0 { |
|
// If a url was passed, move it down the line |
|
// Goroutine so keypresses can be made during |
|
// page load |
|
bombadillo.Visit(args[0]) |
|
} else { |
|
// Otherwise, load the homeurl |
|
// Goroutine so keypresses can be made during |
|
// page load |
|
bombadillo.Visit(bombadillo.Options["homeurl"]) |
|
} |
|
|
|
// Loop indefinitely on user input |
|
for { |
|
bombadillo.TakeControlInput() |
|
} |
|
}
|
|
|