From a1288a8f3f112f6a5c6fe3efc3cfd09fd0f6869b Mon Sep 17 00:00:00 2001 From: Ryan Volz Date: Wed, 9 Nov 2022 21:45:51 -0500 Subject: [PATCH] grc: Fix cairo assertion failure by not storing reference to context. The connection flowgraph element previously kept a reference to the cairo context passed to the draw function in order to be able to use cairo's `in_stroke` function to determine if the mouse cursor was on the path of the curved connection line. As it turns out, this is dangerous because GTK is constantly destroying and creating new cairo contexts and surfaces. This avoids keeping a reference to the cairo context by initializing a local cairo context with the connection class for the sole purpose of storing the curved path and calculating `in_stroke`. The local context and surface can be very basic because not much is needed for `in_stroke`, and the context can persist and just have its path replaced when it needs to be updated. On Windows, resizing the GRC window (and particularly the flowgraph canvas) to a larger size than its initial size would cause the underlying cairo surface to be destroyed and a new one created. The cairo context stored for the curved connection line had a reference to that destroyed surface, and closing that context (upon replacing it with a new one) would attempt decrement the reference count on the surface. This would produce an assertion failure that crashed GRC stating `Assertion failed: CAIRO_REFERENCE_COUNT_HAS_REFERENCE (&surface->ref_count)`. On macOS with the Quartz backend, a related crash and assertion error would manifest when connecting blocks. Signed-off-by: Ryan Volz --- gui/canvas/connection.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/gui/canvas/connection.py b/gui/canvas/connection.py index be5db73..0195c23 100644 --- a/gui/canvas/connection.py +++ b/gui/canvas/connection.py @@ -10,6 +10,8 @@ SPDX-License-Identifier: GPL-2.0-or-later from argparse import Namespace from math import pi +import cairo + from . import colors from .drawable import Drawable from .. import Utils @@ -45,8 +47,11 @@ class Connection(CoreConnection, Drawable): self._rel_points = None # connection coordinates relative to sink/source self._arrow_rotation = 0.0 # rotation of the arrow in radians - self._current_cr = None # for what_is_selected() of curved line self._line_path = None + # simple cairo context for curved line and computing what_is_selected + cr = cairo.Context(cairo.RecordingSurface(cairo.CONTENT_ALPHA, None)) + cr.set_line_width(cr.get_line_width() * LINE_SELECT_SENSITIVITY) + self._line_path_cr = cr @nop_write @property @@ -125,12 +130,13 @@ class Connection(CoreConnection, Drawable): cr.curve_to(*(p2 + p3 + p4)) cr.line_to(*p5) self._line_path = cr.copy_path() + self._line_path_cr.new_path() + self._line_path_cr.append_path(self._line_path) def draw(self, cr): """ Draw the connection. """ - self._current_cr = cr sink = self.sink_port source = self.source_port @@ -192,18 +198,11 @@ class Connection(CoreConnection, Drawable): if coor_m: return Drawable.what_is_selected(self, coor, coor_m) - x, y = [a - b for a, b in zip(coor, self.coordinate)] - - cr = self._current_cr - - if cr is None: + if self._line_path is None: return - cr.save() - cr.new_path() - cr.append_path(self._line_path) - cr.set_line_width(cr.get_line_width() * LINE_SELECT_SENSITIVITY) - hit = cr.in_stroke(x, y) - cr.restore() + + x, y = [a - b for a, b in zip(coor, self.coordinate)] + hit = self._line_path_cr.in_stroke(x, y) if hit: return self