From b0f01cedea68893e85a3fd852c0521238c285695 Mon Sep 17 00:00:00 2001
From: ycsi <195134806+ycsi@users.noreply.github.com>
Date: Sat, 29 Aug 2026 19:47:04 +0800
Subject: [PATCH] feat(capture): toggle mouse pointer via
capture.video.show_pointer (#691)
* feat(capture): toggle mouse pointer via capture.video.show_pointer
- config: register capture.video.show_pointer config option
- config: default pointer to visible (ShowPointer: true) on the
hardcoded fallback pipeline, override applies only when the flag is
explicitly set, so per-pipeline YAML overrides stay untouched
- compose: document NEKO_CAPTURE_VIDEO_SHOW_POINTER example
* fix(capture): apply show_pointer to all pipelines
- capture: thread capture.video.show_pointer into broadcast and
screencast gstreamer pipelines, previously hardcoded show-pointer=true
and silently ignored the flag
- config: resolve capture.video.show_pointer once into
Capture.VideoShowPointer so all pipeline builders share one value
- docs: document capture.video.show_pointer in configuration/capture.md,
including its reach into broadcast and screencast, and regenerate
help.txt/help.json
- compose: drop stray commented-out example, default compose file is
not meant to list every option
---
server/internal/capture/manager.go | 8 ++++----
server/internal/config/capture.go | 25 ++++++++++++++++++++-----
webpage/docs/configuration/capture.md | 2 ++
webpage/docs/configuration/help.json | 10 ++++++++++
webpage/docs/configuration/help.txt | 1 +
5 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/server/internal/capture/manager.go b/server/internal/capture/manager.go
index db8bc69e..5872c90f 100644
--- a/server/internal/capture/manager.go
+++ b/server/internal/capture/manager.go
@@ -101,12 +101,12 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
"! queue "+
"! voaacenc bitrate=%d "+
"! mux. "+
- "ximagesrc display-name=%s show-pointer=true use-damage=false "+
+ "ximagesrc display-name=%s show-pointer=%v use-damage=false "+
"! video/x-raw "+
"! videoconvert "+
"! queue "+
"! x264enc threads=4 bitrate=%d key-int-max=15 byte-stream=true tune=zerolatency speed-preset=%s "+
- "! mux.", url, config.AudioDevice, config.BroadcastAudioBitrate*1000, config.Display, config.BroadcastVideoBitrate, config.BroadcastPreset,
+ "! mux.", url, config.AudioDevice, config.BroadcastAudioBitrate*1000, config.Display, config.VideoShowPointer, config.BroadcastVideoBitrate, config.BroadcastPreset,
), nil
}, config.BroadcastUrl, config.BroadcastAutostart),
screencast: screencastNew(config.ScreencastEnabled, func() string {
@@ -116,12 +116,12 @@ func New(desktop types.DesktopManager, config *config.Capture) *CaptureManagerCt
}
return fmt.Sprintf(
- "ximagesrc display-name=%s show-pointer=true use-damage=false "+
+ "ximagesrc display-name=%s show-pointer=%v use-damage=false "+
"! video/x-raw,framerate=%s "+
"! videoconvert "+
"! queue "+
"! jpegenc quality=%s "+
- "! appsink name=appsink", config.Display, config.ScreencastRate, config.ScreencastQuality,
+ "! appsink name=appsink", config.Display, config.VideoShowPointer, config.ScreencastRate, config.ScreencastQuality,
)
}()),
diff --git a/server/internal/config/capture.go b/server/internal/config/capture.go
index 972c090f..ff7fa727 100644
--- a/server/internal/config/capture.go
+++ b/server/internal/config/capture.go
@@ -28,9 +28,10 @@ const (
type Capture struct {
Display string
- VideoCodec codec.RTPCodec
- VideoIDs []string
- VideoPipelines map[string]types.VideoConfig
+ VideoCodec codec.RTPCodec
+ VideoIDs []string
+ VideoPipelines map[string]types.VideoConfig
+ VideoShowPointer bool
AudioDevice string
AudioCodec codec.RTPCodec
@@ -100,6 +101,11 @@ func (Capture) Init(cmd *cobra.Command) error {
return err
}
+ cmd.PersistentFlags().Bool("capture.video.show_pointer", true, "show mouse pointer in captured video, overrides show_pointer of all video pipelines")
+ if err := viper.BindPFlag("capture.video.show_pointer", cmd.PersistentFlags().Lookup("capture.video.show_pointer")); err != nil {
+ return err
+ }
+
// broadcast
cmd.PersistentFlags().Int("capture.broadcast.audio_bitrate", 128, "broadcast audio bitrate in KB/s")
if err := viper.BindPFlag("capture.broadcast.audio_bitrate", cmd.PersistentFlags().Lookup("capture.broadcast.audio_bitrate")); err != nil {
@@ -369,8 +375,9 @@ func (s *Capture) Set() {
s.VideoCodec = codec.VP8()
s.VideoPipelines = map[string]types.VideoConfig{
"main": {
- Fps: "25",
- GstEncoder: "vp8enc",
+ Fps: "25",
+ GstEncoder: "vp8enc",
+ ShowPointer: true,
GstParams: map[string]string{
"target-bitrate": "round(3072 * 650)",
"cpu-used": "4",
@@ -400,6 +407,14 @@ func (s *Capture) Set() {
log.Warn().Msg("you are setting both single video pipeline and multiple video pipelines, ignoring single video pipeline")
}
+ s.VideoShowPointer = viper.GetBool("capture.video.show_pointer")
+ if viper.IsSet("capture.video.show_pointer") {
+ for k, p := range s.VideoPipelines {
+ p.ShowPointer = s.VideoShowPointer
+ s.VideoPipelines[k] = p
+ }
+ }
+
// audio
s.AudioDevice = viper.GetString("capture.audio.device")
s.AudioPipeline = viper.GetString("capture.audio.pipeline")
diff --git a/webpage/docs/configuration/capture.md b/webpage/docs/configuration/capture.md
index 4ab998c5..00bfd15d 100644
--- a/webpage/docs/configuration/capture.md
+++ b/webpage/docs/configuration/capture.md
@@ -35,6 +35,7 @@ The Gstreamer pipeline is started when the first client requests the video strea
"capture.video.ids",
"capture.video.pipeline",
"capture.video.pipelines",
+ "capture.video.show_pointer",
]} comments={false} />
- is the name of the [X display](https://www.x.org/wiki/) that you want to capture. If not specified, the environment variable `DISPLAY` will be used.
@@ -42,6 +43,7 @@ The Gstreamer pipeline is started when the first client requests the video strea
- is a list of pipeline ids that are defined in the section. The first pipeline in the list will be the default pipeline.
- is a shorthand for defining [Gstreamer pipeline description](#video.gst_pipeline) for a single pipeline. This is option is ignored if is defined.
- is a dictionary of pipeline configurations. Each pipeline configuration is defined by a unique pipeline id. They can be defined in two ways: either by building the pipeline dynamically using [Expression-Driven Configuration](#video.expression) or by defining the pipeline using a [Gstreamer Pipeline Description](#video.gst_pipeline).
+- overrides for every video pipeline, only if explicitly set (per-pipeline `show_pointer` in `config.yaml` is left untouched when this flag is unset). It also controls the mouse pointer for the [Broadcast](#broadcast) and [Screencast](#screencast) pipelines, which do not have a per-pipeline setting of their own.
### Expression-Driven Configuration {#video.expression}
diff --git a/webpage/docs/configuration/help.json b/webpage/docs/configuration/help.json
index 53d7bf6b..12a2e58a 100644
--- a/webpage/docs/configuration/help.json
+++ b/webpage/docs/configuration/help.json
@@ -192,6 +192,16 @@
"defaultValue": {},
"description": "pipelines config used for video streaming"
},
+ {
+ "key": [
+ "capture",
+ "video",
+ "show_pointer"
+ ],
+ "type": "boolean",
+ "defaultValue": "true",
+ "description": "show mouse pointer in captured video, overrides show_pointer of all video pipelines"
+ },
{
"key": [
"capture",
diff --git a/webpage/docs/configuration/help.txt b/webpage/docs/configuration/help.txt
index 31e76192..bf621d79 100644
--- a/webpage/docs/configuration/help.txt
+++ b/webpage/docs/configuration/help.txt
@@ -18,6 +18,7 @@
--capture.video.ids strings ordered list of video ids
--capture.video.pipeline string shortcut for configuring only a single gstreamer pipeline, ignored if pipelines is set
--capture.video.pipelines string pipelines config used for video streaming (default "{}")
+ --capture.video.show_pointer show mouse pointer in captured video, overrides show_pointer of all video pipelines (default true)
--capture.webcam.device string v4l2sink device used for webcam (default "/dev/video0")
--capture.webcam.enabled enable webcam stream
--capture.webcam.height int webcam stream height (default 720)