Implement window manipulation CSI sequences (#256)

* Implement window manipulation CSI sequences

* Fix travis config to make gofmt only for go1.11 builds

* remove commented out functions
This commit is contained in:
nikitar020 2019-03-13 14:16:32 +02:00 committed by Max Risuhin
parent 44233f384e
commit 05c45c0892
8 changed files with 297 additions and 6 deletions

View File

@ -29,7 +29,7 @@ script:
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then make build-darwin-native-travis; fi
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then make build-linux-travis; fi
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then make windows-cross-compile-travis; fi
- if [[ $TRAVIS_OS_NAME == 'linux' ]]; then make check-gofmt; fi
- if [[ $TRAVIS_OS_NAME == 'linux' && $TRAVIS_GO_VERSION =~ ^1\.11\. ]]; then echo 'check-gofmt'; make check-gofmt; fi
env:
global:
- secure: "pdRpTOGQSUgbC9tK37voxUYJHMWDPJEmdMhNBsljpP9VnxxbR6JEFwvOQEmUHGlsYv8jma6a17jE60ngVQk8QP12cPh48i2bdbVgym/zTUOKFawCtPAzs8i7evh0di5eZ3uoyc42kG4skc+ePuVHbXC8jDxwaPpMqSHD7QyQc1/6ckI9LLkyWUqhnJJXkVwhmI74Aa1Im6QhywAWFMeTBRRL02cwr6k7VKSYOn6yrtzJRCALFGpZ/n58lPrpDxN7W8o+HRQP89wIDy8FyNeEPdmqGFNfMHDvI3oJRN4dGC4H9EkKf/iGuNJia1Bs+MgaG9kKlMHsI6Fkh5uw9KNTvC1llx43VRQJzm26cn1CpRxxRtF4F8lqkpY4tHjxxCitV+98ddW8jdmQYyx+LeueC5wqlO9g2M5L3oXsGMqZ++mDRDa8oQoQAVUSVtimeO8ODXFuVNR8TlupP0Cthgucil63VUZfAD8EHc2zpRSFxfYByDH53uMEinn20uovL6W42fqgboC43HOnR6aVfSANPsBFDlcpZFa2BY5RkcKyYdaLkucy0DKJ946UDfhOu6FNm0GPHq5HcgWkLojNF0dEFgG6J+SGQGiPjxTlHP/zoe61qMlWu+fYRXQnKWZN5Kk0T1TbAk6pKSE6wRLG8ddxvMg+eVpGLT+gAvQdrrkMFvs="

View File

@ -48,7 +48,7 @@ func LoadTrueTypeFont(program uint32, r io.Reader, scale float32) (*Font, error)
gl.BindVertexArray(f.vao)
gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo)
gl.BufferData(gl.ARRAY_BUFFER, 6*4*4, nil, gl.STATIC_DRAW)
gl.BufferData(gl.ARRAY_BUFFER, 6*4*4, nil, gl.DYNAMIC_DRAW)
vertAttrib := uint32(gl.GetAttribLocation(f.program, gl.Str("vert\x00")))
gl.EnableVertexAttribArray(vertAttrib)

View File

@ -65,6 +65,8 @@ type GUI struct {
mouseMovedAfterSelectionStarted bool
internalResize bool
selectionRegionMode buffer.SelectionRegionMode
mainThreadFunc chan func()
}
func Min(x, y int) int {
@ -167,6 +169,8 @@ func New(config *config.Config, terminal *terminal.Terminal, logger *zap.Sugared
keyboardShortcuts: shortcuts,
resizeLock: &sync.Mutex{},
internalResize: false,
mainThreadFunc: make(chan func()),
}, nil
}
@ -490,6 +494,8 @@ Buffer Size: %d lines
gui.resizeToTerminal()
case reverse := <-reverseChan:
gui.generateDefaultCell(reverse)
case funcForMainThread := <-gui.mainThreadFunc:
funcForMainThread()
default:
break terminalEvents
}
@ -825,3 +831,14 @@ func (gui *GUI) windowPosChangeCallback(w *glfw.Window, xpos int, ypos int) {
func (gui *GUI) monitorChangeCallback(monitor *glfw.Monitor, event glfw.MonitorEvent) {
gui.SetDPIScale()
}
// Synchronously executes the argument function in the main thread.
// Does not return until f() executed!
func (gui *GUI) executeInMainThread(f func() error) error {
resultChan := make(chan error, 1)
gui.mainThreadFunc <- func() {
resultChan <- f()
}
gui.terminal.NotifyDirty() // wake up the main thread to allow processing
return <-resultChan
}

View File

@ -0,0 +1,107 @@
package gui
import (
"fmt"
"github.com/go-gl/glfw/v3.2/glfw"
"github.com/liamg/aminal/terminal"
)
//
// Implementation of the terminal.WindowManipulationInterface
//
func (gui *GUI) RestoreWindow(term *terminal.Terminal) error {
return gui.executeInMainThread(func() error {
gui.window.Restore()
return nil
})
}
func (gui *GUI) IconifyWindow(term *terminal.Terminal) error {
return gui.executeInMainThread(func() error {
return gui.window.Iconify()
})
}
func (gui *GUI) MoveWindow(term *terminal.Terminal, pixelX int, pixelY int) error {
return gui.executeInMainThread(func() error {
gui.window.SetPos(pixelX, pixelY)
return nil
})
}
func (gui *GUI) ResizeWindowByPixels(term *terminal.Terminal, pixelsHeight int, pixelsWidth int) error {
return gui.executeInMainThread(func() error {
term.Unlock()
gui.window.SetSize(pixelsWidth, pixelsHeight)
term.Lock()
return nil
})
}
func (gui *GUI) BringWindowToFront(term *terminal.Terminal) error {
var err error
if gui.window.GetAttrib(glfw.Iconified) != 0 {
err = gui.executeInMainThread(func() error {
return gui.window.Restore()
})
}
if err != nil {
err = gui.window.Focus()
}
return err
}
func (gui *GUI) ResizeWindowByChars(term *terminal.Terminal, charsHeight int, charsWidth int) error {
return gui.executeInMainThread(func() error {
return term.SetSize(uint(charsWidth), uint(charsHeight))
})
}
func (gui *GUI) MaximizeWindow(term *terminal.Terminal) error {
return gui.executeInMainThread(func() error {
term.Lock()
err := gui.window.Maximize()
term.Unlock()
return err
})
}
func (gui *GUI) ReportWindowState(term *terminal.Terminal) error {
// Report xterm window state. If the xterm window is open (non-iconified), it returns CSI 1 t .
// If the xterm window is iconified, it returns CSI 2 t .
if gui.window.GetAttrib(glfw.Iconified) != 0 {
_ = term.Write([]byte("\x1b[2t"))
} else {
_ = term.Write([]byte("\x1b[1t"))
}
return nil
}
func (gui *GUI) ReportWindowPosition(term *terminal.Terminal) error {
// Report xterm window position as CSI 3 ; x; yt
x, y := gui.window.GetPos()
_ = term.Write([]byte(fmt.Sprintf("\x1b[3;%d;%dt", x, y)))
return nil
}
func (gui *GUI) ReportWindowSizeInPixels(term *terminal.Terminal) error {
// Report xterm window in pixels as CSI 4 ; height ; width t
_ = term.Write([]byte(fmt.Sprintf("\x1b[4;%d;%dt", gui.height, gui.width)))
return nil
}
func (gui *GUI) ReportWindowSizeInChars(term *terminal.Terminal) error {
// Report the size of the text area in characters as CSI 8 ; height ; width t
charsWidth, charsHeight := gui.renderer.GetTermSize()
_ = term.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", charsHeight, charsWidth)))
return nil
}

View File

@ -71,6 +71,8 @@ func initialize(unitTestfunc callback) {
logger.Fatalf("Cannot start: %s", err)
}
terminal.WindowManipulation = g
if unitTestfunc != nil {
go unitTestfunc(terminal, g)
} else {

View File

@ -425,10 +425,6 @@ func csiSetModeHandler(params []string, terminal *Terminal) error {
return csiSetModes(params, true, terminal)
}
func csiWindowManipulation(params []string, terminal *Terminal) error {
return fmt.Errorf("Window manipulation is not yet supported")
}
func csiLinePositionAbsolute(params []string, terminal *Terminal) error {
row := 1
if len(params) > 0 {

View File

@ -33,6 +33,20 @@ const (
MouseExtSGR
)
type WindowManipulationInterface interface {
RestoreWindow(term *Terminal) error
IconifyWindow(term *Terminal) error
MoveWindow(term *Terminal, pixelX int, pixelY int) error
ResizeWindowByPixels(term *Terminal, pixelsHeight int, pixelsWidth int) error
BringWindowToFront(term *Terminal) error
ResizeWindowByChars(term *Terminal, charsHeight int, charsWidth int) error
MaximizeWindow(term *Terminal) error
ReportWindowState(term *Terminal) error
ReportWindowPosition(term *Terminal) error
ReportWindowSizeInPixels(term *Terminal) error
ReportWindowSizeInChars(term *Terminal) error
}
type Terminal struct {
program uint32
buffers []*buffer.Buffer
@ -56,6 +70,8 @@ type Terminal struct {
terminalState *buffer.TerminalState
platformDependentSettings platform.PlatformDependentSettings
dirty *notifier
WindowManipulation WindowManipulationInterface
}
type Modes struct {

View File

@ -0,0 +1,153 @@
package terminal
import (
"fmt"
"strconv"
)
func getOptionalIntegerParam(params []string, paramNo int, defValue int) (int, error) {
result := defValue
if len(params) >= paramNo+1 {
var err error
result, err = strconv.Atoi(params[paramNo])
if err != nil {
return defValue, err
}
}
return result, nil
}
func getMandatoryIntegerParam(params []string, paramNo int) (int, error) {
if len(params) < paramNo+1 {
return 0, fmt.Errorf("no mandatory parameter")
}
result, err := strconv.Atoi(params[paramNo])
if err != nil {
return 0, err
}
return result, nil
}
func csiWindowManipulation(params []string, terminal *Terminal) error {
if terminal.WindowManipulation == nil {
return fmt.Errorf("Handler for CSI window manipulation commands is not set")
}
operation, err := getMandatoryIntegerParam(params, 0)
if err != nil {
return fmt.Errorf("CSI t ignored: %s", err.Error())
}
switch operation {
case 1:
terminal.logger.Debug("De-iconify window")
return terminal.WindowManipulation.RestoreWindow(terminal)
case 2:
terminal.logger.Debug("Iconify window")
return terminal.WindowManipulation.IconifyWindow(terminal)
case 3:
terminal.logger.Debug("Move window")
{
x, err := getMandatoryIntegerParam(params, 1)
if err != nil {
return err
}
y, err := getMandatoryIntegerParam(params, 2)
if err != nil {
return err
}
return terminal.WindowManipulation.MoveWindow(terminal, x, y)
}
case 4:
terminal.logger.Debug("Resize the window in pixels")
{
height, err := getMandatoryIntegerParam(params, 1)
if err != nil {
return err
}
width, err := getMandatoryIntegerParam(params, 2)
if err != nil {
return err
}
return terminal.WindowManipulation.ResizeWindowByPixels(terminal, height, width)
}
case 5:
terminal.logger.Debug("Raise the window to the front")
return terminal.WindowManipulation.BringWindowToFront(terminal)
case 6:
return fmt.Errorf("Lowering the window to the bottom is not implemented")
case 7:
// NB: On Windows this sequence seem handled by the system
return fmt.Errorf("Refreshing the window is not implemented")
case 8:
terminal.logger.Debug("Resize the text area in characters")
{
height, err := getMandatoryIntegerParam(params, 1)
if err != nil {
return err
}
width, err := getMandatoryIntegerParam(params, 2)
if err != nil {
return err
}
return terminal.WindowManipulation.ResizeWindowByChars(terminal, height, width)
}
case 9:
{
p, err := getMandatoryIntegerParam(params, 1)
if err != nil {
return err
}
if p == 0 {
terminal.logger.Debug("Restore maximized window")
return terminal.WindowManipulation.RestoreWindow(terminal)
} else if p == 1 {
terminal.logger.Debug("Maximize window")
return terminal.WindowManipulation.MaximizeWindow(terminal)
}
}
case 11:
terminal.logger.Debug("Report the window state")
return terminal.WindowManipulation.ReportWindowState(terminal)
case 13:
terminal.logger.Debug("Report the window position")
return terminal.WindowManipulation.ReportWindowPosition(terminal)
case 14:
terminal.logger.Debug("Report the window size in pixels")
return terminal.WindowManipulation.ReportWindowSizeInPixels(terminal)
case 18:
terminal.logger.Debug("Report the window size in characters as CSI 8")
return terminal.WindowManipulation.ReportWindowSizeInChars(terminal)
case 19:
return fmt.Errorf("Reporting the screen size in characters is not implemented")
case 20:
return fmt.Errorf("Reporting the window icon label is not implemented")
case 21:
return fmt.Errorf("Reporting the window title is not implemented")
default:
return fmt.Errorf("not supported CSI t")
}
return nil
}