-
-
Notifications
You must be signed in to change notification settings - Fork 313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for GPM Clicking #297
Comments
I'm aware of GPM, but I have no idea how it works. It seems like in an ideal world it would just cause additional escape sequences to sent to the terminal. That may or may not be what happens. |
Looking at the Linux docs for gpm, it appears that gpm is a daemon that injects the events into the terminal so that they are reported in an xterm compatible fashion. So, it may just (from tcell's perspective) be a matter of the user running gpm in the background, and having the necessary terminfo description. It's possible that I can just do this easily myself -- I can try with a VM and see what happens. |
i couldnt find how gpm injects events into the terminal, besides when pasting or reading the selection for which it uses /dev/console (docs are quite sparse), seems like most of the terminal programs use /dev/gpmctl to interact with it (also ncurses uses /dev/gpmctl) i wrote https://github.com/jackdoe/go-gpmctl by reading how daemon/do_client.c in gpm works, it is fresh out of the oven but it seems to work on my laptop |
since i am trying to go back to the tty; browsh is pretty much my only option, so i have a local fork of tcell that does this hack in tscreen.go:
i am just playing with, but its working, at least i can follow links |
A possibly somewhat cleaner way might be to use screen.PostEvent() to post an actual EventMouse. You basically construct one with NewEventMouse and then inject into the event stream with screen.PostEvent(). |
How can I implement it? |
@gdamore I'm still looking for help. Please may you help me know to what file this needs to be added. Please may you give more detail than your previous comment. |
Below gpm package which is MIT licensed (https://opensource.org/license/mit/) adds gpm support to tcell. Usage: // import "path/to/gpm"
func main() {
scr, err := tcell.NewScreen()
if err != nil {
panic(fmt.Sprintf("can't obtain screen: %v", err))
}
if err := scr.Init(); err != nil {
panic(fmt.Sprintf("can't init screen: %v", err))
}
scr, haveGPM := gpm.WarpGPMSupport(scr)
if !haveGPM {
scr.EnableMouse()
}
for {
evt := scr.PollEvent()
if evt == nil {
return
}
switch evt := evt.(type) {
// process events
}
}
} A patch for tcell could be in screen.go // NewScreen returns a default Screen suitable for the user's terminal
// environment.
func NewScreen() (Screen, error) {
// Windows is happier if we try for a console screen first.
if s, _ := NewConsoleScreen(); s != nil {
return s, nil
} else if s, e := NewTerminfoScreen(); s != nil {
s, _ = gpm.WrapGPMSupport(s)
return s, nil
} else {
return nil, e
}
} package gpm
import (
"os"
"github.com/gdamore/tcell/v2"
"github.com/jackdoe/go-gpmctl"
)
// scr enables to embedded tcell.Screen privately
type scr tcell.Screen
type gpmReader interface {
Read() (gpmctl.Event, error)
}
// WrapGPMSupport either returns given screen scr and false if we are
// not in a linux console or no gpm support is found. Otherwise scr is
// wrapped by GPMScreen reporting gpm mouse events and providing a mouse
// cursor. Optionally further environment TERM-strings which should be checked
// for gpm support can be provided.
func WarpGPMSupport(scr tcell.Screen, tt ...string) (
_ tcell.Screen, haveGPM bool,
) {
tt = append([]string{"linux"}, tt...)
properTerminal, envterm := false, os.Getenv("TERM")
for _, t := range tt {
if t != envterm {
continue
}
properTerminal = true
break
}
if !properTerminal {
return scr, false
}
gpm, err := gpmctl.NewGPM(gpmctl.GPMConnect{
EventMask: gpmctl.ANY,
DefaultMask: ^gpmctl.HARD,
MinMod: 0,
MaxMod: ^uint16(0),
})
if err != nil {
return scr, false
}
scr = &GPMScreen{scr: scr, gpm: gpm}
go gpmReporter(scr.(*GPMScreen))
return scr, true
}
type GPMScreen struct {
gpm gpmReader
scr
x, y int
}
func (gpm *GPMScreen) PollEvent() tcell.Event {
evt := gpm.scr.PollEvent()
switch evt := evt.(type) {
case *tcell.EventMouse:
// update the mouse cursor
gpm.update(evt)
case *tcell.EventKey:
// remove mouse cursor on keyboard input
gpm.reset()
}
return evt
}
func (gpm *GPMScreen) GetContent(x, y int) (
primary rune, combining []rune, style tcell.Style, width int,
) {
if x != gpm.x || y != gpm.y {
return gpm.scr.GetContent(x, y)
}
primary, combining, style, width = gpm.scr.GetContent(x, y)
return primary, combining, switchReverseAttribute(style), width
}
func (gpm *GPMScreen) SetContent(
x, y int, primary rune, combining []rune, style tcell.Style,
) {
if x != gpm.x || y != gpm.y {
gpm.scr.SetContent(x, y, primary, combining, style)
return
}
gpm.scr.SetContent(
x, y, primary, combining, switchReverseAttribute(style))
}
func (gpm *GPMScreen) EnableMouse() {}
func (gpm *GPMScreen) update(evt *tcell.EventMouse) {
gpm.switchCursor(false)
gpm.x, gpm.y = evt.Position()
gpm.switchCursor(true)
}
func (gpm *GPMScreen) reset() {
gpm.switchCursor(true)
gpm.x, gpm.y = -1, -1
}
func (gpm *GPMScreen) switchCursor(show bool) {
r, _, sty, _ := gpm.scr.GetContent(gpm.x, gpm.y)
if r == 0 {
return
}
sty = switchReverseAttribute(sty)
gpm.scr.SetContent(gpm.x, gpm.y, r, nil, sty)
if show {
gpm.Show()
}
}
func switchReverseAttribute(sty tcell.Style) tcell.Style {
_, _, aa := sty.Decompose()
if aa&tcell.AttrReverse != 0 {
aa &^= tcell.AttrReverse
} else {
aa |= tcell.AttrReverse
}
return sty.Attributes(aa)
}
func gpmReporter(scr *GPMScreen) {
baseTypes := (gpmctl.MOVE | gpmctl.DOWN | gpmctl.UP | gpmctl.DRAG)
for {
evt, err := scr.gpm.Read()
if err != nil {
continue
}
x, y := int(evt.X-1), int(evt.Y-1)
switch evt.Type & baseTypes {
case gpmctl.MOVE:
scr.PostEvent(tcell.NewEventMouse(
x, y,
gpmButtonsToTcell(evt.Buttons),
gpmModifiersToTcell(int(evt.Modifiers)),
))
case gpmctl.DOWN:
scr.PostEvent(tcell.NewEventMouse(
x, y,
gpmButtonsToTcell(evt.Buttons),
gpmModifiersToTcell(int(evt.Modifiers)),
))
case gpmctl.UP:
scr.PostEvent(tcell.NewEventMouse(
x, y,
tcell.ButtonNone,
gpmModifiersToTcell(int(evt.Modifiers)),
))
case gpmctl.DRAG:
scr.PostEvent(tcell.NewEventMouse(
x, y,
gpmButtonsToTcell(evt.Buttons),
gpmModifiersToTcell(int(evt.Modifiers)),
))
}
}
}
var gpmTcellModifiers = map[int]tcell.ModMask{
1: tcell.ModShift,
4: tcell.ModCtrl,
8: tcell.ModAlt,
}
func gpmModifiersToTcell(m int) (tm tcell.ModMask) {
for _, _m := range []int{1, 4, 8} {
if m&_m == 0 {
continue
}
tm = tm | gpmTcellModifiers[_m]
}
return tm
}
var gpmTcellButtons = map[gpmctl.Buttons]tcell.ButtonMask{
gpmctl.B_LEFT: tcell.Button1,
gpmctl.B_RIGHT: tcell.Button2,
gpmctl.B_MIDDLE: tcell.Button3,
gpmctl.B_FOURTH: tcell.Button4,
gpmctl.B_UP: tcell.WheelUp,
gpmctl.B_DOWN: tcell.WheelDown,
}
var gpmButtons = []gpmctl.Buttons{gpmctl.B_LEFT, gpmctl.B_RIGHT, gpmctl.B_MIDDLE,
gpmctl.B_FOURTH, gpmctl.B_UP, gpmctl.B_DOWN}
func gpmButtonsToTcell(b gpmctl.Buttons) (tb tcell.ButtonMask) {
for _, _b := range gpmButtons {
if b&_b != _b {
continue
}
tb |= gpmTcellButtons[_b]
}
return
} |
any updates regarding this issue? |
the gpm package works for me; it doesn't look like as if it gets patched into tcell. |
I just came here from browsh, which is missing GPM support due to this dependency not implementing it. I sadly dont know Go, so I dont think I can really help you fix it... |
+1 on this, I have the same use case of wanting to use browsh on a TTY. @slukits code above appears well-thought-out, but it's not clear to me which files should be patched to implement it. Anyone able to put this into a pull request? |
I'll take a look at this -- it seems like the approach is sound, but maybe we only want to do it on linux. I have questions about how gpmctl works if the user is not actually on the console though -- it would be unfortunate if this became incompatible with coexisting with X sessions from SSH or something like that. |
I was going to see if I could just import the file that @slukits wrote, but it lacks any kind of a license statement, so I cannot just import it. I have some ideas though, so stay tuned. |
I meant this one, that lacks a license but otherwise looks nice: https://github.com/jackdoe/go-gpmctl |
ah no problem, i added MIT license https://github.com/jackdoe/go-gpmctl/blob/master/LICENSE.md |
On 10 December 2023 21:40:15 CET, gdamore ***@***.***> wrote:
I was going to see if I could just import the file that @slukits wrote, but it lacks any kind of a license statement,
Sorry, it's MIT licensed, enjoy, and thanks for tcell!
so I cannot just import it. I have some ideas though, so stay tuned.
… |
MIT license is fine. I will probably build something on this. I just have to find time amongst all my other priorities. |
Browsh is a console browser that depends on tcell. There's an issue in the browsh repository to support GPM clicking, and the conclusion there is that this is ultimately a tcell issue. I searched in this repo, but didn't see a corresponding issue, so I thought I would create one.
(GPM provides mouse/clicking support for tty terminals.)
The text was updated successfully, but these errors were encountered: