forked from sloum/bombadillo
Compare commits
1 Commits
master
...
case-insen
Author | SHA1 | Date |
---|---|---|
sloum | cb55293bdb |
10
Makefile
10
Makefile
|
@ -14,7 +14,7 @@ test : GOCMD := go1.11.13
|
|||
BUILD_TIME := ${shell date "+%Y-%m-%dT%H:%M%z"}
|
||||
|
||||
# If VERSION is empty or not defined use the contents of the VERSION file
|
||||
VERSION := ${shell git describe --exact-match 2> /dev/null}
|
||||
VERSION := ${shell git describe --tags 2> /dev/null}
|
||||
ifndef VERSION
|
||||
VERSION := ${shell cat ./VERSION}
|
||||
endif
|
||||
|
@ -57,7 +57,6 @@ install-bin: build
|
|||
clean:
|
||||
${GOCMD} clean
|
||||
rm -f ./bombadillo.1.gz 2> /dev/null
|
||||
rm -f ./${BINARY}_* 2> /dev/null
|
||||
|
||||
.PHONY: uninstall
|
||||
uninstall: clean
|
||||
|
@ -67,13 +66,6 @@ uninstall: clean
|
|||
rm -f ${DESTDIR}${DATAROOTDIR}/pixmaps/bombadillo-icon.png
|
||||
-update-desktop-database 2> /dev/null
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
GOOS=linux GOARCH=amd64 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_64
|
||||
GOOS=linux GOARCH=arm ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_arm
|
||||
GOOS=linux GOARCH=386 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_linux_32
|
||||
GOOS=darwin GOARCH=amd64 ${GOCMD} build ${LDFLAGS} -o ${BINARY}_darwin_64
|
||||
|
||||
|
||||
.PHONY: test
|
||||
test: clean build
|
||||
|
|
|
@ -27,7 +27,6 @@ These instructions will get a copy of the project up and running on your local m
|
|||
### Prerequisites
|
||||
|
||||
You will need to have [Go](https://golang.org/) version >= 1.11.
|
||||
To use the Makefile you will need a make that is GNU Make compatible (sorry BSD folks)
|
||||
|
||||
### Building, Installing, Uninstalling
|
||||
|
||||
|
|
13
bombadillo.1
13
bombadillo.1
|
@ -59,7 +59,7 @@ Displaying web content directly in \fBbombadillo\fP requires lynx, w3m or elinks
|
|||
These commands work as a single keypress anytime \fBbombadillo\fP is not taking in a line based command or when the user is being prompted for action. This is the default command mode of \fBbombadillo\fP.
|
||||
.TP
|
||||
.B
|
||||
b, h
|
||||
b
|
||||
Navigate back one place in your document history.
|
||||
.TP
|
||||
.B
|
||||
|
@ -71,7 +71,7 @@ d
|
|||
Scroll down an amount corresponding to 75% of your terminal window height in the current document.
|
||||
.TP
|
||||
.B
|
||||
f, l
|
||||
f
|
||||
Navigate forward one place in your document history.
|
||||
.TP
|
||||
.B
|
||||
|
@ -95,7 +95,6 @@ n
|
|||
Jump to next found text item.
|
||||
.TP
|
||||
.B
|
||||
N
|
||||
Jump to previous found text item.
|
||||
.TP
|
||||
.B
|
||||
|
@ -107,10 +106,6 @@ R
|
|||
Reload the current page (does not destroy forward history).
|
||||
.TP
|
||||
.B
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0
|
||||
Quick navigation to the first 10 links on a page. The 0 key will navigate to the link numbered '10', all other numbers navigate to their matching link number.
|
||||
.TP
|
||||
.B
|
||||
u
|
||||
Scroll up an amount corresponding to 75% of your terminal window height in the current document.
|
||||
.TP
|
||||
|
@ -233,10 +228,6 @@ defaultscheme
|
|||
The scheme that should be used when no scheme is present in a given URL. \fIgopher\fP, \fIgemini\fP, \fIhttp\fP, and \fIhttps\fP are valid values.
|
||||
.TP
|
||||
.B
|
||||
geminiblocks
|
||||
Determines how to treat preformatted text blocks in text/gemini documents. \fIblock\fP will show the contents of the block, \fIalt\fP will show any available alt text for the block, \fIboth\fP will show both the content and the alt text, and \fIneither\fP will show neither. Unlike other settings, a change to this value will require a fresh page load to see the change.
|
||||
.TP
|
||||
.B
|
||||
homeurl
|
||||
The url that \fBbombadillo\fP navigates to when the program loads or when the \fIhome\fP or \fIh\fP LINE COMMAND is issued. This should be a valid url. If a scheme/protocol is not included, gopher will be assumed.
|
||||
.TP
|
||||
|
|
110
client.go
110
client.go
|
@ -3,8 +3,8 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
@ -19,7 +19,6 @@ import (
|
|||
"tildegit.org/sloum/bombadillo/http"
|
||||
"tildegit.org/sloum/bombadillo/local"
|
||||
"tildegit.org/sloum/bombadillo/telnet"
|
||||
"tildegit.org/sloum/bombadillo/termios"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
|
@ -44,7 +43,14 @@ type client struct {
|
|||
//--------------------------------------------------\\
|
||||
|
||||
func (c *client) GetSizeOnce() {
|
||||
var w, h = termios.GetWindowSize()
|
||||
cmd := exec.Command("stty", "size")
|
||||
cmd.Stdin = os.Stdin
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
|
||||
}
|
||||
var h, w int
|
||||
_, _ = fmt.Sscan(string(out), &h, &w)
|
||||
c.Height = h
|
||||
c.Width = w
|
||||
}
|
||||
|
@ -55,7 +61,14 @@ func (c *client) GetSize() {
|
|||
c.Draw()
|
||||
|
||||
for {
|
||||
var w, h = termios.GetWindowSize()
|
||||
cmd := exec.Command("stty", "size")
|
||||
cmd.Stdin = os.Stdin
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
cui.Exit(5, "Fatal error: Unable to retrieve terminal size")
|
||||
}
|
||||
var h, w int
|
||||
_, _ = fmt.Sscan(string(out), &h, &w)
|
||||
if h != c.Height || w != c.Width {
|
||||
c.Height = h
|
||||
c.Width = w
|
||||
|
@ -122,11 +135,15 @@ func (c *client) Draw() {
|
|||
} else {
|
||||
for i := 0; i < c.Height-3; i++ {
|
||||
if i < len(pageContent) {
|
||||
screen.WriteString(pageContent[i])
|
||||
screen.WriteString("\033[0K")
|
||||
extra := 0
|
||||
escapes := re.FindAllString(pageContent[i], -1)
|
||||
for _, esc := range escapes {
|
||||
extra += len(esc)
|
||||
}
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width+extra, c.Width+extra, pageContent[i]))
|
||||
screen.WriteString("\n")
|
||||
} else {
|
||||
screen.WriteString("\033[0K")
|
||||
screen.WriteString(fmt.Sprintf("%-*.*s", c.Width, c.Width, " "))
|
||||
screen.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
@ -145,21 +162,15 @@ func (c *client) TakeControlInput() {
|
|||
input := cui.Getch()
|
||||
|
||||
switch input {
|
||||
case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0':
|
||||
if input == '0' {
|
||||
c.goToLink("10")
|
||||
} else {
|
||||
c.goToLink(string(input))
|
||||
}
|
||||
case 'j':
|
||||
case 'j', 'J':
|
||||
// scroll down one line
|
||||
c.ClearMessage()
|
||||
c.Scroll(1)
|
||||
case 'k':
|
||||
case 'k', 'K':
|
||||
// scroll up one line
|
||||
c.ClearMessage()
|
||||
c.Scroll(-1)
|
||||
case 'q':
|
||||
case 'q', 'Q':
|
||||
// quit bombadillo
|
||||
cui.Exit(0, "")
|
||||
case 'g':
|
||||
|
@ -180,7 +191,7 @@ func (c *client) TakeControlInput() {
|
|||
c.ClearMessage()
|
||||
distance := c.Height - c.Height/4
|
||||
c.Scroll(-distance)
|
||||
case 'b', 'h':
|
||||
case 'b':
|
||||
// go back
|
||||
c.ClearMessage()
|
||||
err := c.PageState.NavigateHistory(-1)
|
||||
|
@ -205,7 +216,7 @@ func (c *client) TakeControlInput() {
|
|||
// open the bookmarks browser
|
||||
c.BookMarks.ToggleOpen()
|
||||
c.Draw()
|
||||
case 'f', 'l':
|
||||
case 'f', 'F':
|
||||
// go forward
|
||||
c.ClearMessage()
|
||||
err := c.PageState.NavigateHistory(1)
|
||||
|
@ -453,8 +464,6 @@ func (c *client) doCommandAs(action string, values []string) {
|
|||
c.Options[values[0]] = lowerCaseOpt(values[0], val)
|
||||
if values[0] == "tlskey" || values[0] == "tlscertificate" {
|
||||
c.Certs.LoadCertificate(c.Options["tlscertificate"], c.Options["tlskey"])
|
||||
} else if values[0] == "geminiblocks" {
|
||||
gemini.BlockBehavior = c.Options[values[0]]
|
||||
} else if values[0] == "configlocation" {
|
||||
c.SetMessage("Cannot set READ ONLY setting 'configlocation'", true)
|
||||
c.DrawMessage()
|
||||
|
@ -661,7 +670,7 @@ func (c *client) doLinkCommand(action, target string) {
|
|||
|
||||
}
|
||||
|
||||
func (c *client) search(query, uri, question string) {
|
||||
func (c *client) search(query, url, question string) {
|
||||
var entry string
|
||||
var err error
|
||||
if query == "" {
|
||||
|
@ -685,32 +694,22 @@ func (c *client) search(query, uri, question string) {
|
|||
} else {
|
||||
entry = query
|
||||
}
|
||||
if uri == "" {
|
||||
uri = c.Options["searchengine"]
|
||||
if url == "" {
|
||||
url = c.Options["searchengine"]
|
||||
}
|
||||
u, err := MakeUrl(uri)
|
||||
u, err := MakeUrl(url)
|
||||
if err != nil {
|
||||
c.SetMessage("The search url is not valid", true)
|
||||
c.SetMessage("The search url is not a valid url", true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
var rootUrl string
|
||||
switch u.Scheme {
|
||||
case "gopher":
|
||||
if ind := strings.Index(u.Full, "\t"); ind >= 0 {
|
||||
rootUrl = u.Full[:ind]
|
||||
} else {
|
||||
rootUrl = u.Full
|
||||
}
|
||||
c.Visit(fmt.Sprintf("%s\t%s", rootUrl, entry))
|
||||
go c.Visit(fmt.Sprintf("%s\t%s", u.Full, entry))
|
||||
case "gemini":
|
||||
if ind := strings.Index(u.Full, "?"); ind >= 0 {
|
||||
rootUrl = u.Full[:ind]
|
||||
} else {
|
||||
rootUrl = u.Full
|
||||
}
|
||||
escapedEntry := url.PathEscape(entry)
|
||||
c.Visit(fmt.Sprintf("%s?%s", rootUrl, escapedEntry))
|
||||
// TODO url escape the entry variable
|
||||
escapedEntry := entry
|
||||
go c.Visit(fmt.Sprintf("%s?%s", u.Full, escapedEntry))
|
||||
case "http", "https":
|
||||
c.Visit(u.Full)
|
||||
default:
|
||||
|
@ -930,7 +929,7 @@ func (c *client) Visit(url string) {
|
|||
// +++ Begin Protocol Handlers +++
|
||||
|
||||
func (c *client) handleGopher(u Url) {
|
||||
if u.DownloadOnly || (c.Options["showimages"] == "false" && (u.Mime == "I" || u.Mime == "g")) {
|
||||
if u.DownloadOnly {
|
||||
nameSplit := strings.Split(u.Resource, "/")
|
||||
filename := nameSplit[len(nameSplit)-1]
|
||||
filename = strings.Trim(filename, " \t\r\n\v\f\a")
|
||||
|
@ -948,11 +947,6 @@ func (c *client) handleGopher(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, content, links)
|
||||
if u.Mime == "I" || u.Mime == "g" {
|
||||
pg.FileType = "image"
|
||||
} else {
|
||||
pg.FileType = "text"
|
||||
}
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
|
@ -972,13 +966,10 @@ func (c *client) handleGemini(u Url) {
|
|||
go saveConfig()
|
||||
switch capsule.Status {
|
||||
case 1:
|
||||
// Query
|
||||
c.search("", u.Full, capsule.Content)
|
||||
case 2:
|
||||
// Success
|
||||
if capsule.MimeMaj == "text" || (c.Options["showimages"] == "true" && capsule.MimeMaj == "image") {
|
||||
if capsule.MimeMaj == "text" {
|
||||
pg := MakePage(u, capsule.Content, capsule.Links)
|
||||
pg.FileType = capsule.MimeMaj
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
|
@ -993,21 +984,14 @@ func (c *client) handleGemini(u Url) {
|
|||
c.saveFileFromData(capsule.Content, filename)
|
||||
}
|
||||
case 3:
|
||||
// Redirect
|
||||
lowerRedirect := strings.ToLower(capsule.Content)
|
||||
lowerOriginal := strings.ToLower(u.Full)
|
||||
if strings.Replace(lowerRedirect, lowerOriginal, "", 1) == "/" {
|
||||
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
|
||||
c.DrawMessage()
|
||||
ch := cui.Getch()
|
||||
if ch == 'y' || ch == 'Y' {
|
||||
c.Visit(capsule.Content)
|
||||
} else {
|
||||
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
|
||||
c.SetMessage("Redirect aborted", false)
|
||||
c.DrawMessage()
|
||||
ch := cui.Getch()
|
||||
if ch == 'y' || ch == 'Y' {
|
||||
c.Visit(capsule.Content)
|
||||
} else {
|
||||
c.SetMessage("Redirect aborted", false)
|
||||
c.DrawMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1034,10 +1018,6 @@ func (c *client) handleLocal(u Url) {
|
|||
return
|
||||
}
|
||||
pg := MakePage(u, content, links)
|
||||
ext := strings.ToLower(filepath.Ext(u.Full))
|
||||
if ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".png" {
|
||||
pg.FileType = "image"
|
||||
}
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
|
|
37
cui/cui.go
37
cui/cui.go
|
@ -5,8 +5,6 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"tildegit.org/sloum/bombadillo/termios"
|
||||
)
|
||||
|
||||
var Shapes = map[string]string{
|
||||
|
@ -57,17 +55,16 @@ func Exit(exitCode int, msg string) {
|
|||
|
||||
// InitTerm sets the terminal modes appropriate for Bombadillo
|
||||
func InitTerm() {
|
||||
termios.SetCharMode()
|
||||
Tput("smcup") // use alternate screen
|
||||
Tput("rmam") // turn off line wrapping
|
||||
fmt.Print("\033[?25l") // hide cursor
|
||||
SetCharMode()
|
||||
Tput("rmam") // turn off line wrapping
|
||||
Tput("smcup") // use alternate screen
|
||||
}
|
||||
|
||||
// CleanupTerm reverts changs to terminal mode made by InitTerm
|
||||
func CleanupTerm() {
|
||||
moveCursorToward("down", 500)
|
||||
moveCursorToward("right", 500)
|
||||
termios.SetLineMode()
|
||||
SetLineMode()
|
||||
|
||||
fmt.Print("\n")
|
||||
fmt.Print("\033[?25h") // reenables cursor blinking
|
||||
|
@ -101,7 +98,7 @@ func Getch() rune {
|
|||
}
|
||||
|
||||
func GetLine(prefix string) (string, error) {
|
||||
termios.SetLineMode()
|
||||
SetLineMode()
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print(prefix)
|
||||
|
@ -110,10 +107,32 @@ func GetLine(prefix string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
termios.SetCharMode()
|
||||
SetCharMode()
|
||||
return text[:len(text)-1], nil
|
||||
}
|
||||
|
||||
func SetCharMode() {
|
||||
cmd := exec.Command("stty", "cbreak", "-echo")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Print("\033[?25l")
|
||||
}
|
||||
|
||||
func SetLineMode() {
|
||||
cmd := exec.Command("stty", "-cbreak", "echo")
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Tput(opt string) {
|
||||
cmd := exec.Command("tput", opt)
|
||||
cmd.Stdin = os.Stdin
|
||||
|
|
|
@ -45,14 +45,12 @@ var defaultOptions = map[string]string{
|
|||
// the "configlocation" as follows:
|
||||
// "configlocation": xdgConfigPath()
|
||||
|
||||
"configlocation": xdgConfigPath(),
|
||||
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
|
||||
"geminiblocks": "block", // "block", "alt", "neither", "both"
|
||||
"homeurl": "gopher://bombadillo.colorfield.space:70/1/user-guide.map",
|
||||
"savelocation": homePath(),
|
||||
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",
|
||||
"showimages": "true",
|
||||
"telnetcommand": "telnet",
|
||||
"configlocation": xdgConfigPath(),
|
||||
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
|
||||
"theme": "normal", // "normal", "inverted", "color"
|
||||
"tlscertificate": "",
|
||||
"tlskey": "",
|
||||
|
|
104
gemini/gemini.go
104
gemini/gemini.go
|
@ -6,7 +6,6 @@ import (
|
|||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -25,8 +24,6 @@ type TofuDigest struct {
|
|||
ClientCert tls.Certificate
|
||||
}
|
||||
|
||||
var BlockBehavior = "block"
|
||||
|
||||
//------------------------------------------------\\
|
||||
// + + + R E C E I V E R S + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
@ -52,8 +49,8 @@ func (t *TofuDigest) Purge(host string) error {
|
|||
return fmt.Errorf("Invalid host %q", host)
|
||||
}
|
||||
|
||||
func (t *TofuDigest) Add(host, hash string, time int64) {
|
||||
t.certs[strings.ToLower(host)] = fmt.Sprintf("%s|%d", hash, time)
|
||||
func (t *TofuDigest) Add(host, hash string) {
|
||||
t.certs[strings.ToLower(host)] = hash
|
||||
}
|
||||
|
||||
func (t *TofuDigest) Exists(host string) bool {
|
||||
|
@ -70,11 +67,12 @@ func (t *TofuDigest) Find(host string) (string, error) {
|
|||
return "", fmt.Errorf("Invalid hostname, no key saved")
|
||||
}
|
||||
|
||||
func (t *TofuDigest) Match(host, localCert string, cState *tls.ConnectionState) error {
|
||||
func (t *TofuDigest) Match(host string, cState *tls.ConnectionState) error {
|
||||
host = strings.ToLower(host)
|
||||
now := time.Now()
|
||||
|
||||
for _, cert := range cState.PeerCertificates {
|
||||
if localCert != hashCert(cert.Raw) {
|
||||
if t.certs[host] != hashCert(cert.Raw) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -120,40 +118,13 @@ func (t *TofuDigest) newCert(host string, cState *tls.ConnectionState) error {
|
|||
continue
|
||||
}
|
||||
|
||||
t.Add(host, hashCert(cert.Raw), cert.NotAfter.Unix())
|
||||
t.Add(host, hashCert(cert.Raw))
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf(reasons.String())
|
||||
}
|
||||
|
||||
func (t *TofuDigest) GetCertAndTimestamp(host string) (string, int64, error) {
|
||||
certTs, err := t.Find(host)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
certTsSplit := strings.SplitN(certTs, "|", -1)
|
||||
if len(certTsSplit) < 2 {
|
||||
_ = t.Purge(host)
|
||||
return certTsSplit[0], -1, fmt.Errorf("Invalid certstring, no delimiter")
|
||||
}
|
||||
ts, err := strconv.ParseInt(certTsSplit[1], 10, 64)
|
||||
if err != nil {
|
||||
_ = t.Purge(host)
|
||||
return certTsSplit[0], -1, err
|
||||
}
|
||||
now := time.Now()
|
||||
if ts < now.Unix() {
|
||||
// Ignore error return here since an error would indicate
|
||||
// the host does not exist and we have already checked for
|
||||
// that and the desired outcome of the action is that the
|
||||
// host will no longer exist, so we are good either way
|
||||
_ = t.Purge(host)
|
||||
return "", -1, fmt.Errorf("Expired cert")
|
||||
}
|
||||
return certTsSplit[0], ts, nil
|
||||
}
|
||||
|
||||
func (t *TofuDigest) IniDump() string {
|
||||
if len(t.certs) < 1 {
|
||||
return ""
|
||||
|
@ -205,11 +176,9 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
|
|||
return "", fmt.Errorf("Insecure, no certificates offered by server")
|
||||
}
|
||||
|
||||
localCert, localTs, err := td.GetCertAndTimestamp(host)
|
||||
|
||||
if localTs > 0 {
|
||||
if td.Exists(host) {
|
||||
// See if we have a matching cert
|
||||
err := td.Match(host, localCert, &connState)
|
||||
err := td.Match(host, &connState)
|
||||
if err != nil && err.Error() != "EXP" {
|
||||
// If there is no match and it isnt because of an expiration
|
||||
// just return the error
|
||||
|
@ -344,7 +313,8 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
|
|||
resource = "/"
|
||||
}
|
||||
currentUrl := fmt.Sprintf("gemini://%s:%s%s", host, port, resource)
|
||||
capsule.Content, capsule.Links = parseGemini(body, currentUrl)
|
||||
rootUrl := fmt.Sprintf("gemini://%s:%s", host, port)
|
||||
capsule.Content, capsule.Links = parseGemini(body, rootUrl, currentUrl)
|
||||
} else {
|
||||
capsule.Content = body
|
||||
}
|
||||
|
@ -365,27 +335,13 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func parseGemini(b, currentUrl string) (string, []string) {
|
||||
func parseGemini(b, rootUrl, currentUrl string) (string, []string) {
|
||||
splitContent := strings.Split(b, "\n")
|
||||
links := make([]string, 0, 10)
|
||||
|
||||
inPreBlock := false
|
||||
|
||||
outputIndex := 0
|
||||
for i, ln := range splitContent {
|
||||
splitContent[i] = strings.Trim(ln, "\r\n")
|
||||
isPreBlockDeclaration := strings.HasPrefix(ln, "```")
|
||||
if isPreBlockDeclaration && !inPreBlock && (BlockBehavior == "both" || BlockBehavior == "alt") {
|
||||
inPreBlock = !inPreBlock
|
||||
alt := strings.TrimSpace(ln)
|
||||
if len(alt) > 3 {
|
||||
alt = strings.TrimSpace(alt[3:])
|
||||
splitContent[outputIndex] = fmt.Sprintf("[ %s ]", alt)
|
||||
outputIndex++
|
||||
}
|
||||
} else if isPreBlockDeclaration {
|
||||
inPreBlock = !inPreBlock
|
||||
} else if len([]rune(ln)) > 3 && ln[:2] == "=>" && !inPreBlock {
|
||||
if len([]rune(ln)) > 3 && ln[:2] == "=>" {
|
||||
var link, decorator string
|
||||
subLn := strings.Trim(ln[2:], "\r\n\t \a")
|
||||
splitPoint := strings.IndexAny(subLn, " \t")
|
||||
|
@ -399,35 +355,33 @@ func parseGemini(b, currentUrl string) (string, []string) {
|
|||
}
|
||||
|
||||
if strings.Index(link, "://") < 0 {
|
||||
link, _ = handleRelativeUrl(link, currentUrl)
|
||||
link = handleRelativeUrl(link, rootUrl, currentUrl)
|
||||
}
|
||||
|
||||
links = append(links, link)
|
||||
linknum := fmt.Sprintf("[%d]", len(links))
|
||||
splitContent[outputIndex] = fmt.Sprintf("%-5s %s", linknum, decorator)
|
||||
outputIndex++
|
||||
} else {
|
||||
if inPreBlock && (BlockBehavior == "alt" || BlockBehavior == "neither") {
|
||||
continue
|
||||
}
|
||||
splitContent[outputIndex] = ln
|
||||
outputIndex++
|
||||
splitContent[i] = fmt.Sprintf("%-5s %s", linknum, decorator)
|
||||
}
|
||||
}
|
||||
return strings.Join(splitContent[:outputIndex], "\n"), links
|
||||
return strings.Join(splitContent, "\n"), links
|
||||
}
|
||||
|
||||
// handleRelativeUrl provides link completion
|
||||
func handleRelativeUrl(relLink, current string) (string, error) {
|
||||
base, err := url.Parse(current)
|
||||
if err != nil {
|
||||
return relLink, err
|
||||
func handleRelativeUrl(u, root, current string) string {
|
||||
if len(u) < 1 {
|
||||
return u
|
||||
}
|
||||
rel, err := url.Parse(relLink)
|
||||
if err != nil {
|
||||
return relLink, err
|
||||
|
||||
if u[0] == '/' {
|
||||
return fmt.Sprintf("%s%s", root, u)
|
||||
}
|
||||
return base.ResolveReference(rel).String(), nil
|
||||
|
||||
ind := strings.LastIndex(current, "/")
|
||||
if ind < 10 {
|
||||
return fmt.Sprintf("%s/%s", root, u)
|
||||
}
|
||||
|
||||
current = current[:ind+1]
|
||||
return fmt.Sprintf("%s%s", current, u)
|
||||
}
|
||||
|
||||
func hashCert(cert []byte) string {
|
||||
|
|
|
@ -131,11 +131,9 @@ func parseMap(text string) (string, []string) {
|
|||
|
||||
if len(line[0]) > 1 {
|
||||
title = line[0][1:]
|
||||
} else if len(line[0]) == 1 {
|
||||
title = ""
|
||||
} else {
|
||||
title = ""
|
||||
line[0] = "i"
|
||||
line[0] = "i "
|
||||
}
|
||||
|
||||
if len(line) < 4 || strings.HasPrefix(line[0], "i") {
|
||||
|
|
26
main.go
26
main.go
|
@ -25,14 +25,12 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"tildegit.org/sloum/bombadillo/config"
|
||||
"tildegit.org/sloum/bombadillo/cui"
|
||||
"tildegit.org/sloum/bombadillo/gemini"
|
||||
_ "tildegit.org/sloum/bombadillo/gemini"
|
||||
)
|
||||
|
||||
var version string
|
||||
|
@ -67,8 +65,6 @@ func validateOpt(opt, val string) bool {
|
|||
"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)
|
||||
|
@ -87,7 +83,7 @@ func validateOpt(opt, val string) bool {
|
|||
|
||||
func lowerCaseOpt(opt, val string) string {
|
||||
switch opt {
|
||||
case "webmode", "theme", "defaultscheme", "showimages", "geminiblocks":
|
||||
case "webmode", "theme", "defaultscheme":
|
||||
return strings.ToLower(val)
|
||||
default:
|
||||
return val
|
||||
|
@ -124,9 +120,6 @@ func loadConfig() {
|
|||
if _, ok := bombadillo.Options[lowerkey]; ok {
|
||||
if validateOpt(lowerkey, v.Value) {
|
||||
bombadillo.Options[lowerkey] = v.Value
|
||||
if lowerkey == "geminiblocks" {
|
||||
gemini.BlockBehavior = v.Value
|
||||
}
|
||||
} else {
|
||||
bombadillo.Options[lowerkey] = defaultOptions[lowerkey]
|
||||
}
|
||||
|
@ -138,20 +131,7 @@ func loadConfig() {
|
|||
}
|
||||
|
||||
for _, v := range settings.Certs {
|
||||
// Remove expired certs
|
||||
vals := strings.SplitN(v.Value, "|", -1)
|
||||
if len(vals) < 2 {
|
||||
continue
|
||||
}
|
||||
ts, err := strconv.ParseInt(vals[1], 10, 64)
|
||||
now := time.Now()
|
||||
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)
|
||||
bombadillo.Certs.Add(v.Key, v.Value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
44
page.go
44
page.go
|
@ -3,8 +3,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tildegit.org/sloum/bombadillo/tdiv"
|
||||
)
|
||||
|
||||
//------------------------------------------------\\
|
||||
|
@ -23,9 +21,6 @@ type Page struct {
|
|||
FoundLinkLines []int
|
||||
SearchTerm string
|
||||
SearchIndex int
|
||||
FileType string
|
||||
WrapWidth int
|
||||
Color bool
|
||||
}
|
||||
|
||||
//------------------------------------------------\\
|
||||
|
@ -52,27 +47,12 @@ func (p *Page) ScrollPositionRange(termHeight int) (int, int) {
|
|||
return p.ScrollPosition, end
|
||||
}
|
||||
|
||||
func (p *Page) RenderImage(width int) {
|
||||
w := (width - 5) * 2
|
||||
if w > 300 {
|
||||
w = 300
|
||||
}
|
||||
p.WrappedContent = tdiv.Render([]byte(p.RawContent), w)
|
||||
p.WrapWidth = width
|
||||
}
|
||||
|
||||
// WrapContent performs a hard wrap to the requested
|
||||
// width and updates the WrappedContent
|
||||
// of the Page struct width a string slice
|
||||
// of the wrapped data
|
||||
func (p *Page) WrapContent(width int, color bool) {
|
||||
if p.FileType == "image" {
|
||||
p.RenderImage(width)
|
||||
return
|
||||
}
|
||||
width = min(width, 100)
|
||||
counter := 0
|
||||
spacer := " "
|
||||
var content strings.Builder
|
||||
var esc strings.Builder
|
||||
escape := false
|
||||
|
@ -126,6 +106,7 @@ func (p *Page) WrapContent(width int, color bool) {
|
|||
content.WriteRune('\n')
|
||||
counter = 0
|
||||
if p.Location.Mime == "1" {
|
||||
spacer := " "
|
||||
content.WriteString(spacer)
|
||||
counter += len(spacer)
|
||||
}
|
||||
|
@ -135,8 +116,6 @@ func (p *Page) WrapContent(width int, color bool) {
|
|||
}
|
||||
|
||||
p.WrappedContent = strings.Split(content.String(), "\n")
|
||||
p.WrapWidth = width
|
||||
p.Color = color
|
||||
p.HighlightFoundText()
|
||||
}
|
||||
|
||||
|
@ -144,8 +123,9 @@ func (p *Page) HighlightFoundText() {
|
|||
if p.SearchTerm == "" {
|
||||
return
|
||||
}
|
||||
lowS := strings.ToLower(p.SearchTerm)
|
||||
for i, ln := range p.WrappedContent {
|
||||
found := strings.Index(ln, p.SearchTerm)
|
||||
found := strings.Index(strings.ToLower(ln), lowS)
|
||||
if found < 0 {
|
||||
continue
|
||||
}
|
||||
|
@ -153,7 +133,8 @@ func (p *Page) HighlightFoundText() {
|
|||
if bombadillo.Options["theme"] == "inverse" {
|
||||
format = "\033[27m%s\033[7m"
|
||||
}
|
||||
ln = strings.Replace(ln, p.SearchTerm, fmt.Sprintf(format, p.SearchTerm), -1)
|
||||
foundS := ln[found:found+len(lowS)]
|
||||
ln = strings.Replace(ln, foundS, fmt.Sprintf(format, foundS), -1)
|
||||
p.WrappedContent[i] = ln
|
||||
}
|
||||
}
|
||||
|
@ -169,12 +150,14 @@ func (p *Page) FindText() {
|
|||
if bombadillo.Options["theme"] == "inverse" {
|
||||
format = "\033[27m%s\033[7m"
|
||||
}
|
||||
lowS := strings.ToLower(s)
|
||||
for i, ln := range p.WrappedContent {
|
||||
found := strings.Index(ln, s)
|
||||
found := strings.Index(strings.ToLower(ln), lowS)
|
||||
if found < 0 {
|
||||
continue
|
||||
}
|
||||
ln = strings.Replace(ln, s, fmt.Sprintf(format, s), -1)
|
||||
foundS := ln[found:found+len(lowS)]
|
||||
ln = strings.Replace(ln, foundS, fmt.Sprintf(format, foundS), -1)
|
||||
p.WrappedContent[i] = ln
|
||||
p.FoundLinkLines = append(p.FoundLinkLines, i)
|
||||
}
|
||||
|
@ -186,13 +169,6 @@ func (p *Page) FindText() {
|
|||
|
||||
// MakePage returns a Page struct with default values
|
||||
func MakePage(url Url, content string, links []string) Page {
|
||||
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0, "", 40, false}
|
||||
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0}
|
||||
return p
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
6
pages.go
6
pages.go
|
@ -66,11 +66,7 @@ func (p *Pages) Render(termHeight, termWidth int, color bool) []string {
|
|||
}
|
||||
pos := p.History[p.Position].ScrollPosition
|
||||
prev := len(p.History[p.Position].WrappedContent)
|
||||
|
||||
if termWidth != p.History[p.Position].WrapWidth || p.History[p.Position].Color != color {
|
||||
p.History[p.Position].WrapContent(termWidth, color)
|
||||
}
|
||||
|
||||
p.History[p.Position].WrapContent(termWidth, color)
|
||||
now := len(p.History[p.Position].WrappedContent)
|
||||
if prev > now {
|
||||
diff := prev - now
|
||||
|
|
290
tdiv/tdiv.go
290
tdiv/tdiv.go
|
@ -1,290 +0,0 @@
|
|||
package tdiv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getBraille(pattern string) (rune, error) {
|
||||
switch pattern {
|
||||
case "000000":
|
||||
return ' ', nil
|
||||
case "100000":
|
||||
return '⠁', nil
|
||||
case "001000":
|
||||
return '⠂', nil
|
||||
case "101000":
|
||||
return '⠃', nil
|
||||
case "000010":
|
||||
return '⠄', nil
|
||||
case "100010":
|
||||
return '⠅', nil
|
||||
case "001010":
|
||||
return '⠆', nil
|
||||
case "101010":
|
||||
return '⠇', nil
|
||||
case "010000":
|
||||
return '⠈', nil
|
||||
case "110000":
|
||||
return '⠉', nil
|
||||
case "011000":
|
||||
return '⠊', nil
|
||||
case "111000":
|
||||
return '⠋', nil
|
||||
case "010010":
|
||||
return '⠌', nil
|
||||
case "110010":
|
||||
return '⠍', nil
|
||||
case "011010":
|
||||
return '⠎', nil
|
||||
case "111010":
|
||||
return '⠏', nil
|
||||
case "000100":
|
||||
return '⠐', nil
|
||||
case "100100":
|
||||
return '⠑', nil
|
||||
case "001100":
|
||||
return '⠒', nil
|
||||
case "101100":
|
||||
return '⠓', nil
|
||||
case "000110":
|
||||
return '⠔', nil
|
||||
case "100110":
|
||||
return '⠕', nil
|
||||
case "001110":
|
||||
return '⠖', nil
|
||||
case "101110":
|
||||
return '⠗', nil
|
||||
case "010100":
|
||||
return '⠘', nil
|
||||
case "110100":
|
||||
return '⠙', nil
|
||||
case "011100":
|
||||
return '⠚', nil
|
||||
case "111100":
|
||||
return '⠛', nil
|
||||
case "010110":
|
||||
return '⠜', nil
|
||||
case "110110":
|
||||
return '⠝', nil
|
||||
case "011110":
|
||||
return '⠞', nil
|
||||
case "111110":
|
||||
return '⠟', nil
|
||||
case "000001":
|
||||
return '⠠', nil
|
||||
case "100001":
|
||||
return '⠡', nil
|
||||
case "001001":
|
||||
return '⠢', nil
|
||||
case "101001":
|
||||
return '⠣', nil
|
||||
case "000011":
|
||||
return '⠤', nil
|
||||
case "100011":
|
||||
return '⠥', nil
|
||||
case "001011":
|
||||
return '⠦', nil
|
||||
case "101011":
|
||||
return '⠧', nil
|
||||
case "010001":
|
||||
return '⠨', nil
|
||||
case "110001":
|
||||
return '⠩', nil
|
||||
case "011001":
|
||||
return '⠪', nil
|
||||
case "111001":
|
||||
return '⠫', nil
|
||||
case "010011":
|
||||
return '⠬', nil
|
||||
case "110011":
|
||||
return '⠭', nil
|
||||
case "011011":
|
||||
return '⠮', nil
|
||||
case "111011":
|
||||
return '⠯', nil
|
||||
case "000101":
|
||||
return '⠰', nil
|
||||
case "100101":
|
||||
return '⠱', nil
|
||||
case "001101":
|
||||
return '⠲', nil
|
||||
case "101101":
|
||||
return '⠳', nil
|
||||
case "000111":
|
||||
return '⠴', nil
|
||||
case "100111":
|
||||
return '⠵', nil
|
||||
case "001111":
|
||||
return '⠶', nil
|
||||
case "101111":
|
||||
return '⠷', nil
|
||||
case "010101":
|
||||
return '⠸', nil
|
||||
case "110101":
|
||||
return '⠹', nil
|
||||
case "011101":
|
||||
return '⠺', nil
|
||||
case "111101":
|
||||
return '⠻', nil
|
||||
case "010111":
|
||||
return '⠼', nil
|
||||
case "110111":
|
||||
return '⠽', nil
|
||||
case "011111":
|
||||
return '⠾', nil
|
||||
case "111111":
|
||||
return '⠿', nil
|
||||
default:
|
||||
return '!', fmt.Errorf("Invalid character entry")
|
||||
}
|
||||
}
|
||||
|
||||
// scaleImage loads and scales an image and returns a 2d pixel-int slice
|
||||
//
|
||||
// Adapted from:
|
||||
// http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/
|
||||
func scaleImage(file io.Reader, newWidth int) (int, int, [][]int, error) {
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
width, height := bounds.Max.X, bounds.Max.Y
|
||||
newHeight := int(float64(newWidth) * (float64(height) / float64(width)))
|
||||
|
||||
out := make([][]int, newHeight)
|
||||
for i := range out {
|
||||
out[i] = make([]int, newWidth)
|
||||
}
|
||||
|
||||
xRatio := float64(width) / float64(newWidth)
|
||||
yRatio := float64(height) / float64(newHeight)
|
||||
var px, py int
|
||||
for i := 0; i < newHeight; i++ {
|
||||
for j := 0; j < newWidth; j++ {
|
||||
px = int(float64(j) * xRatio)
|
||||
py = int(float64(i) * yRatio)
|
||||
out[i][j] = rgbaToGray(img.At(px, py).RGBA())
|
||||
}
|
||||
}
|
||||
return newWidth, newHeight, out, nil
|
||||
}
|
||||
|
||||
// Get the bi-dimensional pixel array
|
||||
func getPixels(file io.Reader) (int, int, [][]int, error) {
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
width, height := bounds.Max.X, bounds.Max.Y
|
||||
|
||||
var pixels [][]int
|
||||
for y := 0; y < height; y++ {
|
||||
var row []int
|
||||
for x := 0; x < width; x++ {
|
||||
row = append(row, rgbaToGray(img.At(x, y).RGBA()))
|
||||
}
|
||||
pixels = append(pixels, row)
|
||||
}
|
||||
|
||||
return width, height, pixels, nil
|
||||
}
|
||||
|
||||
func errorDither(w, h int, p [][]int) [][]int {
|
||||
mv := [4][2]int{
|
||||
[2]int{0, 1},
|
||||
[2]int{1, 1},
|
||||
[2]int{1, 0},
|
||||
[2]int{1, -1},
|
||||
}
|
||||
per := [4]float64{0.4375, 0.0625, 0.3125, 0.1875}
|
||||
var res, diff int
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
cur := p[y][x]
|
||||
if cur > 128 {
|
||||
res = 1
|
||||
diff = -(255 - cur)
|
||||
} else {
|
||||
res = 0
|
||||
diff = cur // TODO see why this was abs() in the py version
|
||||
}
|
||||
for i, v := range mv {
|
||||
if y+v[0] >= h || x+v[1] >= w || x+v[1] <= 0 {
|
||||
continue
|
||||
}
|
||||
px := p[y+v[0]][x+v[1]]
|
||||
px = int(float64(diff)*per[i] + float64(px))
|
||||
if px < 0 {
|
||||
px = 0
|
||||
} else if px > 255 {
|
||||
px = 255
|
||||
}
|
||||
p[y+v[0]][x+v[1]] = px
|
||||
p[y][x] = res
|
||||
}
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func toBraille(p [][]int) []rune {
|
||||
w := len(p[0]) // TODO this is unsafe
|
||||
h := len(p)
|
||||
rows := h / 3
|
||||
cols := w / 2
|
||||
out := make([]rune, rows*(cols+1))
|
||||
counter := 0
|
||||
for y := 0; y < h-3; y += 4 {
|
||||
for x := 0; x < w-1; x += 2 {
|
||||
str := fmt.Sprintf(
|
||||
"%d%d%d%d%d%d",
|
||||
p[y][x], p[y][x+1],
|
||||
p[y+1][x], p[y+1][x+1],
|
||||
p[y+2][x], p[y+2][x+1])
|
||||
b, err := getBraille(str)
|
||||
if err != nil {
|
||||
out[counter] = ' '
|
||||
} else {
|
||||
out[counter] = b
|
||||
}
|
||||
counter++
|
||||
}
|
||||
out[counter] = '\n'
|
||||
counter++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func rgbaToGray(r uint32, g uint32, b uint32, a uint32) int {
|
||||
rf := float64(r/257) * 0.92126
|
||||
gf := float64(g/257) * 0.97152
|
||||
bf := float64(b/257) * 0.90722
|
||||
grey := int((rf + gf + bf) / 3)
|
||||
return grey
|
||||
}
|
||||
|
||||
func Render(in []byte, width int) []string {
|
||||
image.RegisterFormat("jpeg", "jpeg", jpeg.Decode, jpeg.DecodeConfig)
|
||||
image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig)
|
||||
image.RegisterFormat("gif", "gif", gif.Decode, gif.DecodeConfig)
|
||||
w, h, p, err := scaleImage(bytes.NewReader(in), width)
|
||||
|
||||
if err != nil {
|
||||
return []string{"Unable to render image.", "Please download using:", "", " :w ."}
|
||||
}
|
||||
px := errorDither(w, h, p)
|
||||
b := toBraille(px)
|
||||
out := strings.SplitN(string(b), "\n", -1)
|
||||
return out
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// +build linux
|
||||
|
||||
package termios
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermiosIoctl = syscall.TCGETS
|
||||
setTermiosIoctl = syscall.TCSETS
|
||||
)
|
|
@ -1,10 +0,0 @@
|
|||
// +build !linux
|
||||
|
||||
package termios
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermiosIoctl = syscall.TIOCGETA
|
||||
setTermiosIoctl = syscall.TIOCSETAF
|
||||
)
|
|
@ -1,60 +0,0 @@
|
|||
package termios
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type winsize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
Xpixel uint16
|
||||
Ypixel uint16
|
||||
}
|
||||
|
||||
var fd = os.Stdin.Fd()
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetWindowSize() (int, int) {
|
||||
var value winsize
|
||||
ioctl(fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&value)))
|
||||
return int(value.Col), int(value.Row)
|
||||
}
|
||||
|
||||
func getTermios() syscall.Termios {
|
||||
var value syscall.Termios
|
||||
err := ioctl(fd, getTermiosIoctl, uintptr(unsafe.Pointer(&value)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func setTermios(termios syscall.Termios) {
|
||||
err := ioctl(fd, setTermiosIoctl, uintptr(unsafe.Pointer(&termios)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.KeepAlive(termios)
|
||||
}
|
||||
|
||||
func SetCharMode() {
|
||||
t := getTermios()
|
||||
t.Lflag = t.Lflag ^ syscall.ICANON
|
||||
t.Lflag = t.Lflag ^ syscall.ECHO
|
||||
setTermios(t)
|
||||
}
|
||||
|
||||
func SetLineMode() {
|
||||
var t = getTermios()
|
||||
t.Lflag = t.Lflag | (syscall.ICANON | syscall.ECHO)
|
||||
setTermios(t)
|
||||
}
|
Loading…
Reference in New Issue