Skip to content

Commit

Permalink
Fixed zoom bug by writing some tests. Also moved thigns around a lot.
Browse files Browse the repository at this point in the history
  • Loading branch information
tombh committed May 20, 2016
1 parent fa72b1b commit 13d9fad
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 280 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ build/
*.log
*.out
blank.png
stdin_forward
xzoom/xzoom_debug
interfacer/interfacer
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ RUN apk add --no-cache xvfb xdotool@testing ffmpeg openssh mosh firefox
# Generate host keys
RUN ssh-keygen -A

# Installing Hiptext, video to text renderer and our own stdin_forward
# Installing Hiptext, video to text renderer and our own interface.go
RUN apk --no-cache add --virtual build-dependencies \
build-base git go freetype-dev jpeg-dev ffmpeg-dev ragel libx11-dev libxt-dev
# setup GOPATH and GOBIN and build stdin_forward
# setup GOPATH and GOBIN and build interface.go
RUN apk --no-cache add libgflags-dev@testing glog-dev@testing
RUN mkdir -p build \
&& cd build \
Expand Down
113 changes: 90 additions & 23 deletions stdin_forward.go → interfacer/interfacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"fmt"
"strings"
"path/filepath"
"time"
"os"
"os/exec"
Expand All @@ -17,15 +18,15 @@ import (
// NB: The following comments are parsed by `go build` ...

// #cgo LDFLAGS: -lXext -lX11 -lXt
// #include "xzoom/xzoom.h"
// #include "../xzoom/xzoom.h"
import "C"

var logfile = "./input.log"
var logfile string
var current string
var curev termbox.Event
var lastMouseButton string
var desktopWidth float32 = 1600
var desktopHeight float32 = 1200
var desktopWidth = float32(C.WIDTH)
var desktopHeight = float32(C.HEIGHT)
var desktopXFloat float32
var desktopYFloat float32
var roundedDesktopX int
Expand All @@ -35,14 +36,20 @@ var roundedDesktopY int
var hipWidth int
var hipHeight int

var panning bool
var panNeedsSetup bool
var panCachedXOffset float32
var panCachedYOffset float32

func initialise() {
tErr := os.Truncate(logfile, 0)
if tErr != nil {
panic(tErr)
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
panic(err)
}
os.Mkdir(filepath.Join(dir, "..", "logs"), os.ModePerm)
logfile = fmt.Sprintf(filepath.Join(dir, "..", "logs", "input.log"))
if _, err := os.Stat(logfile); err == nil {
os.Truncate(logfile, 0)
}
log("Starting...")
calculateHipDimensions()
Expand Down Expand Up @@ -85,6 +92,13 @@ func log(msg string) {
}
}

func getXGrab() int {
return int(C.xgrab)
}
func getYGrab() int {
return int(C.ygrab)
}

// Issue an xdotool command to simulate mouse and keyboard input
func xdotool(args ...string) {
log(strings.Join(args, " "))
Expand Down Expand Up @@ -133,16 +147,14 @@ func mouseButtonStr(k termbox.Key) []string {
return []string{"mouseup", lastMouseButton}
case termbox.MouseWheelUp:
if ctrlPressed() {
C.magx++
C.magy++
zoom("in")
return []string{"noop"}
}
return []string{"click", "4"}
case termbox.MouseWheelDown:
if ctrlPressed() {
if C.magx > 1 {
C.magx--
C.magy--
if C.magnification > 1 {
zoom("out")
}
return []string{"noop"}
}
Expand All @@ -151,6 +163,59 @@ func mouseButtonStr(k termbox.Key) []string {
return []string{""}
}

func zoom(direction string) {
oldZoom := C.magnification

// The actual zoom
if direction == "in" {
C.magnification++
} else {
C.magnification--
}
C.width[C.SRC] = (C.WIDTH + C.magnification - 1) / C.magnification;
C.height[C.SRC] = (C.HEIGHT + C.magnification - 1) / C.magnification;

// Move the viewport so that the mouse is still over the same part of
// the desktop.
factor := float32(oldZoom) / float32(C.magnification)
magnifiedRelativeX := factor * (desktopXFloat - float32(C.xgrab))
magnifiedRelativeY := factor * (desktopYFloat - float32(C.ygrab))
C.xgrab = C.int(desktopXFloat - magnifiedRelativeX)
C.ygrab = C.int(desktopYFloat - magnifiedRelativeY)

keepViewportInDesktop()
}

func keepViewportInDesktop() {
// Manage the viewport size
if C.width[C.SRC] < 1 {
C.width[C.SRC] = 1
}
if C.width[C.SRC] > C.WIDTH {
C.width[C.SRC] = C.WIDTH
}
if C.height[C.SRC] < 1 {
C.height[C.SRC] = 1
}
if C.height[C.SRC] > C.HEIGHT {
C.height[C.SRC] = C.HEIGHT
}

// Manage the viewport position
if C.xgrab > (C.WIDTH - C.width[C.SRC]) {
C.xgrab = C.WIDTH - C.width[C.SRC]
}
if C.xgrab < 0 {
C.xgrab = 0
}
if C.ygrab > (C.HEIGHT - C.height[C.SRC]) {
C.ygrab = C.HEIGHT - C.height[C.SRC]
}
if C.ygrab < 0 {
C.ygrab = 0
}
}

// Auxillary data. Whether the mouse was moving or a mod key like CTRL
// is being pressed at the same time.
func modStr(m termbox.Modifier) string {
Expand All @@ -170,27 +235,29 @@ func modStr(m termbox.Modifier) string {
}

func mouseEvent() {
log(
fmt.Sprintf(
"EventMouse: x: %d, y: %d, b: %s, mod: %s",
curev.MouseX, curev.MouseY, mouseButtonStr(curev.Key), modStr(curev.Mod)))

setCurrentDesktopCoords()
// Always move the mouse first so that button presses are correct. This is because we're not constantly
// updating the mouse position, *unless* a drag event is happening. This saves bandwidth. Also, mouse
// movement isn't supported on all terminals.
xdotool("mousemove", fmt.Sprintf("%d", roundedDesktopX), fmt.Sprintf("%d", roundedDesktopY))

log(
fmt.Sprintf(
"EventMouse: x: %d, y: %d, b: %s, mod: %s",
curev.MouseX, curev.MouseY, mouseButtonStr(curev.Key), modStr(curev.Mod)))

if ctrlPressed() && mouseMotion() && lastMouseButton == "1" {
C.pan = 1
if panNeedsSetup == true {
panning = true
if panNeedsSetup {
panCachedXOffset = float32(C.xgrab)
panCachedYOffset = float32(C.ygrab)
panNeedsSetup = false
}
panNeedsSetup = false
C.xgrab = C.int(desktopXFloat - panCachedXOffset)
C.ygrab = C.int(desktopYFloat - panCachedYOffset)
} else {
panNeedsSetup = true
C.pan = 0
panning = false
if !ctrlPressed() {
xdotool(mouseButtonStr(curev.Key)...)
}
Expand All @@ -207,7 +274,7 @@ func setCurrentDesktopCoords() {
eventY := float32(curev.MouseY)
width := float32(C.width[C.SRC])
height := float32(C.height[C.SRC])
if C.pan == 1 {
if panning {
// When panning starts we want to do it all within the same viewport.
// Without the caching here, then the viewport would change for each
// mouse movement and panning becomes overly sensitive.
Expand All @@ -222,7 +289,7 @@ func setCurrentDesktopCoords() {
log(
fmt.Sprintf(
"setCurrentDesktopCoords: tw: %d, th: %d, dx: %d, dy: %d, mag: %d",
hipHeightFloat, hipWidthFloat, desktopXFloat, desktopYFloat, C.magx))
hipHeightFloat, hipWidthFloat, eventX, width, C.magnification))
roundedDesktopX = roundToInt(desktopXFloat)
roundedDesktopY = roundToInt(desktopYFloat)
}
Expand Down
95 changes: 95 additions & 0 deletions interfacer/mouse_input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"github.com/nsf/termbox-go"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)

func TestMouseInput(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Mouse Input Suite")
}

var _ = Describe("Mouse Input", func() {
BeforeEach(func() {
err := termbox.Init()
if err != nil {
panic(err)
}
initialise()
hipWidth = 90
hipHeight = 30
setCurrentDesktopCoords()
})

AfterEach(func(){
termbox.Close()
})

Describe("Mouse position", func(){
It("Should work in the top left", func() {
curev.MouseX = 30
curev.MouseY = 10
setCurrentDesktopCoords()
Expect(roundedDesktopX).To(Equal(533))
Expect(roundedDesktopY).To(Equal(400))
})
It("Should work in the middle", func() {
curev.MouseX = 45
curev.MouseY = 15
setCurrentDesktopCoords()
Expect(roundedDesktopX).To(Equal(800))
Expect(roundedDesktopY).To(Equal(600))
})
It("Should work in the bottom right", func() {
curev.MouseX = 60
curev.MouseY = 20
setCurrentDesktopCoords()
Expect(roundedDesktopX).To(Equal(1067))
Expect(roundedDesktopY).To(Equal(800))
})
})

Describe("Zooming", func() {
BeforeEach(func(){
curev.MouseX = 30
curev.MouseY = 10
setCurrentDesktopCoords()
})
It("Should zoom in once", func() {
Expect(getXGrab()).To(Equal(0))
Expect(getYGrab()).To(Equal(0))
Expect(roundedDesktopX).To(Equal(533))
Expect(roundedDesktopY).To(Equal(400))
zoom("in")
setCurrentDesktopCoords()
Expect(getXGrab()).To(Equal(266))
Expect(getYGrab()).To(Equal(200))
Expect(roundedDesktopX).To(Equal(533))
Expect(roundedDesktopY).To(Equal(400))
})
It("Should zoom in then out", func() {
zoom("in")
setCurrentDesktopCoords()
zoom("out")
zoom("out")
setCurrentDesktopCoords()
Expect(getXGrab()).To(Equal(0))
Expect(getYGrab()).To(Equal(0))
Expect(roundedDesktopX).To(Equal(533))
Expect(roundedDesktopY).To(Equal(400))
})
It("Should zoom near an edge without breaking out", func() {
curev.MouseX = 0
curev.MouseY = 0
setCurrentDesktopCoords()
zoom("in")
Expect(getXGrab()).To(Equal(0))
Expect(getYGrab()).To(Equal(0))
Expect(roundedDesktopX).To(Equal(0))
Expect(roundedDesktopY).To(Equal(0))
})
})
})
10 changes: 5 additions & 5 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ UDP_URI='udp://127.0.0.1:1234'
# | | |
# ---------------------------------
# So xzoom mirrors the desktop and ffmpeg streams the xzoom window.
Xvfb :0 -screen 0 "$(($DESKTOP_WIDTH * 2))"x"$DESKTOP_HEIGHT"x16 > xvfb.log 2>&1 &
Xvfb :0 -screen 0 "$(($DESKTOP_WIDTH * 2))"x"$DESKTOP_HEIGHT"x16 > ./logs/xvfb.log 2>&1 &

# TODO: detect X start rather than sleep
sleep 1

/usr/bin/firefox >> xvfb.log 2>&1 &
/usr/bin/firefox >> ./logs/xvfb.log 2>&1 &

# Convert the X framebuffer desktop into a video stream, but only stream the
# right hand side where the xzoom window is.
Expand All @@ -37,22 +37,22 @@ ffmpeg \
-vcodec mpeg2video \
-f mpegts \
$UDP_URI \
> ffmpeg.log 2>&1 &
> ./logs/ffmpeg.log 2>&1 &

# The above ffmpeg can take a while to open the UDP stream, so wait a little
# TODO: detect the stream's presence rather than sleep
sleep 1

# Intercept STDIN (mouse and keypresses) and forward to the X framebuffer via xdotool
(./stdin_forward <&3 > interface.log 2>&1 &) 3<&0
(./interfacer/interfacer <&3 > ./logs/interface.log 2>&1 &) 3<&0

# Hiptext renders images and videos into text characters displayable in a terminal.
# It complains unless you specify the exact path to the font, seems like a bug to me.
# TODO: support variable width, ideally dynamic sizing
hiptext \
-font /usr/share/fonts/ttf-dejavu/DejaVuSansMono.ttf \
$UDP_URI \
2> hiptext.log
2> ./logs/hiptext.log

# Kill all the subprocesses created in this script if the script itself exits
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
Loading

0 comments on commit 13d9fad

Please sign in to comment.