Skip to content

Commit bf1216d

Browse files
authoredFeb 25, 2025··
fix: windows: enable mouse mode on demand (#1340)
Since we added native Windows API input events support, we always enable mouse events even if the user doesn't want them. This is a problem because it breaks applications that don't expect mouse events and selection stops working without any modifier key. Fixes: #1313
1 parent e817654 commit bf1216d

File tree

4 files changed

+49
-9
lines changed

4 files changed

+49
-9
lines changed
 

‎inputreader_other.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ import (
99
"github.com/muesli/cancelreader"
1010
)
1111

12-
func newInputReader(r io.Reader) (cancelreader.CancelReader, error) {
12+
func newInputReader(r io.Reader, _ bool) (cancelreader.CancelReader, error) {
1313
return cancelreader.NewReader(r)
1414
}

‎inputreader_windows.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type conInputReader struct {
2525

2626
var _ cancelreader.CancelReader = &conInputReader{}
2727

28-
func newInputReader(r io.Reader) (cancelreader.CancelReader, error) {
28+
func newInputReader(r io.Reader, enableMouse bool) (cancelreader.CancelReader, error) {
2929
fallback := func(io.Reader) (cancelreader.CancelReader, error) {
3030
return cancelreader.NewReader(r)
3131
}
@@ -38,11 +38,21 @@ func newInputReader(r io.Reader) (cancelreader.CancelReader, error) {
3838
return fallback(r)
3939
}
4040

41-
originalMode, err := prepareConsole(conin,
42-
windows.ENABLE_MOUSE_INPUT,
41+
modes := []uint32{
4342
windows.ENABLE_WINDOW_INPUT,
4443
windows.ENABLE_EXTENDED_FLAGS,
45-
)
44+
}
45+
46+
// Since we have options to enable mouse events, [WithMouseCellMotion],
47+
// [WithMouseAllMotion], and [EnableMouseCellMotion],
48+
// [EnableMouseAllMotion], and [DisableMouse], we need to check if the user
49+
// has enabled mouse events and add the appropriate mode accordingly.
50+
// Otherwise, mouse events will be enabled all the time.
51+
if enableMouse {
52+
modes = append(modes, windows.ENABLE_MOUSE_INPUT)
53+
}
54+
55+
originalMode, err := prepareConsole(conin, modes...)
4656
if err != nil {
4757
return nil, fmt.Errorf("failed to prepare console input: %w", err)
4858
}

‎tea.go

+27-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"io"
1717
"os"
1818
"os/signal"
19+
"runtime"
1920
"runtime/debug"
2021
"sync"
2122
"sync/atomic"
@@ -183,6 +184,9 @@ type Program struct {
183184
// fps is the frames per second we should set on the renderer, if
184185
// applicable,
185186
fps int
187+
188+
// mouseMode is true if the program should enable mouse mode on Windows.
189+
mouseMode bool
186190
}
187191

188192
// Quit is a special command that tells the Bubble Tea program to exit.
@@ -413,9 +417,25 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
413417
// mouse mode (1006) is a no-op if the terminal doesn't support it.
414418
p.renderer.enableMouseSGRMode()
415419

420+
// XXX: This is used to enable mouse mode on Windows. We need
421+
// to reinitialize the cancel reader to get the mouse events to
422+
// work.
423+
if runtime.GOOS == "windows" && !p.mouseMode {
424+
p.mouseMode = true
425+
p.initCancelReader(true) //nolint:errcheck
426+
}
427+
416428
case disableMouseMsg:
417429
p.disableMouse()
418430

431+
// XXX: On Windows, mouse mode is enabled on the input reader
432+
// level. We need to instruct the input reader to stop reading
433+
// mouse events.
434+
if runtime.GOOS == "windows" && p.mouseMode {
435+
p.mouseMode = false
436+
p.initCancelReader(true) //nolint:errcheck
437+
}
438+
419439
case showCursorMsg:
420440
p.renderer.showCursor()
421441

@@ -579,6 +599,11 @@ func (p *Program) Run() (Model, error) {
579599
p.renderer.enableMouseAllMotion()
580600
p.renderer.enableMouseSGRMode()
581601
}
602+
603+
// XXX: Should we enable mouse mode on Windows?
604+
// This needs to happen before initializing the cancel and input reader.
605+
p.mouseMode = p.startupOptions&withMouseCellMotion != 0 || p.startupOptions&withMouseAllMotion != 0
606+
582607
if p.startupOptions&withReportFocus != 0 {
583608
p.renderer.enableReportFocus()
584609
}
@@ -607,7 +632,7 @@ func (p *Program) Run() (Model, error) {
607632

608633
// Subscribe to user input.
609634
if p.input != nil {
610-
if err := p.initCancelReader(); err != nil {
635+
if err := p.initCancelReader(false); err != nil {
611636
return model, err
612637
}
613638
}
@@ -763,7 +788,7 @@ func (p *Program) RestoreTerminal() error {
763788
if err := p.initTerminal(); err != nil {
764789
return err
765790
}
766-
if err := p.initCancelReader(); err != nil {
791+
if err := p.initCancelReader(false); err != nil {
767792
return err
768793
}
769794
if p.altScreenWasActive {

‎tty.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,14 @@ func (p *Program) restoreInput() error {
7575
}
7676

7777
// initCancelReader (re)commences reading inputs.
78-
func (p *Program) initCancelReader() error {
78+
func (p *Program) initCancelReader(cancel bool) error {
79+
if cancel && p.cancelReader != nil {
80+
p.cancelReader.Cancel()
81+
p.waitForReadLoop()
82+
}
83+
7984
var err error
80-
p.cancelReader, err = newInputReader(p.input)
85+
p.cancelReader, err = newInputReader(p.input, p.mouseMode)
8186
if err != nil {
8287
return fmt.Errorf("error creating cancelreader: %w", err)
8388
}

0 commit comments

Comments
 (0)
Please sign in to comment.