Ubuntu 26.04: Virtual desktops no longer update wthout damage

I’m developing a VR application using that allows you to create and view virtual screens in VR. My project works perfectly on Ubuntu 24.04 but now with 26.04 to my dismay it’s unusable. I’m using GStreamer and pipewire for the heavy lifting but I’ve already ruled them out as potential issues. I’m using the exact same GStreamer and pipewire versions in both 24.04 and 26.04 so it’s not that. This leads me to believe it’s a GNOME issue.

On Ubuntu 24 moving the mouse around updates immediately creating fluid motion. On Ubuntu 25 it updates smoothly as well but for unrelated reasons relating to a “mutter guard window” it’s unusable there as well. On Ubuntu 26 moving the mouse creates no motion at all. Only actions or damage will cause updates.

I’m using gnome-remote-desktop/src/org.gnome.Mutter.ScreenCast.xml at master · jadahl/gnome-remote-desktop · GitHub which I know is private and subject to change to create and manage monitors. I’ve also tried xdg-desktop-portal/data/org.freedesktop.portal.ScreenCast.xml at main · flatpak/xdg-desktop-portal · GitHub which still exhibits the same incorrect behavior.

Can someone explain why you’ve made it update on damage only and how I tell it not to? I need my streamed mouse movements back.

Here is a script that can be used to show how broken this is.

#!/usr/bin/python3

monitors_config=[(1920,1080,60)]

import sys
import signal
import dbus
from gi.repository import GLib
from dbus.mainloop.glib import DBusGMainLoop
from functools import partial

import gi
gi.require_version('Gst', '1.0')
from gi.repository import GObject, Gst

DBusGMainLoop(set_as_default=True)
Gst.init(None)

loop = GLib.MainLoop()

bus = dbus.SessionBus()
screen_cast_iface = 'org.gnome.Mutter.ScreenCast'
screen_cast_session_iface = 'org.gnome.Mutter.ScreenCast.Session'

screen_cast = bus.get_object(screen_cast_iface,
                             '/org/gnome/Mutter/ScreenCast')
session_path = screen_cast.CreateSession([], dbus_interface=screen_cast_iface)
print("session path: %s"%session_path)
session = bus.get_object(screen_cast_iface, session_path)

streams=[]
pipelines=[]

def terminate(monitor_idx):
    global pipelines
    print("pipeline: " + str(pipelines[monitor_idx]))
    if pipelines[monitor_idx] is not None:
        print("draining pipeline")
        pipelines[monitor_idx].send_event(Gst.Event.new_eos())
        pipelines[monitor_idx].set_state(Gst.State.NULL)
    print("stopping")
    

def on_message(bus, message, monitor_idx):
    global pipelines
    type = message.type
    print("message pipeline: " + str(pipelines[monitor_idx]))
    if type == Gst.MessageType.EOS or type == Gst.MessageType.ERROR:
        partial(terminate, monitor_idx=monitor_idx)
        session.Stop(dbus_interface=screen_cast_session_iface)
        loop.quit()

def on_pipewire_stream_added(node_id, monitor_idx):
    global pipelines
    global monitors_config
    print("added", monitors_config[monitor_idx])

    format_element = "video/x-raw,max-framerate=%d/1,width=%d,height=%d !"%(
      int(monitors_config[monitor_idx][2]), int(monitors_config[monitor_idx][0]), int(monitors_config[monitor_idx][1]))
    pipelines[monitor_idx] = Gst.parse_launch('pipewiresrc path=%u ! %s videoconvert ! glimagesink'%(
        node_id, format_element))
    pipelines[monitor_idx].set_state(Gst.State.PLAYING)
    pipelines[monitor_idx].get_bus().connect('message', partial(on_message, monitor_idx=monitor_idx))

for cfg, idx in zip(monitors_config, range(len(monitors_config))):
  stream_path = session.RecordVirtual(
      dbus.Dictionary({'is-platform': dbus.Boolean(True, variant_level=1),
                       'cursor-mode': dbus.UInt32(1, variant_level=1)}, signature='sv'),
      dbus_interface=screen_cast_session_iface)
  streams.append(bus.get_object(screen_cast_iface, stream_path))
  pipelines.append(None)
  streams[-1].connect_to_signal("PipeWireStreamAdded", partial(on_pipewire_stream_added, monitor_idx=idx))

session.Start(dbus_interface=screen_cast_session_iface)

try:
    loop.run()
except KeyboardInterrupt:
    print("interrupted")
    for idx in range(len(monitors_config)):
      terminate(idx)
    session.Stop(dbus_interface=screen_cast_session_iface)
    loop.quit()


Running this creates a virtual desktop and opens a view of the screen. Move your mouse onto the virtual monitor and observe how it doesn’t update until you click or right click. Now drag a window around on it and notice how damage causes fluid updates.

Now run this same script on Ubuntu 24 or 25. The mouse updates fluidly without issue.

Adding export MUTTER_DEBUG_DISABLE_HW_CURSORS=1 to ~/.profile and logging in again seems to fix it but this isn’t an ideal solution at all.

can you oen an issue on gitlab ?