How to use removeCallback?

I’m working on a script with a basic UI, and am trying to use Glyphs callbacks for DOCUMENTOPENED, DOCUMENTCLOSED, and DOCUMENTACTIVATED.

Using the basic export callback demo, I can add my custom callback function for DOCUMENTOPENED. But I cannot remove the callback function with .removeCallback(). I’ve tried both of these options:

Glyphs.removeCallback( myCustomCallback )
Glyphs.removeCallback( myCustomCallback,  DOCUMENTOPENED)

Neither seem to work—my callback function continues to run when a new doc opens. I’ve even tried del myCustomCallback to remove my callback function entirely, but it continues to run when a doc opens!

I’m assuming that it’s important to have a removeCallback for any addCallback. Is that true? How critical is it? If my script hits an error/exception and stops working, will Glyphs have any trouble with a callback I added? Please describe how I should add and remove callbacks responsibly. Thanks!

@mekkablue

After researching how event observers and callbacks work in Robofont (which I also have to support for my script), I’ve been able to get this working in Glyphs too. The Glyphs docs don’t mention that removeCallback will only work when used in the same script “run” as the addCallback (I’m not sure “run” is the right term, technically). I had been experimenting with callbacks in the Macro Panel, by running my script with addCallback, then, later, changing it to removeCallback and running the script again. Unlike most things in the Glyphs API, that doesn’t work. addCallback and removeCallback need to be in the same script.

This means that, practically speaking, callbacks are only useful in a class—like I have for a Vanilla window UI. This is a demo of the callbacks working:

# While this little window is open,
# my callback will run when a document opens

from vanilla import Window

class workingCallbackDemo(object):

	def __init__(self):
		self.w = Window((200, 200))
		
		Glyphs.addCallback( self.myCustomCallback, DOCUMENTOPENED )
		
		# Need to bind a method/function to when the window closes,
		# to remove the event callback first
		self.w.bind('close', self.prepareToCloseWindow)

		self.w.open()
		
	def myCustomCallback(self, info):
		openFonts = Glyphs.fonts
		print( 'There are %d fonts open' % len(openFonts) )
		
	def prepareToCloseWindow(self, sender):
		Glyphs.removeCallback( self.myCustomCallback, DOCUMENTOPENED )

workingCallbackDemo()

I wish the documentation mentioned any of this. @mekkablue, can you add some mention of this? I don’t understand the technical side of this well enough to know how to describe it for the documentation.

@GeorgSeifert I also noticed that when the DOCUMENTCLOSED event happens, Glyphs still includes the closed document in Glyphs.fonts. To test that, just use the demo I just posted above, and change the callback to DOCUMENTCLOSED.

Closing the only open document will print “There are 1 fonts open”. Is this a bug? If not, is there any way I can figure out which documents are open when one closes? This is critical to my script.

@GeorgSeifert @mekkablue I really really need an answer to this last question. When the DOCUMENTCLOSED callback runs, I need to know which font documents are now open. Right now, if I call Glyphs.fonts in that callback, I get a list of the fonts that were open before something closed. In other words, DOCUMENTCLOSED tells me that “a document will close”—not that “a document has closed.” I need to know what fonts are open after the document has closed. I can’t work around this by using a Python timer to wait a moment before running Glyphs.fonts.

RoboFont’s fontDidClose callback works as expected: I can get the currently open fonts in the callback and the document that was closed is not included in the list. I really need to know how to accomplish this in Glyphs. Thanks!

If figured out a solution, which I’m posting for anyone who finds this thread needing an answer. I still don’t understand why this workaround should be necessary, and I sincerely hope that Glyphs can change the DOCUMENTCLOSED callback to fire after the document has closed.

The following code manages to reliably figure out which font is being closed, then remove it from the list of open fonts. It expects that the callback function has a keyword argument of info=None (as shown in the Glyphs API docs). This info will be present when a document is closed, and allow us to find the associated window and font that was closed.

# Inside the callback function triggered by DOCUMENTCLOSED...

openFonts = list( Glyphs.fonts )

# figure out which font was just closed and remove it
# from the list of open fonts. Glyphs doesn't do this
# for us like Rofo does.
try :
    fontBeingClosed = info.object().glyphsDocument().font
    if fontBeingClosed in openFonts :
        openFonts.remove( fontBeingClosed )
except :
    pass

I don’t really understand why you need to remove the a font from the openFonts list?

I added a DOCUMENTDIDCLOSE callback (and renamed the DOCUMENTCLOSED to DOCUMENTWILLCLOSE (the old one is still there, of course).

I’ll need to rework the callback handling. The python wrapper tries to be a bit too convenient but is too confusing.

Thanks, @GeorgSeifert. While you’re reworking that, take a look at DOCUMENTACTIVATED—it has the same problem where its callback function gets an out of date Glyphs.fonts list in the following situations:

  • When using ⌘ ` to cycle between windows (the order of fonts doesn’t update)
  • After closing a font document. After the DOCUMENTCLOSED event there’s often a DOCUMENTACTIVATED when another font document/window becomes the active one, but a callback can’t get an up to date Glyphs.fonts list.

I just described why I need this functionality in this other thread: Does each GSFont have a unique ID?. Having a unique ID for each font document won’t solve these callback issues but it will help my script be less dependent on the callbacks working perfectly. For me, the unique ID is the highest priority.

This callbacks are not meant for what you are trying to do. I gave some ideas in the other thread.

Hello, I need urgent help. I have made a total mess of The Callbacks and they are angry.

I wrote a two-line script to test out callbacks in Glyphs:

def updateCallback(info):
	print("hey")
	
Glyphs.addCallback(updateCallback, UPDATEINTERFACE)

I ran the script, found it to work, and started writing a different script utilising this functionality. But when I ran my new script, with a new function self.updateCallback, I found that Glyphs was still printing “hey” every time the interface got updated. I can’t figure out how to remove the original function updateCallback() and link (?) a new function to the callback.

Running Glyphs.removeCallback(updateCallback, UPDATEINTERFACE) doesn’t work.

Please help, my macro window is printing nothing but
hey
hey
hey
hey
hey
hey
hey
hey

If you ran the script from the Macro panel, I suspect that the function definition was also executed multiple times and so now the symbol updateCallback no longer refers to the function that was registered, meaning that you cannot use it to remove the callback.

When using the addCallback and removeCallback methods in a plugin or script should still work.

I’m afraid it doesn’t. I cannot add a new function to the same callback.

Where are you running your code? The Macro panel?

Yes, macro panel. I just tried this:

def update_ui(info):
	print("Test")

Glyphs.addCallback(update_ui, UPDATEINTERFACE)

But my macro panel still only prints hey. I don’t seem to be able to add any other function.

If I first run

def a(info):
	print("A")

Glyphs.addCallback(a, UPDATEINTERFACE)

and then

def b(info):
	print("B")

Glyphs.addCallback(b, UPDATEINTERFACE)

I get both A and B written to the console on any user interface update.

The callback API is build to facilitate calling it from scripts and from plugins. Both have different requirements and in the end there are several issues like this. I recommend that you read the source of the addCallback() and removeCallback() functions in the wrapper. It might give you an idea how it works.

This does not work for me, I only get “hey”.

I’m sorry, that’s a bit too advanced for me. What does that mean?

Is there any way that I can remove all the callbacks that I added? To start from zero again?

Restarting Glyphs fixed the issue.