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 <ryan.volz@gmail.com>
This commit is contained in:
Ryan Volz 2022-11-09 21:45:51 -05:00 committed by mormj
parent 32fa6b4e0a
commit a1288a8f3f

View File

@ -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