charmbracelet / bubbletea

A powerful little TUI framework 🏗

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

kill the process but the mouse code still output in the console

4everSivan opened this issue · comments

Describe the bug
Linux use -9 kill the process but the mouse code still output in the console until run process again and use normal exit.

Setup

  • OS [centos 7.9]
  • Shell [system console]

To Reproduce
Steps to reproduce the behavior:

  1. Run the process.
  2. Kill the process.
  3. See error

Source Code

p := tea.NewProgram(ui, tea.WithAltScreen(), tea.WithMouseAllMotion())
	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}

Expected behavior
nothing output when kill or close the process.

Screenshots
image

I also use bubblezone for my process, i dont know what made it happen🙏

It's expected behavior that the mouse code still output in the console. Use tea.WithMouseAllMotion() will change the terminal state, when you kill -9 <pid>, your process can do nothing but stop immediately. Try use kill -SIGINT <pid> or kill -SIGTERM <pid> which have registered handler to correctly tear down.
Below code show what happen when you use tea.WithMouseAllMotion(). r.out.EnableMouseExtendedMode() changes the behavior of terminal.
https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L506

} else if p.startupOptions&withMouseAllMotion != 0 {
	p.renderer.enableMouseAllMotion()
	p.renderer.enableMouseSGRMode()
}

https://github.com/charmbracelet/bubbletea/blob/master/standard_renderer.go#L383-L388

func (r *standardRenderer) enableMouseSGRMode() {
	r.mtx.Lock()
	defer r.mtx.Unlock()

	r.out.EnableMouseExtendedMode()
}

Below code show why you can stop your program properly use SIGINT and SIGTERM. tea will shutdown when receiving SIGINT or SIGTERM. During shutdown restoreTerminalState is called which calls disableMouse, therefore your terminal works as usual.
https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L212-L246

func (p *Program) handleSignals() chan struct{} {
	ch := make(chan struct{})

	// Listen for SIGINT and SIGTERM.
	//
	// In most cases ^C will not send an interrupt because the terminal will be
	// in raw mode and ^C will be captured as a keystroke and sent along to
	// Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be
	// caught here.
	//
	// SIGTERM is sent by unix utilities (like kill) to terminate a process.
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
		defer func() {
			signal.Stop(sig)
			close(ch)
		}()

		for {
			select {
			case <-p.ctx.Done():
				return

			case <-sig:
				if atomic.LoadUint32(&p.ignoreSignals) == 0 {
					p.msgs <- QuitMsg{}
					return
				}
			}
		}
	}()

	return ch
}

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L316-L328

		case msg := <-p.msgs:
			// Filter messages.
			if p.filter != nil {
				msg = p.filter(model, msg)
			}
			if msg == nil {
				continue
			}

			// Handle special internal messages.
			switch msg := msg.(type) {
			case QuitMsg:
				return model, nil

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L572

p.shutdown(killed)

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L635-649

func (p *Program) shutdown(kill bool) {
	if p.renderer != nil {
		if kill {
			p.renderer.kill()
		} else {
			p.renderer.stop()
		}
	}

	_ = p.restoreTerminalState()
	if p.restoreOutput != nil {
		_ = p.restoreOutput()
	}
	p.finished <- struct{}{}
}

https://github.com/charmbracelet/bubbletea/blob/master/tty.go#L25-L40

func (p *Program) restoreTerminalState() error {
	if p.renderer != nil {
		p.renderer.disableBracketedPaste()
		p.renderer.showCursor()
		p.disableMouse()

		if p.renderer.altScreen() {
			p.renderer.exitAltScreen()

			// give the terminal a moment to catch up
			time.Sleep(time.Millisecond * 10) //nolint:gomnd
		}
	}

	return p.restoreInput()
}

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L299-L303

func (p *Program) disableMouse() {
	p.renderer.disableMouseCellMotion()
	p.renderer.disableMouseAllMotion()
	p.renderer.disableMouseSGRMode()
}

It's expected behavior that the mouse code still output in the console. Use tea.WithMouseAllMotion() will change the terminal state, when you kill -9 <pid>, your process can do nothing but stop immediately. Try use kill -SIGINT <pid> or kill -SIGTERM <pid> which have registered handler to correctly tear down.鼠标代码仍然在控制台中输出是预期的行为。使用 tea.WithMouseAllMotion() 将更改终端状态,当您 kill -9 <pid> 时,您的进程只能立即停止,而不能执行任何操作。尝试使用 kill -SIGINT <pid>kill -SIGTERM <pid> ,它们已注册处理程序以正确终止。 Below code show what happen when you use tea.WithMouseAllMotion(). r.out.EnableMouseExtendedMode() changes the behavior of terminal.以下代码显示了使用 tea.WithMouseAllMotion() 时会发生什么。 r.out.EnableMouseExtendedMode() 更改了终端的行为。 https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L506

} else if p.startupOptions&withMouseAllMotion != 0 {
	p.renderer.enableMouseAllMotion()
	p.renderer.enableMouseSGRMode()
}

https://github.com/charmbracelet/bubbletea/blob/master/standard_renderer.go#L383-L388

func (r *standardRenderer) enableMouseSGRMode() {
	r.mtx.Lock()
	defer r.mtx.Unlock()

	r.out.EnableMouseExtendedMode()
}

Below code show why you can stop your program properly use SIGINT and SIGTERM. tea will shutdown when receiving SIGINT or SIGTERM. During shutdown restoreTerminalState is called which calls disableMouse, therefore your terminal works as usual.以下代码显示了为什么您可以使用 SIGINT 和 SIGTERM 正确停止程序。tea 在收到 SIGINT 或 SIGTERM 时将关闭。在关闭期间调用 restoreTerminalState ,它调用 disableMouse ,因此您的终端像往常一样工作。 https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L212-L246

func (p *Program) handleSignals() chan struct{} {
	ch := make(chan struct{})

	// Listen for SIGINT and SIGTERM.
	//
	// In most cases ^C will not send an interrupt because the terminal will be
	// in raw mode and ^C will be captured as a keystroke and sent along to
	// Program.Update as a KeyMsg. When input is not a TTY, however, ^C will be
	// caught here.
	//
	// SIGTERM is sent by unix utilities (like kill) to terminate a process.
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
		defer func() {
			signal.Stop(sig)
			close(ch)
		}()

		for {
			select {
			case <-p.ctx.Done():
				return

			case <-sig:
				if atomic.LoadUint32(&p.ignoreSignals) == 0 {
					p.msgs <- QuitMsg{}
					return
				}
			}
		}
	}()

	return ch
}

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L316-L328

		case msg := <-p.msgs:
			// Filter messages.
			if p.filter != nil {
				msg = p.filter(model, msg)
			}
			if msg == nil {
				continue
			}

			// Handle special internal messages.
			switch msg := msg.(type) {
			case QuitMsg:
				return model, nil

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L572

p.shutdown(killed)

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L635-649

func (p *Program) shutdown(kill bool) {
	if p.renderer != nil {
		if kill {
			p.renderer.kill()
		} else {
			p.renderer.stop()
		}
	}

	_ = p.restoreTerminalState()
	if p.restoreOutput != nil {
		_ = p.restoreOutput()
	}
	p.finished <- struct{}{}
}

https://github.com/charmbracelet/bubbletea/blob/master/tty.go#L25-L40

func (p *Program) restoreTerminalState() error {
	if p.renderer != nil {
		p.renderer.disableBracketedPaste()
		p.renderer.showCursor()
		p.disableMouse()

		if p.renderer.altScreen() {
			p.renderer.exitAltScreen()

			// give the terminal a moment to catch up
			time.Sleep(time.Millisecond * 10) //nolint:gomnd
		}
	}

	return p.restoreInput()
}

https://github.com/charmbracelet/bubbletea/blob/master/tea.go#L299-L303

func (p *Program) disableMouse() {
	p.renderer.disableMouseCellMotion()
	p.renderer.disableMouseAllMotion()
	p.renderer.disableMouseSGRMode()
}

OK, thanks bro.