Add password modal and file path autocompletion
This commit is contained in:
		@@ -21,6 +21,7 @@ package ui
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unicode"
 | 
			
		||||
@@ -232,21 +233,57 @@ func cmdResetSession(cmd *Command) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autocompleteFile(cmd *CommandAutocomplete) (completions []string, newText string) {
 | 
			
		||||
	// TODO implement
 | 
			
		||||
	return []string{}, ""
 | 
			
		||||
	inputPath, err := filepath.Abs(cmd.RawArgs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var searchNamePrefix, searchDir string
 | 
			
		||||
	if strings.HasSuffix(cmd.RawArgs, "/") {
 | 
			
		||||
		searchDir = inputPath
 | 
			
		||||
	} else {
 | 
			
		||||
		searchNamePrefix = filepath.Base(inputPath)
 | 
			
		||||
		searchDir = filepath.Dir(inputPath)
 | 
			
		||||
	}
 | 
			
		||||
	files, err := ioutil.ReadDir(searchDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, file := range files {
 | 
			
		||||
		name := file.Name()
 | 
			
		||||
		if !strings.HasPrefix(name, searchNamePrefix) || (name[0] == '.' && searchNamePrefix == "") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		fullPath := filepath.Join(searchDir, name)
 | 
			
		||||
		if file.IsDir() {
 | 
			
		||||
			fullPath += "/"
 | 
			
		||||
		}
 | 
			
		||||
		completions = append(completions, fullPath)
 | 
			
		||||
	}
 | 
			
		||||
	if len(completions) == 1 {
 | 
			
		||||
		newText = fmt.Sprintf("/%s %s", cmd.OrigCommand, completions[0])
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO add dialog for asking passphrase
 | 
			
		||||
const extremelyTemporaryHardcodedPassphrase = "gomuks"
 | 
			
		||||
 | 
			
		||||
func cmdImportKeys(cmd *Command) {
 | 
			
		||||
	data, err := ioutil.ReadFile(cmd.RawArgs)
 | 
			
		||||
	path, err := filepath.Abs(cmd.RawArgs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to read %s: %v", cmd.RawArgs, err)
 | 
			
		||||
		cmd.Reply("Failed to get absolute path: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	data, err := ioutil.ReadFile(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to read %s: %v", path, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key import", false)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		cmd.Reply("Passphrase entry cancelled")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	mach := cmd.Matrix.Crypto().(*crypto.OlmMachine)
 | 
			
		||||
	imported, total, err := mach.ImportKeys(extremelyTemporaryHardcodedPassphrase, data)
 | 
			
		||||
	imported, total, err := mach.ImportKeys(passphrase, data)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to import sessions: %v", err)
 | 
			
		||||
	} else {
 | 
			
		||||
@@ -255,15 +292,25 @@ func cmdImportKeys(cmd *Command) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exportKeys(cmd *Command, sessions []*crypto.InboundGroupSession) {
 | 
			
		||||
	export, err := crypto.ExportKeys(extremelyTemporaryHardcodedPassphrase, sessions)
 | 
			
		||||
	path, err := filepath.Abs(cmd.RawArgs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to get absolute path: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	passphrase, ok := cmd.MainView.AskPassword("Key export", true)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		cmd.Reply("Passphrase entry cancelled")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	export, err := crypto.ExportKeys(passphrase, sessions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to export sessions: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = ioutil.WriteFile(cmd.RawArgs, export, 0400)
 | 
			
		||||
	err = ioutil.WriteFile(path, export, 0400)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		cmd.Reply("Failed to write sessions to %s: %v", cmd.RawArgs, err)
 | 
			
		||||
		cmd.Reply("Failed to write sessions to %s: %v", path, err)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd.Reply("Successfully exported %d sessions to %s", len(sessions), cmd.RawArgs)
 | 
			
		||||
		cmd.Reply("Successfully exported %d sessions to %s", len(sessions), path)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										133
									
								
								ui/password-modal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								ui/password-modal.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
// gomuks - A terminal Matrix client written in Go.
 | 
			
		||||
// Copyright (C) 2020 Tulir Asokan
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU Affero 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 Affero General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU Affero General Public License
 | 
			
		||||
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"maunium.net/go/mauview"
 | 
			
		||||
	"maunium.net/go/tcell"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type PasswordModal struct {
 | 
			
		||||
	mauview.Component
 | 
			
		||||
 | 
			
		||||
	outputChan chan string
 | 
			
		||||
	cancelChan chan struct{}
 | 
			
		||||
 | 
			
		||||
	form *mauview.Form
 | 
			
		||||
 | 
			
		||||
	text        *mauview.TextField
 | 
			
		||||
	confirmText *mauview.TextField
 | 
			
		||||
 | 
			
		||||
	input        *mauview.InputField
 | 
			
		||||
	confirmInput *mauview.InputField
 | 
			
		||||
 | 
			
		||||
	cancel *mauview.Button
 | 
			
		||||
	submit *mauview.Button
 | 
			
		||||
 | 
			
		||||
	parent *MainView
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) AskPassword(title string, isNew bool) (string, bool) {
 | 
			
		||||
	pwm := NewPasswordModal(view, title, isNew)
 | 
			
		||||
	view.ShowModal(pwm)
 | 
			
		||||
	return pwm.Wait()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewPasswordModal(parent *MainView, title string, isNew bool) *PasswordModal {
 | 
			
		||||
	pwm := &PasswordModal{
 | 
			
		||||
		parent:     parent,
 | 
			
		||||
		form:       mauview.NewForm(),
 | 
			
		||||
		outputChan: make(chan string, 1),
 | 
			
		||||
		cancelChan: make(chan struct{}, 1),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pwm.form.
 | 
			
		||||
		SetColumns([]int{1, 20, 1, 20, 1}).
 | 
			
		||||
		SetRows([]int{1, 1, 1, 0, 0, 0, 1, 1, 1})
 | 
			
		||||
 | 
			
		||||
	width := 45
 | 
			
		||||
	height := 8
 | 
			
		||||
 | 
			
		||||
	pwm.text = mauview.NewTextField()
 | 
			
		||||
	if isNew {
 | 
			
		||||
		pwm.text.SetText("Create a passphrase")
 | 
			
		||||
	} else {
 | 
			
		||||
		pwm.text.SetText("Enter the passphrase")
 | 
			
		||||
	}
 | 
			
		||||
	pwm.input = mauview.NewInputField().
 | 
			
		||||
		SetMaskCharacter('*').
 | 
			
		||||
		SetPlaceholder("correct horse battery staple")
 | 
			
		||||
	pwm.form.AddComponent(pwm.text, 1, 1, 3, 1)
 | 
			
		||||
	pwm.form.AddFormItem(pwm.input, 1, 2, 3, 1)
 | 
			
		||||
 | 
			
		||||
	if isNew {
 | 
			
		||||
		height += 3
 | 
			
		||||
		pwm.confirmInput = mauview.NewInputField().
 | 
			
		||||
			SetMaskCharacter('*').
 | 
			
		||||
			SetPlaceholder("correct horse battery staple").
 | 
			
		||||
			SetChangedFunc(pwm.HandleChange)
 | 
			
		||||
		pwm.input.SetChangedFunc(pwm.HandleChange)
 | 
			
		||||
		pwm.confirmText = mauview.NewTextField().SetText("Confirm passphrase")
 | 
			
		||||
 | 
			
		||||
		pwm.form.SetRow(3, 1).SetRow(4, 1).SetRow(5, 1)
 | 
			
		||||
		pwm.form.AddComponent(pwm.confirmText, 1, 4, 3, 1)
 | 
			
		||||
		pwm.form.AddFormItem(pwm.confirmInput, 1, 5, 3, 1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pwm.cancel = mauview.NewButton("Cancel").SetOnClick(pwm.ClickCancel)
 | 
			
		||||
	pwm.submit = mauview.NewButton("Submit").SetOnClick(pwm.ClickSubmit)
 | 
			
		||||
 | 
			
		||||
	pwm.form.AddFormItem(pwm.submit, 3, 7, 1, 1)
 | 
			
		||||
	pwm.form.AddFormItem(pwm.cancel, 1, 7, 1, 1)
 | 
			
		||||
 | 
			
		||||
	box := mauview.NewBox(pwm.form).SetTitle(title)
 | 
			
		||||
	center := mauview.Center(box, width, height).SetAlwaysFocusChild(true)
 | 
			
		||||
	center.Focus()
 | 
			
		||||
	pwm.form.FocusNextItem()
 | 
			
		||||
	pwm.Component = center
 | 
			
		||||
 | 
			
		||||
	return pwm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pwm *PasswordModal) HandleChange(_ string) {
 | 
			
		||||
	if pwm.input.GetText() == pwm.confirmInput.GetText() {
 | 
			
		||||
		pwm.submit.SetBackgroundColor(mauview.Styles.ContrastBackgroundColor)
 | 
			
		||||
	} else {
 | 
			
		||||
		pwm.submit.SetBackgroundColor(tcell.ColorDefault)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pwm *PasswordModal) ClickCancel() {
 | 
			
		||||
	pwm.parent.HideModal()
 | 
			
		||||
	pwm.cancelChan <- struct{}{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pwm *PasswordModal) ClickSubmit() {
 | 
			
		||||
	if pwm.confirmInput == nil || pwm.input.GetText() == pwm.confirmInput.GetText() {
 | 
			
		||||
		pwm.parent.HideModal()
 | 
			
		||||
		pwm.outputChan <- pwm.input.GetText()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (pwm *PasswordModal) Wait() (string, bool) {
 | 
			
		||||
	select {
 | 
			
		||||
	case result := <- pwm.outputChan:
 | 
			
		||||
		return result, true
 | 
			
		||||
	case <- pwm.cancelChan:
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user