Showing posts with label golang. Show all posts
Showing posts with label golang. Show all posts

Saturday, April 4, 2020

Here We Go


For a long time, I wanted to have an application to keep my personal data in place. My requirements were next:


  • It should be lightweight and minimalistic.
  • It must present protected (hidden) and open (visible) data in the single set.
  • It should make the process of data input/edition easy, but still protects this data from an accidental unintentional change.
And here let me show you the one which I eventually built.



This application is written in Go. It is not a real Windows GUI but rather TUI and it uses an excellent Go package "rivo/tview" as the foundation. It looks and feels less like modern Windows commercial programs, but more like old fashioned PC Dos application.  Still, it does whatever expected in an efficient and straightforward way.

The terminal screen consists of two areas:

  • Top menu
  • Table with User Records

The application starts with a single argument: the path to the file with data storage. Storage is encoded and password protected. If such storage does not exist, the application will ask you to enter the new password and will create new storage.
At the start, the application puts focus on the top menu. Hitting "Enter", while button "Select" is in focus, move the focus to the table. To put the focus back on the top menu use "Esc".To navigate through the menu or the table user must use arrow keys.
Numbers of table rows and columns are unlimited but it is unlikely somebody will use more than a hundred rows or more than 3..5 columns. Each rows contains one cell (Record Name), which is always visible, and several values. Values on each row may be either all visible or all hidden.
The application supports four modes:


  • Clipboard-on-Enter. If the user hits "Enter" on the selected cell its content is copied into the clipboard.
  • Clipboard-on-Select. During navigation content of the selected cell is copied into the clipboard.
  • Visible-on-Enter. If the user hits "Enter" on the selected cell with hidden content it becomes visible. When the cell becomes unselected its content becomes hidden again.
  • Visible-on-Select. If the user selects the cell with hidden content it becomes visible. When the cell becomes unselected its content becomes hidden again.
The user may add new records (button "Add") or edit existing (button "Edit"). To edit existing record select record on the table, then by "Esc" go to the top menu and hit button "Edit". The record may be extended with one extra value. If there is a need to add several extra values repeat the process several times. The short clip below illustrates how the program works.




Source code is available  https://github.com/jumbleview/tspur . The project "readme" contains some additional details regarding its dependencies, platform support, how to build it and run the demo. If you will find the approach useful feel free to take this application as is or use it as a starting point for your own project.

Update 4/29/20. Optional and limited support of Git added to the project.

It would be not wise to keep a file with data only locally. Keeping it somewhere on remote storage provides data reliability and the ability to access it from different computers. Git repository looks like the obvious choice. It is assumed that a user will create a private repository with some Git provider and clone it locally. If "tspur" sees that data storage is located on the directory with working git tree it adds button "Git" to the top menu. That button may trigger a chain of git operations, namely: stages file with data, commits it, and pushes it to the remote. 


June 2024 update.  I published post about  current application state https://www.jumbleview.info/2024/06/four-years-in-usage.html

Saturday, September 3, 2016

Windows Console on Demand in Go

There are two kinds of Windows programs: Windows GUI or Console Application. There could be third case:  process, which does not have any visual representation at all and needs to be running in the background. For that case program maybe be built as Windows GUI, but it should not have any code, which creates Windows objects.  But what if  you want from application sometimes  to be silent and sometimes  to be verbose and to have a console. Possible use cases are:
  • Printing version of the utility if started with special flag (like -V)
  • Program needs complicated tune up through the  command line arguments or configuration file, but normally is  running on the background. It would be beneficially to have a choice either run it with console or run it quietly.
That could be achieved by compiling application as Windows GUI and allocating  or attaching console to the process during run-time if needed. There are a lot of examples how to do that in C/C++. When I myself looked for the solution several years ago I have found this link  helpful. Fragment of the code, which creates console may look like this.

 // taken from: http://www.codeproject.com/KB/dialog/ConsoleAdapter.aspx

 AllocConsole();
 int fdout = _open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
 FILE* fout = _fdopen(fdout, "w");
 *stdout = *fout;

 int fdin = _open_osfhandle((long)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
 FILE* fin = _fdopen(fdin, "r");
 *stdin = *fin;


Nowadays I found myself  programming more and more in Go. While initially Go compiler was intended for Linux only,  that is not the case anymore. Windows now is the first class citizen in the Go world. Recently I needed to create application, which may open the console if that demanded by  command line argument, but work invisibly  otherwise . To my surprise there were not that many online information how to do that in Go. I did find one answered question in StackOverflow. Alas, proposed solution did not work for me out of the box. But using it as starting point and after some googling I have found workable solution. Here is sample of Go application, which will allocate console, print some prompt, wait for keyboard input and quit.

// go build -ldflags -H=windowsgui
package main

import "fmt"
import "os"
import "syscall"

func main() {
 modkernel32 := syscall.NewLazyDLL("kernel32.dll")
 procAllocConsole := modkernel32.NewProc("AllocConsole")
 r0, r1, err0 := syscall.Syscall(procAllocConsole.Addr(), 0, 0, 0, 0)
 if r0 == 0 { // Allocation failed, probably process already has a console
  fmt.Printf("Could not allocate console: %s. Check build flags..", err0)
  os.Exit(1)
 }
 hout, err1 := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
 hin, err2 := syscall.GetStdHandle(syscall.STD_INPUT_HANDLE)
 if err1 != nil || err2 != nil { // nowhere to print the error
  os.Exit(2)
 }
 os.Stdout = os.NewFile(uintptr(hout), "/dev/stdout")
 os.Stdin = os.NewFile(uintptr(hin), "/dev/stdin")
 fmt.Printf("Hello!\nResult of console allocation: ")
 fmt.Printf("r0=%d,r1=%d,err=%s\nFor Goodbye press Enter..", r0, r1, err0)
 var s string
 fmt.Scanln(&s)
 os.Exit(0)
}
I would like to point some details about syscall.Syscall invocation for AllocConsole :
  • This function  has five arguments. It is different of what you will find in the online Go Doc . Looks like doc references Linux definition, which is not the same as Windows.
  • Second parameter here is number of arguments in dll function. For our case it is 0.
  • Function returns three variables, but only first is really meaningful. It is the result returned by AllocConsole function. Second variables always 0. Third variables  is an  error, but in cannot be analyzed in the regular Go way as far as it is never nil.  Still it could be useful: in case of success its value is different from value on failure (for example if application was build with no windowsgui flag and already has the console ) .