Blob Blame History Raw
From 67a4506d4d8a0cbbaca5df4adfc309e54e557aee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@gmail.com>
Date: Tue, 5 Jan 2021 12:04:23 +0100
Subject: [PATCH] screencast: Stop recording when screen size or resource scale
 change

Video encoders don't really handle changing the size of the video, and if
we'd e.g. change resolution while recording, we would end up with a corrupt
video file. Handle this more gracefully by stopping the recording if the
conditions change.
---
 js/ui/screencast.js  | 92 +++++++++++++++++++++++++++++++++++++++++---
 src/shell-recorder.c | 50 ++++++++++++------------
 src/shell-recorder.h |  1 +
 3 files changed, 114 insertions(+), 29 deletions(-)

diff --git a/js/ui/screencast.js b/js/ui/screencast.js
index 0b0b14a8e..54f8fb5ae 100644
--- a/js/ui/screencast.js
+++ b/js/ui/screencast.js
@@ -1,6 +1,6 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
-const { Gio, GLib, Shell } = imports.gi;
+const { Gio, GLib, Meta, Shell } = imports.gi;
 const Signals = imports.signals;
 
 const Main = imports.ui.main;
@@ -53,16 +53,27 @@ var ScreencastService = class {
         this._stopRecordingForSender(name);
     }
 
-    _stopRecordingForSender(sender) {
+    _stopRecordingForSender(sender, closeNow=false) {
         let recorder = this._recorders.get(sender);
         if (!recorder)
             return false;
 
         Gio.bus_unwatch_name(recorder._watchNameId);
-        recorder.close();
+        if (closeNow)
+            recorder.close_now();
+        else
+            recorder.close();
         this._recorders.delete(sender);
         this.emit('updated');
 
+        let connection = this._dbusImpl.get_connection();
+        let info = this._dbusImpl.get_info();
+        connection.emit_signal(sender,
+            this._dbusImpl.get_object_path(),
+            info ? info.name : null,
+            'Stopped',
+            null);
+
         return true;
     }
 
@@ -78,6 +89,53 @@ var ScreencastService = class {
             recorder.set_draw_cursor(options['draw-cursor']);
     }
 
+    _ensureResourceScaleChangedHandler() {
+        if (this._resourceScaleChangedHandlerId)
+            return;
+
+        this._resourceScaleChangedHandlerId =
+            global.stage.connect('notify::resource-scale',
+                () => {
+                    for (let sender of this._recorders.keys()) {
+                        let recorder = this._recorders.get(sender);
+
+                        if (!recorder.is_recording())
+                            continue;
+
+                        this._stopRecordingForSender(sender, true);
+                    }
+                });
+    }
+
+    _ensureMonitorsChangedHandler() {
+        if (this._monitorsChangedHandlerId)
+            return;
+
+        this._monitorsChangedHandlerId = Main.layoutManager.connect('monitors-changed',
+            () => {
+                for (let sender of this._recorders.keys()) {
+                    let recorder = this._recorders.get(sender);
+
+                    if (!recorder.is_recording())
+                        continue;
+
+                    let geometry = recorder._geometry;
+                    let screenWidth = global.screen_width;
+                    let screenHeight = global.screen_height;
+
+                    if (recorder._isAreaScreecast) {
+                        if (geometry.x + geometry.width > screenWidth ||
+                            geometry.y + geometry.height > screenHeight)
+                            this._stopRecordingForSender(sender, true);
+                    } else {
+                        if (geometry.width != screenWidth ||
+                            geometry.height != screenHeight)
+                            this._stopRecordingForSender(sender, true);
+                    }
+                }
+            });
+    }
+
     ScreencastAsync(params, invocation) {
         let returnValue = [false, ''];
         if (!Main.sessionMode.allowScreencast ||
@@ -95,8 +153,20 @@ var ScreencastService = class {
             this._applyOptionalParameters(recorder, options);
             let [success, fileName] = recorder.record();
             returnValue = [success, fileName ? fileName : ''];
-            if (!success)
+            if (success) {
+                recorder._isAreaScreecast = false;
+                recorder._geometry =
+                    new Meta.Rectangle({
+                        x: 0,
+                        y: 0,
+                        width: global.screen_width,
+                        height: global.screen_height
+                    });
+                this._ensureResourceScaleChangedHandler();
+                this._ensureMonitorsChangedHandler();
+            } else {
                 this._stopRecordingForSender(sender);
+            }
         }
 
         invocation.return_value(GLib.Variant.new('(bs)', returnValue));
@@ -131,8 +201,20 @@ var ScreencastService = class {
             this._applyOptionalParameters(recorder, options);
             let [success, fileName] = recorder.record();
             returnValue = [success, fileName ? fileName : ''];
-            if (!success)
+            if (success) {
+                recorder._isAreaScreecast = true;
+                recorder._geometry =
+                    new Meta.Rectangle({
+                        x: x,
+                        y: y,
+                        width: width,
+                        height: height
+                    });
+                this._ensureResourceScaleChangedHandler();
+                this._ensureMonitorsChangedHandler();
+            } else {
                 this._stopRecordingForSender(sender);
+            }
         }
 
         invocation.return_value(GLib.Variant.new('(bs)', returnValue));
diff --git a/src/shell-recorder.c b/src/shell-recorder.c
index 0203ecf1c..e561a0152 100644
--- a/src/shell-recorder.c
+++ b/src/shell-recorder.c
@@ -511,21 +511,6 @@ recorder_update_size (ShellRecorder *recorder)
     }
 }
 
-static void
-recorder_on_stage_notify_size (GObject          *object,
-                               GParamSpec       *pspec,
-                               ShellRecorder    *recorder)
-{
-  recorder_update_size (recorder);
-
-  /* This breaks the recording but tweaking the GStreamer pipeline a bit
-   * might make it work, at least if the codec can handle a stream where
-   * the frame size changes in the middle.
-   */
-  if (recorder->current_pipeline)
-    recorder_pipeline_set_caps (recorder->current_pipeline);
-}
-
 static gboolean
 recorder_idle_redraw (gpointer data)
 {
@@ -622,12 +607,6 @@ recorder_connect_stage_callbacks (ShellRecorder *recorder)
                     G_CALLBACK (recorder_on_stage_destroy), recorder);
   g_signal_connect_after (recorder->stage, "paint",
                           G_CALLBACK (recorder_on_stage_paint), recorder);
-  g_signal_connect (recorder->stage, "notify::width",
-                    G_CALLBACK (recorder_on_stage_notify_size), recorder);
-  g_signal_connect (recorder->stage, "notify::height",
-                    G_CALLBACK (recorder_on_stage_notify_size), recorder);
-  g_signal_connect (recorder->stage, "notify::resource-scale",
-                    G_CALLBACK (recorder_on_stage_notify_size), recorder);
 }
 
 static void
@@ -639,9 +618,6 @@ recorder_disconnect_stage_callbacks (ShellRecorder *recorder)
   g_signal_handlers_disconnect_by_func (recorder->stage,
                                         (void *)recorder_on_stage_paint,
                                         recorder);
-  g_signal_handlers_disconnect_by_func (recorder->stage,
-                                        (void *)recorder_on_stage_notify_size,
-                                        recorder);
 
   /* We don't don't deselect for cursor changes in case someone else just
    * happened to be selecting for cursor events on the same window; sending
@@ -1578,6 +1554,32 @@ shell_recorder_record (ShellRecorder  *recorder,
   return TRUE;
 }
 
+/**
+ * shell_recorder_close_now:
+ * @recorder: the #ShellRecorder
+ *
+ * Stops recording immediately. It's possible to call shell_recorder_record()
+ * again to reopen a new recording stream, but unless change the recording
+ * filename, this may result in the old recording being overwritten.
+ */
+void
+shell_recorder_close_now (ShellRecorder *recorder)
+{
+  g_return_if_fail (SHELL_IS_RECORDER (recorder));
+  g_return_if_fail (recorder->state != RECORDER_STATE_CLOSED);
+
+  recorder_remove_update_pointer_timeout (recorder);
+  recorder_close_pipeline (recorder);
+
+  recorder->state = RECORDER_STATE_CLOSED;
+
+  /* Reenable after the recording */
+  meta_enable_unredirect_for_display (shell_global_get_display (shell_global_get ()));
+
+  /* Release the refcount we took when we started recording */
+  g_object_unref (recorder);
+}
+
 /**
  * shell_recorder_close:
  * @recorder: the #ShellRecorder
diff --git a/src/shell-recorder.h b/src/shell-recorder.h
index c1e0e6368..1c3e6aab4 100644
--- a/src/shell-recorder.h
+++ b/src/shell-recorder.h
@@ -37,6 +37,7 @@ void               shell_recorder_set_area     (ShellRecorder *recorder,
 gboolean           shell_recorder_record       (ShellRecorder  *recorder,
                                                 char          **filename_used);
 void               shell_recorder_close        (ShellRecorder *recorder);
+void               shell_recorder_close_now    (ShellRecorder *recorder);
 void               shell_recorder_pause        (ShellRecorder *recorder);
 gboolean           shell_recorder_is_recording (ShellRecorder *recorder);
 
-- 
2.27.0