Go TUI programming using TCell API - go

I am trying to learn TUI programming in Go using the TCell API.
It is a simple app that print word "hello". However, when I run the program below, nothing happens. Please tell me what I am doing wrong.
package main
import (
"fmt"
"github.com/gdamore/tcell"
"os"
)
func main() {
scn, err := tcell.NewScreen()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
hhh := []rune("hello")
scn.SetContent(10, 10, rune(' '), hhh, tcell.StyleDefault)
scn.Show()
}

The creator of this api (https://github.com/gdamore/tcell.git) provided the solution. Here is his respond:
There are three potential issues.
First, you need to initialize the screen. Call scn.Init() after creating the screen.
The second is that your call to SetContent is misguided. The string you are passing is to accommodate combining characters. Instead you need to call SetContent 5 times (one for each letter of "hello") with a different offset, and the appropriate letter of "hello". You probably want to just pass "" for the 4th argument (the string), since none of this is combining characters..
The third problem is that your program just exits. On most terminals this will cause the reset of the terminal to occur, losing your output. (On xterm, for example, tcell uses the alternate screen buffer by default, which leads to exit causing the contents of that screen to be lost, when it switches back to the primary screen buffer at program termination.) The simplest way to prove this is to add a time.Sleep(time.Second * 10) or similar as the last line of your program.
Here is the modified code:
import (
"fmt"
"github.com/gdamore/tcell"
"github.com/gdamore/tcell/encoding"
"os"
"time"
)
func main() {
encoding.Register()
scn, err := tcell.NewScreen()
scn.Init()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
scn.Clear()
scn.SetContent(10, 10, rune('h'), []rune(""), tcell.StyleDefault)
scn.Show()
time.Sleep(time.Second * 2)
}
Hope this help.

Related

How can I scan a rune?

So far, I haven't been able to print a rune by scanning it with fmt.Scan and printing it with fmt.Print. This is the vary basic code I'm working on:
package main
import "fmt"
func main() {
var c rune
fmt.Scan(&c)
fmt.Printf("%c", c)
}
But it doesn't work, in fact, Printf doesn't produce any output. Instead, by manually assigning a char to my variable c (like var c rune = 'a', without using fmt.Scan), I'm able to print the wanted rune. How can I scan a rune?
As we know Scan return n and err so please check for error under Scan statement as follows
n, err := fmt.Scan(&c)
if err != nil {
fmt.Println(err)
}
It will clearly show you the error and why it was ignored.
Other than the above, please try it locally on your own laptop instead of the playground because on the playground it most of the time gives an EOF error as most of them do not support reading from the terminal.
I hope the above helps you in debugging the issue.
Other Reference:
Scanf ignores if not provided \n

How do I call a function from the main application from a plugin?

I have recently looked into Go plugins instead of manually loading .so files myself.
Basically, I have a game server application, and I want to be able to load plugins (using plugins package) when the server starts. Then, in the plugin itself, I want to be able to call exported functions that are a part of the server.
Say I have this plugin, which is compiled to example_plugin.so using go build -buildmode=plugin:
package main
import "fmt"
func init() {
fmt.Println("Hello from plugin!")
}
Then say I have this server application, which loads the plugin (and ultimately calls the "init" function under the hood):
package main
import (
"fmt"
"plugin"
)
func main() {
fmt.Println("Server started")
if _, err := plugin.Open("example_plugin.so"); err != nil {
panic(err)
}
}
// some API function that loaded plugins can call
func GetPlayers() {}
The output is:
Server started
Hello from plugin!
This works as expected, however I want to be able to call that GetPlayers function (and any other exported functions in the server application, ideally) from the plugin (and any other plugins.) I was thinking about making some sort of library consisting of interfaces containing API functions that the server implements, however I have no idea where to start. I know I will probably need to use a .a file or something similar.
For clarification, I am developing this application for use on Linux, so I am fine with a solution that only works on Linux.
Apologies if this is poorly worded, first time posting on SO.
As mentioned in the comments, there is a Lookup function. In the documentation for the module they have the following example:
// A Symbol is a pointer to a variable or function.
// For example, a plugin defined as
//
// var V int
//
// func F() { fmt.Printf("Hello, number %d\n", V) }
//
// may be loaded with the Open function and then the exported package
// symbols V and F can be accessed
package main
import (
"fmt"
"plugin"
)
func main() {
p, err := plugin.Open("plugin_name.so")
if err != nil {
panic(err)
}
v, err := p.Lookup("V")
if err != nil {
panic(err)
}
f, err := p.Lookup("F")
if err != nil {
panic(err)
}
*v.(*int) = 7
f.(func())() // prints "Hello, number 7"
}
I think the most confusing lines here are
*v.(*int) = 7
f.(func())() // prints "Hello, number 7"
The first one of them performs a type assertion to *int to assert that v is indeed a pointer to int. That is needed since Lookup returns an interface{} and in order to do anything useful with a value, you should clarify its type.
The second line performs another type assertion, this time making sure that f is a function with no arguments and no return values, after which, immediately calls it. Since function F from the original module was referencing V (which we've replaced with 7), this call will display Hello, number 7.

Why is ioutil.ReadFile() hanging after being called in a for loop?

I wrote a function that converts an image to a base64 string:
func getBase64Screenshot() string {
// Image name
imageName := "Screenshots/screenshot.png"
imageFileBytes, err := ioutil.ReadFile(imageName)
handleError(err)
// Converts file to base64 string
encoded := base64.StdEncoding.EncodeToString(imageFileBytes)
return encoded
}
The above function is called in a for loop, however after some iterations of the for loop, the program just hangs at the line imageFileBytes, err := ioutil.ReadFile(imageName) (it doesn't throw an error, it just stops running and stalls).
I ran some experiments and found that if I used a smaller image, it would make it through ~5 iterations of the for loop before stalling, however if I screenshot my entire screen it would only make it through the first iteration of the for loop before stalling.
My question is this: What is causing the program to hang, and is there anything I can do to prevent it from hanging?
Thanks!
One way to debug the issue is to send a SIGABRT to your running program (when hanging) in order to causes the program to exit with a stack dump.
kill -ABRT <pid>
# or CTRL+Pause on Windows.
Then you will see where the program is hang, and if it is actually related to your function or not.
Debug your code.
Try this and let me know if this works for you too:
This works like a charm for me on Linux:
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
)
func main() {
for i := 0; i < 10; i++ {
fmt.Println(getBase64Screenshot()[:10])
}
}
func getBase64Screenshot() string {
buf, err := ioutil.ReadFile(os.Args[0])
if err != nil {
log.Fatal(err)
}
encoded := base64.StdEncoding.EncodeToString(buf)
return encoded
}
Output:
time go run main.go
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
f0VMRgIBAQ
real 0m0.340s
user 0m0.369s
sys 0m0.128s
Hey guys I was able to fix the issue.
You see, initially the function getBase64Screenshot() that I wrote above was being called every 10 seconds inside of a goroutine.
To fix the issue I refactored my code to eliminate the goroutine and instead call getBase64Screenshot() inside of an infinite loop:
for {
getBase64Screenshot()
time.Sleep(timedelayMilliseconds * time.Millisecond)
}
I've tested the above code by running it for over 30 minutes with very large screenshots (>1MB in size), and it has never crashed or stalled.
Though I've fixed the issue I encountered, I haven't been able to figure out what exactly caused it in the first place. I welcome any theories as to why my original code above stalled.

Reading from stdout pipe once ready in golang

I'm facing with a weird golang issue. The following code will clarify:
package main
import (
"os/exec"
"io"
"fmt"
"os"
)
var (
pw io.WriteCloser
pr io.ReadCloser
)
func main() {
term := exec.Command("/bin/sh")
// Get stdin writer pipe
pw, _ = term.StdinPipe()
pr, _ = term.StdoutPipe()
term.Start()
run("cd ~")
pwd := run("pwd");
// Do something with pwd output
...
term.Wait()
}
func run(c string) string {
io.WriteString(pw, fmt.Sprintln(c))
buf := make([]byte, 32 * 1024)
pr.Read(buf)
return string(buf)
}
I'd like to run some commands in a shell env and read their output. There's no problem on write/run command but it seems that there're some limitations while reading:
you can't know if a command doesn't output anything or not;
there's no way to check if stdout is ready to be read or not.
The pr.Read(dest) method will block the code flow until something is read from stdout. As said, the goal is to read sequentially (without using a go routine and/or an infinite loop). This means that if we send a cd command the func end is never reached.
Setting the non-block flag through unix.SetNonblock on stdout file descriptor seems to solve the above issue but you can't know prior if it's ready or not and an error saying "resource temporary not available" is returned from .Read call.
As Cerise Limón mentioned go functions whould be the way to go here, since these sorts of interactive scripting exercises are traditionally done with expect.
You can wrap the the parrellel execution into a library to it might still look like sequencial code, so this might be helpful: https://github.com/ThomasRooney/gexpect
From the readme:
child, err := gexpect.Spawn("python")
if err != nil { panic(err) }
child.Expect(">>>")
child.SendLine("print 'Hello World'")
child.Interact()
child.Close()

Making a full screen Terminal application with Go

I'm trying to build a full screen terminal application. I'm using Go as my language of choice. I've figured out how to read from os.Stdin, but I'm unclear on how to clear the terminal window and manipulate the cursor position. I also want to capture the terminal input without it being printed (echoed back).
My questions are:
How can I effectively clear and print to the terminal with column/row coordinates?
How do I stop the terminal from printing keys pressed
My intent:
I want to create a full screen terminal application that renders it's own UI and handles input internally (hot keys/navigation/etc...).
If there are any libraries that cover this sort of use case please feel free to suggest them.
The easiest way to clear the terminal and set position is via ansi escape codes. However, this may not be the ideal way as variation in terminals may come back to bite you.
fmt.Print("\033[2J") //Clear screen
fmt.Printf("\033[%d;%dH", line, col) // Set cursor position
A better alternative would be to use a library like goncurses or termbox-go (credit: second is from Tim Cooper's comment).
With such a library you can do things like this:
import (
gc "code.google.com/p/goncurses"
)
func main() {
s, err := gc.Init()
if err != nil {
panic(err)
}
defer gc.End()
s.Move(5, 2)
s.Println("Hello")
s.GetChar()
}
Code above copied from Rosetta Code
As of December 2019, I would recommend using rivo/tview library.
(goncurses mentioned by #vastlysuperiorman has not been updated since June 2019 and termbox-go is explicitly declared unmaintained).
Here's the "hello world" app, taken from the project's README (reformatted for readability):
package main
import (
"github.com/rivo/tview"
)
func main() {
box := tview.NewBox().
SetBorder(true).
SetTitle("Hello, world!")
if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {
panic(err)
}
}
tview provides screenshots and example code as well as the standard godoc reference.
To stop the terminal from printing keys pressed you can use the below code:
import (
"fmt"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func main(){
fmt.Print("Enter Value: ")
byteInput, _ := terminal.ReadPassword(int(syscall.Stdin))
input:= string(byteInput)
fmt.Println() // it's necessary to add a new line after user's input
fmt.Printf("Your input is '%s'", input)
}

Resources