Compare commits

...

7 Commits

Author SHA1 Message Date
Liam Galvin c18b702b61
Fix issues with transparency (#301)
* Fix issues with transparency
* Dispose of temporary alpha image
2021-07-31 17:35:26 +01:00
Liam Galvin 8d4ea4d7fe docs: Fix merge 2021-07-31 14:38:49 +01:00
Liam Galvin 05e7618fa1
docs: Add installation steps + script (#300)
* docs: Add installation steps + script
2021-07-31 14:38:41 +01:00
Liam Galvin 6fa6998aca docs: Add installation steps + script 2021-07-31 14:35:27 +01:00
Liam Galvin 1f2980cbb4
Persist mouse mode during buffer switching (#299)
* Persist mouse mode during buffer switching

* Update mouse.go
2021-07-31 14:34:02 +01:00
Liam Galvin 80b4d6ac9c
Improve mouse handling (#298) 2021-07-31 13:56:39 +01:00
Liam Galvin d1a7b7d6a1 Don't save terminal state if the process was launched from a non-tty, e.g. WM 2021-07-31 13:40:52 +01:00
14 changed files with 115 additions and 80 deletions

View File

@ -22,7 +22,13 @@ Install dependencies:
- `xorg-dev`
- `libgl1-mesa-dev`
Grab a binary from the [latest release](https://github.com/liamg/darktile/releases/latest).
Grab a binary from the [latest release](https://github.com/liamg/darktile/releases/latest), `chmod +x` it and place it in your `$PATH`.
If you're too lazy to do the above and you like to live life on the edge, you can pipe this script to sudo:
```bash
curl -s "https://raw.githubusercontent.com/liamg/darktile/main/scripts/install.sh" | sudo bash
```
## Configuration

View File

@ -90,6 +90,7 @@ var rootCmd = &cobra.Command{
gui.WithFontDPI(conf.Font.DPI),
gui.WithFontSize(conf.Font.Size),
gui.WithFontFamily(conf.Font.Family),
gui.WithOpacity(conf.Opacity),
}
if screenshotAfterMS > 0 {

View File

@ -73,7 +73,7 @@ func LoadThemeFromPath(conf *Config, path string) (*termutil.Theme, error) {
func loadThemeFromConf(conf *Config, themeConf *Theme) (*termutil.Theme, error) {
factory := termutil.NewThemeFactory().WithOpacity(conf.Opacity)
factory := termutil.NewThemeFactory()
colours := map[termutil.Colour]string{
termutil.ColourBlack: themeConf.Black,

View File

@ -14,6 +14,8 @@ import (
// Draw renders the terminal GUI to the ebtien window. Required to implement the ebiten interface.
func (g *GUI) Draw(screen *ebiten.Image) {
tmp := ebiten.NewImage(g.size.X, g.size.Y)
cellSize := g.fontManager.CharSize()
dotDepth := g.fontManager.DotDepth()
@ -36,10 +38,10 @@ func (g *GUI) Draw(screen *ebiten.Image) {
extraW := float64(g.size.X) - endX
extraH := float64(g.size.Y) - endY
if extraW > 0 {
ebitenutil.DrawRect(screen, endX, 0, extraW, endY, defBg)
ebitenutil.DrawRect(tmp, endX, 0, extraW, endY, defBg)
}
if extraH > 0 {
ebitenutil.DrawRect(screen, 0, endY, float64(g.size.X), extraH, defBg)
ebitenutil.DrawRect(tmp, 0, endY, float64(g.size.X), extraH, defBg)
}
var inHighlight bool
@ -52,7 +54,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
for y := int(buffer.ViewHeight() - 1); y >= 0; y-- {
py := cellSize.Y * y
ebitenutil.DrawRect(screen, 0, float64(py), float64(g.size.X), float64(cellSize.Y), defBg)
ebitenutil.DrawRect(tmp, 0, float64(py), float64(g.size.X), float64(cellSize.Y), defBg)
inHighlight = false
for x := uint16(0); x < buffer.ViewWidth(); x++ {
cell := buffer.GetCell(x, uint16(y))
@ -74,7 +76,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
colour = defBg
}
ebitenutil.DrawRect(screen, float64(px), float64(py), float64(cellSize.X), float64(cellSize.Y), colour)
ebitenutil.DrawRect(tmp, float64(px), float64(py), float64(cellSize.X), float64(cellSize.Y), colour)
if buffer.IsHighlighted(termutil.Position{
Line: uint64(y),
@ -106,7 +108,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
}
if isCursor && !ebiten.IsFocused() {
ebitenutil.DrawRect(screen, float64(px)+1, float64(py)+1, float64(cellSize.X)-2, float64(cellSize.Y)-2, g.terminal.Theme().DefaultBackground())
ebitenutil.DrawRect(tmp, float64(px)+1, float64(py)+1, float64(cellSize.X)-2, float64(cellSize.Y)-2, g.terminal.Theme().DefaultBackground())
}
}
for x := uint16(0); x < buffer.ViewWidth(); x++ {
@ -139,13 +141,13 @@ func (g *GUI) Draw(screen *ebiten.Image) {
if cell.Underline() {
uly := float64(py + (dotDepth+cellSize.Y)/2)
ebitenutil.DrawLine(screen, float64(px), uly, float64(px+cellSize.X), uly, colour)
ebitenutil.DrawLine(tmp, float64(px), uly, float64(px+cellSize.X), uly, colour)
}
text.Draw(screen, string(cell.Rune().Rune), useFace, px, py+dotDepth, colour)
text.Draw(tmp, string(cell.Rune().Rune), useFace, px, py+dotDepth, colour)
if cell.Strikethrough() {
ebitenutil.DrawLine(screen, float64(px), float64(py+(cellSize.Y/2)), float64(px+cellSize.X), float64(py+(cellSize.Y/2)), colour)
ebitenutil.DrawLine(tmp, float64(px), float64(py+(cellSize.Y/2)), float64(px+cellSize.X), float64(py+(cellSize.Y/2)), colour)
}
}
@ -157,7 +159,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(sx, sy)
screen.DrawImage(
tmp.DrawImage(
ebiten.NewImageFromImage(sixel.Sixel.Image),
op,
)
@ -237,19 +239,19 @@ func (g *GUI) Draw(screen *ebiten.Image) {
}
// draw opaque box below and above highlighted line(s)
ebitenutil.DrawRect(screen, 0, float64(highlightMin.Line*uint64(cellSize.Y)), float64(cellSize.X*int(highlightMin.Col)), float64(cellSize.Y), color.RGBA{A: 0x80})
ebitenutil.DrawRect(screen, float64((cellSize.X)*int(highlightMax.Col+1)), float64(highlightMax.Line*uint64(cellSize.Y)), float64(g.size.X), float64(cellSize.Y), color.RGBA{A: 0x80})
ebitenutil.DrawRect(screen, 0, 0, float64(g.size.X), float64(highlightMin.Line*uint64(cellSize.Y)), color.RGBA{A: 0x80})
ebitenutil.DrawRect(tmp, 0, float64(highlightMin.Line*uint64(cellSize.Y)), float64(cellSize.X*int(highlightMin.Col)), float64(cellSize.Y), color.RGBA{A: 0x80})
ebitenutil.DrawRect(tmp, float64((cellSize.X)*int(highlightMax.Col+1)), float64(highlightMax.Line*uint64(cellSize.Y)), float64(g.size.X), float64(cellSize.Y), color.RGBA{A: 0x80})
ebitenutil.DrawRect(tmp, 0, 0, float64(g.size.X), float64(highlightMin.Line*uint64(cellSize.Y)), color.RGBA{A: 0x80})
afterLineY := float64((1 + highlightMax.Line) * uint64(cellSize.Y))
ebitenutil.DrawRect(screen, 0, afterLineY, float64(g.size.X), float64(g.size.Y)-afterLineY, color.RGBA{A: 0x80})
ebitenutil.DrawRect(tmp, 0, afterLineY, float64(g.size.X), float64(g.size.Y)-afterLineY, color.RGBA{A: 0x80})
// annotation border
ebitenutil.DrawRect(screen, float64(annotationX)-padding, annotationY-padding, float64(annotationWidth)+(padding*2), annotationHeight+(padding*2), g.terminal.Theme().SelectionBackground())
ebitenutil.DrawRect(tmp, float64(annotationX)-padding, annotationY-padding, float64(annotationWidth)+(padding*2), annotationHeight+(padding*2), g.terminal.Theme().SelectionBackground())
// annotation background
ebitenutil.DrawRect(screen, 1+float64(annotationX)-padding, 1+annotationY-padding, float64(annotationWidth)+(padding*2)-2, annotationHeight+(padding*2)-2, g.terminal.Theme().DefaultBackground())
ebitenutil.DrawRect(tmp, 1+float64(annotationX)-padding, 1+annotationY-padding, float64(annotationWidth)+(padding*2)-2, annotationHeight+(padding*2)-2, g.terminal.Theme().DefaultBackground())
// vertical line
ebitenutil.DrawLine(screen, lineX, float64(lineY), lineX, lineY+lineHeight, g.terminal.Theme().SelectionBackground())
ebitenutil.DrawLine(tmp, lineX, float64(lineY), lineX, lineY+lineHeight, g.terminal.Theme().SelectionBackground())
var tY int
var tX int
@ -259,7 +261,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(annotationX), annotationY)
screen.DrawImage(
tmp.DrawImage(
ebiten.NewImageFromImage(annotation.Image),
op,
)
@ -271,7 +273,7 @@ func (g *GUI) Draw(screen *ebiten.Image) {
tX = 0
continue
}
text.Draw(screen, string(r), regularFace, annotationX+tX, int(annotationY)+dotDepth+tY, g.terminal.Theme().DefaultForeground())
text.Draw(tmp, string(r), regularFace, annotationX+tX, int(annotationY)+dotDepth+tY, g.terminal.Theme().DefaultForeground())
tX += cellSize.X
}
@ -298,11 +300,11 @@ func (g *GUI) Draw(screen *ebiten.Image) {
boxWidth = endX / 8
}
ebitenutil.DrawRect(screen, float64(msgX-1), msgY-1, boxWidth+2, boxHeight+2, msg.Foreground)
ebitenutil.DrawRect(screen, float64(msgX), msgY, boxWidth, boxHeight, msg.Background)
ebitenutil.DrawRect(tmp, float64(msgX-1), msgY-1, boxWidth+2, boxHeight+2, msg.Foreground)
ebitenutil.DrawRect(tmp, float64(msgX), msgY, boxWidth, boxHeight, msg.Background)
for y, line := range lines {
for x, r := range line {
text.Draw(screen, string(r), regularFace, msgX+pad+(x*cellSize.X), pad+(y*cellSize.Y)+int(msgY)+dotDepth, msg.Foreground)
text.Draw(tmp, string(r), regularFace, msgX+pad+(x*cellSize.X), pad+(y*cellSize.Y)+int(msgY)+dotDepth, msg.Foreground)
}
}
msgEndY = msgEndY - float64(pad*4) - float64(len(lines)*g.CellSize().Y)
@ -310,7 +312,11 @@ func (g *GUI) Draw(screen *ebiten.Image) {
}
if g.screenshotRequested {
g.takeScreenshot(screen)
g.takeScreenshot(tmp)
}
opt := &ebiten.DrawImageOptions{}
opt.ColorM.Scale(1, 1, 1, g.opacity)
screen.DrawImage(tmp, opt)
tmp.Dispose()
}

View File

@ -39,6 +39,7 @@ type GUI struct {
screenshotFilename string
startupFuncs []func(g *GUI)
keyState *keyState
opacity float64
}
type PopupMessage struct {
@ -88,6 +89,7 @@ func (g *GUI) Run() error {
}()
ebiten.SetScreenTransparent(true)
ebiten.SetScreenClearedEveryFrame(true)
ebiten.SetWindowResizable(true)
ebiten.SetRunnableOnUnfocused(true)
ebiten.SetFPSMode(ebiten.FPSModeVsyncOffMinimum)

View File

@ -168,6 +168,8 @@ func (g *GUI) handleInput() error {
return g.terminal.WriteToPty([]byte{0x09}) // tab
case g.keyState.RepeatPressed(ebiten.KeyEscape):
g.terminal.GetActiveBuffer().ClearSelection()
g.terminal.GetActiveBuffer().ClearHighlight()
return g.terminal.WriteToPty([]byte{0x1b}) // escape
case g.keyState.RepeatPressed(ebiten.KeyBackspace):
if ebiten.IsKeyPressed(ebiten.KeyAlt) {

View File

@ -121,6 +121,9 @@ func (g *GUI) handleMouse() error {
Col: uint16(col),
})
}
ebiten.ScheduleFrame()
} else {
g.mouseDrag = false
}
@ -222,7 +225,7 @@ func (g *GUI) handleClick(clickCount, x, y int) (bool, error) {
line := uint64(y / g.fontManager.CharSize().Y)
g.terminal.GetActiveBuffer().SelectWordAt(termutil.Position{Col: col, Line: line}, wordMatcher)
return true, nil
case 3: // triple click
default: // triple click (or more!)
g.terminal.GetActiveBuffer().ExtendSelectionToEntireLines()
return true, nil
}
@ -268,7 +271,7 @@ func (g *GUI) handleMouseRemotely(x, y int, pressedLeft, pressedMiddle, pressedR
tx, ty := 1+(x/g.fontManager.CharSize().X), 1+(y/g.fontManager.CharSize().Y)
mode := g.terminal.GetActiveBuffer().GetMouseMode()
mode := g.terminal.GetMouseMode()
switch mode {
case termutil.MouseModeNone:
@ -292,7 +295,7 @@ func (g *GUI) handleMouseRemotely(x, y int, pressedLeft, pressedMiddle, pressedR
var button rune
extMode := g.terminal.GetActiveBuffer().GetMouseExtMode()
extMode := g.terminal.GetMouseExtMode()
switch true {
case pressedLeft:

View File

@ -8,6 +8,13 @@ func WithFontFamily(family string) func(g *GUI) error {
}
}
func WithOpacity(opacity float64) func(g *GUI) error {
return func(g *GUI) error {
g.opacity = opacity
return nil
}
}
func WithFontSize(size float64) func(g *GUI) error {
return func(g *GUI) error {
g.fontManager.SetSize(size)

View File

@ -25,8 +25,6 @@ type Buffer struct {
charsets []*map[rune]rune // array of 2 charsets, nil means ASCII (no conversion)
currentCharset int // active charset index in charsets array, valid values are 0 or 1
modes Modes
mouseMode MouseMode
mouseExtMode MouseExtMode
selectionStart *Position
selectionEnd *Position
highlightStart *Position
@ -80,14 +78,6 @@ func (buffer *Buffer) IsCursorVisible() bool {
return buffer.modes.ShowCursor
}
func (buffer *Buffer) GetMouseMode() MouseMode {
return buffer.mouseMode
}
func (buffer *Buffer) GetMouseExtMode() MouseExtMode {
return buffer.mouseExtMode
}
func (buffer *Buffer) IsApplicationCursorKeysModeEnabled() bool {
return buffer.modes.ApplicationCursorKeys
}

View File

@ -717,11 +717,9 @@ func (t *Terminal) csiSetMode(modes string, enabled bool) bool {
t.activeBuffer.modes.AutoWrap = enabled
case "?9":
if enabled {
//terminal.logger.Infof("Turning on X10 mouse mode")
t.activeBuffer.mouseMode = (MouseModeX10)
t.mouseMode = (MouseModeX10)
} else {
//terminal.logger.Infof("Turning off X10 mouse mode")
t.activeBuffer.mouseMode = (MouseModeNone)
t.mouseMode = (MouseModeNone)
}
case "?12", "?13":
t.activeBuffer.modes.BlinkingCursor = enabled
@ -737,46 +735,40 @@ func (t *Terminal) csiSetMode(modes string, enabled bool) bool {
// enable mouse tracking
// 1000 refers to ext mode for extended mouse click area - otherwise only x <= 255-31
if enabled {
t.activeBuffer.mouseMode = (MouseModeVT200)
t.mouseMode = (MouseModeVT200)
} else {
t.activeBuffer.mouseMode = (MouseModeNone)
t.mouseMode = (MouseModeNone)
}
case "?1002":
if enabled {
//terminal.logger.Infof("Turning on Button Event mouse mode")
t.activeBuffer.mouseMode = (MouseModeButtonEvent)
t.mouseMode = (MouseModeButtonEvent)
} else {
//terminal.logger.Infof("Turning off Button Event mouse mode")
t.activeBuffer.mouseMode = (MouseModeNone)
t.mouseMode = (MouseModeNone)
}
case "?1003":
if enabled {
t.activeBuffer.mouseMode = MouseModeAnyEvent
t.mouseMode = MouseModeAnyEvent
} else {
t.activeBuffer.mouseMode = MouseModeNone
t.mouseMode = MouseModeNone
}
case "?1005":
if enabled {
t.activeBuffer.mouseExtMode = MouseExtUTF
t.mouseExtMode = MouseExtUTF
} else {
t.activeBuffer.mouseExtMode = MouseExtNone
t.mouseExtMode = MouseExtNone
}
case "?1006":
if enabled {
//.logger.Infof("Turning on SGR ext mouse mode")
t.activeBuffer.mouseExtMode = MouseExtSGR
t.mouseExtMode = MouseExtSGR
} else {
//terminal.logger.Infof("Turning off SGR ext mouse mode")
t.activeBuffer.mouseExtMode = (MouseExtNone)
t.mouseExtMode = (MouseExtNone)
}
case "?1015":
if enabled {
//terminal.logger.Infof("Turning on URXVT ext mouse mode")
t.activeBuffer.mouseExtMode = (MouseExtURXVT)
t.mouseExtMode = (MouseExtURXVT)
} else {
//terminal.logger.Infof("Turning off URXVT ext mouse mode")
t.activeBuffer.mouseExtMode = (MouseExtNone)
t.mouseExtMode = (MouseExtNone)
}
case "?1048":
if enabled {

View File

@ -27,6 +27,8 @@ type Terminal struct {
closeChan chan struct{}
buffers []*Buffer
activeBuffer *Buffer
mouseMode MouseMode
mouseExtMode MouseExtMode
logFile *os.File
theme *Theme
running bool
@ -157,11 +159,14 @@ func (t *Terminal) Run(updateChan chan struct{}, rows uint16, cols uint16) error
}
// Set stdin in raw mode.
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
t.windowManipulator.ReportError(err)
if fd := int(os.Stdin.Fd()); term.IsTerminal(fd) {
oldState, err := term.MakeRaw(fd)
if err != nil {
t.windowManipulator.ReportError(err)
}
defer func() { _ = term.Restore(fd, oldState) }() // Best effort.
}
defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
go t.process()
@ -282,6 +287,14 @@ func (t *Terminal) switchBuffer(index uint8) {
}
}
func (t *Terminal) GetMouseMode() MouseMode {
return t.mouseMode
}
func (t *Terminal) GetMouseExtMode() MouseExtMode {
return t.mouseExtMode
}
func (t *Terminal) GetActiveBuffer() *Buffer {
return t.activeBuffer
}

View File

@ -35,7 +35,6 @@ const (
)
type Theme struct {
alpha uint8
colourMap map[Colour]color.Color
}
@ -87,7 +86,7 @@ func (t *Theme) ColourFrom4Bit(code uint8) color.Color {
func (t *Theme) DefaultBackground() color.Color {
c, ok := t.colourMap[ColourBackground]
if !ok {
return color.RGBA{0, 0, 0, t.alpha}
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
@ -95,7 +94,7 @@ func (t *Theme) DefaultBackground() color.Color {
func (t *Theme) DefaultForeground() color.Color {
c, ok := t.colourMap[ColourForeground]
if !ok {
return color.RGBA{255, 255, 255, t.alpha}
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
@ -103,7 +102,7 @@ func (t *Theme) DefaultForeground() color.Color {
func (t *Theme) SelectionBackground() color.Color {
c, ok := t.colourMap[ColourSelectionBackground]
if !ok {
return color.RGBA{0, 0, 0, t.alpha}
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
@ -111,7 +110,7 @@ func (t *Theme) SelectionBackground() color.Color {
func (t *Theme) SelectionForeground() color.Color {
c, ok := t.colourMap[ColourSelectionForeground]
if !ok {
return color.RGBA{255, 255, 255, t.alpha}
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
@ -119,7 +118,7 @@ func (t *Theme) SelectionForeground() color.Color {
func (t *Theme) CursorBackground() color.Color {
c, ok := t.colourMap[ColourCursorBackground]
if !ok {
return color.RGBA{255, 255, 255, t.alpha}
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
@ -127,7 +126,7 @@ func (t *Theme) CursorBackground() color.Color {
func (t *Theme) CursorForeground() color.Color {
c, ok := t.colourMap[ColourCursorForeground]
if !ok {
return color.RGBA{0, 0, 0, t.alpha}
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
@ -149,12 +148,12 @@ func (t *Theme) ColourFrom8Bit(n string) (color.Color, error) {
R: byte(c),
G: byte(c),
B: byte(c),
A: t.alpha,
A: 0xff,
}, nil
}
var colour color.RGBA
colour.A = t.alpha
colour.A = 0xff
indexR := ((index - 16) / 36)
if indexR > 0 {
colour.R = uint8(55 + indexR*40)
@ -188,7 +187,7 @@ func (t *Theme) ColourFrom24Bit(r, g, b string) (color.Color, error) {
R: byte(ri),
G: byte(gi),
B: byte(bi),
A: t.alpha,
A: 0xff,
}, nil
}

View File

@ -10,7 +10,6 @@ type ThemeFactory struct {
func NewThemeFactory() *ThemeFactory {
return &ThemeFactory{
theme: &Theme{
alpha: 0xff,
colourMap: map[Colour]color.Color{},
},
colourMap: make(map[Colour]color.Color),
@ -24,17 +23,12 @@ func (t *ThemeFactory) Build() *Theme {
R: uint8(r / 0xff),
G: uint8(g / 0xff),
B: uint8(b / 0xff),
A: t.theme.alpha,
A: 0xff,
}
}
return t.theme
}
func (t *ThemeFactory) WithOpacity(opacity float64) *ThemeFactory {
t.theme.alpha = uint8(0xff * opacity)
return t
}
func (t *ThemeFactory) WithColour(key Colour, colour color.Color) *ThemeFactory {
t.colourMap[key] = colour
return t

20
scripts/install.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e
echo "Determining platform..."
platform=$(uname | tr '[:upper:]' '[:lower:]')
echo "Finding latest release..."
asset=$(curl --silent https://api.github.com/repos/liamg/darktile/releases/latest | jq -r ".assets[] | select(.name | contains(\"${platform}\")) | .url")
echo "Downloading latest release for your platform..."
curl -s -L -H "Accept: application/octet-stream" "${asset}" --output /tmp/darktile
echo "Installing darktile..."
chmod +x /tmp/darktile
installdir="${HOME}/bin/"
if [ "$EUID" -eq 0 ]; then
installdir="/usr/local/bin/"
fi
mkdir -p $installdir
mv /tmp/darktile "${installdir}/darktile"
which darktile &> /dev/null || (echo "Please add ${installdir} to your PATH to complete installation!" && exit 1)
echo "Installation complete!"