Skip to content

Listeners

MF366 edited this page Aug 6, 2024 · 7 revisions

What are Listeners?

Listeners (BETA) were introduced in v11.0.0. You create one in a plugin and then when it listens to the target, it runs the output.

Creating a Listener

Let's say we want to show a popup saying "Saved at PATH" everytime a user Saves As.

The syntax would therefore be:

my_listener = Listener(bytes("my_very_own_plugin", "utf-8"), save_as_file, my_already_defined_func)

Going Over the Arguments

  • id: a unique ID for your listener. It should contain the name of your plugin and the version of it in order not to immitate an existing ID. This ID is compressed and then base64 encoded.
  • target: this is the function you want to listen. This should be a WriterClassic function but in theory, you can use a function made by your plugin. It is out of this page's scope to explain how to do so.
  • output: this is the function that runs when the target is detected. It can be a lambda.

Introducing the Condition

There's another argument (condition) which is optional and, by default, is set to a string containing True.

When the target is run, the output runs. However, if the condition is evaluated as False, it does not run.

The condition can access the WriterClassic globals as readonly and is re-evaluated each time the listener runs. This means you can work with variables that update in the condition argument.

With out new knowledge, let's add a condition.

Just to showcase it let's use random. Of course you should do an actual condition though.

my_listener = Listener(bytes("my_very_own_plugin", "utf-8"), save_as_file, my_already_defined_func, "random.randint(0, 100) > 50")

Basically, we set so this listener only runs 50% of the times save_as_file gets called.

Activating the Listener

Creating a Listener does not activate it... yet.

First, you must choose whether you want a BEFORE or AFTER Listener.

Before Listeners are run as soon as target runs.

After Listeners are run before the funtion ends.*

* IMPORTANT NOTE: sometimes, After Listeners are run much before the function ends. Unlike Before Listeners, After Listeners are more complex. Each function has its own approach when it comes to After Listeners. Certain functions support Before Listeners but not After Listeners.

In this case, let's use an After Listener.

Here's how to do it:

after_listeners.add_listeners({my_listener})

If you wanted to use a Before Listener, the variable would be before_listeners and not after_listeners.

Return Value Meaning Possible?
0 Sucess! Yes
9 Listener Subgroup Already Exists No, as add_listeners checks for this condition
10 There's another Listener with the same ID Yes

Verifying the existence of a Listener

Let's say you want to avoid the error code 10 from the previous code. One thing you can do is check if the ID is already taken.

Since there is no function for doing this operation, you can do the following workaround (which is what WriterClassic does internally):

if after_listeners.edit_listener(my_listener.listener_id, 4) == 2:
    after_listeners.add_listeners({my_listener}) # yay, we can use this ID

else:
    print('ID is taken.')

Why does this code work? Check the next heading so you understand.

Using the edit_listener() function

This function is very powerful.

It can be used for thse main things:

  • Verifying if an ID is taken
  • Disabling a Listener
  • Re-enabling a Listener
  • Deleting a Listener for good

Here's an example that shows off all these features:

if after_listeners.edit_listener(my_listener.listener_id, 4) == 2:
    after_listeners.add_listeners({my_listener}) # yay, we can use this ID

else:
    print('ID is taken.')
    quit()

input() # confirm before continuing
after_listeners.edit_listener(my_listener.listener_id, 1) # Out: 0

input() # confirm before re-enabling
after_listeners.edit_listener(my_listener.listener_id, 2) # Out: 0

input() # confirm before continuing
after_listeners.edit_listener(my_listener.listener_id, 7) # Out: 5

input() # 
after_listeners.edit_listener(my_listener.listener_id, 3) # Out: the listener

print('Done!')

Long code, right?

We are gonna take a look at 2 things:

  • Operation Types (the second argument of edit)
  • Return Values
Operation Type What does it do?
<1 Verify ID and do nothing
1 Stop/disable Listener
2 Re-enable Listener
3 Delete Listener
>3 Verify ID and do nothing
Anything other than an integer Verify ID and do nothing
Return Value Meaning Which operations might return it?
An instance of Listener() Operation 3 was sucessful! 3
0 Sucess! 1 and 2
2 The ID could not be found. Any
3 The Listener was already enabled. 2
4 The Listener was already disabled. 1
5 Invalid Operation Type* Anything other than 1, 2 or 3

* NOTE: 5 only gets returned if the operation is invalid AND 2 wasn't returned before. This is the reason why you can use this function to verify if an ID exists.

Running a Listening Group

Most times you don't want to do this as Listening Groups are ran automatically when the target is ran.

Let's say you want to run a Listening Group manually. For this exmaple, let's go back to our simple code.

my_listener = Listener(bytes("my_very_own_plugin", "utf-8"), save_as_file, my_already_defined_func)

if after_listeners.edit_listener(my_listener.listener_id, 4) == 2:
    after_listeners.add_listeners({my_listener}) # yay, we can use this ID

else:
    quit()

after_listeners.run_group(save_as_file)

And... BOOM! Our listener shall run.

Introducing AutoPurge

This run function also has an optional argument: autopurge. By default, it is True. Unless you have a real good reason, you want to keep this as True.

after_listeners.run_group(save_as_file, True) # keeping this as True

When AutoPurge is enabled, it gets rid of all the Listeners that are mal-functioning.

Possible Outputs of run_group()

Return Value Meaning AutoPurge might be...
0 Sucess! (No errors or purges) True or False
1 Sucess! (Errors but no purges) False
6 Tried to run the disabled group; not allowed. True or False
7 The group does not exist. True or False
8 Sucess! (Errors and purges) True

Removing a Group

Let's say you want to remove a function from being listened (it can still be listened to later; this just gets rid of all listeners listening to it).

We can do it this way:

after_listeners.remove_group(save_as_file)

Done!

Return Value Meaning
A set (type set) containing several instances of Listener Sucess!
11 The group does not exist.
12 Cannot remove the inactive group. (Only happens if you set group to the inactive group)

Remove all groups

If removing a single group isn't enough, you can remove ALL.*

* NOTE: all except the Inactive group.

The syntax is even simpler:

after_listeners.remove_all_groups() # no arguments needed

This will always return 0, meaning Sucess!

Other Aspects

  • after_listeners and before_listeners are global WriterClassic variables; both are instances of FunctionListeners()
  • Even if the user quits your plugin, the function might still be cached. This is not very reliable, however. You should, instead, keep the plugin running. The way you do so is up to you.
  • Pay attention that not all functions can be auto listenned to.
  • It's, in theory, possible to do a workaround and get a circular listenning to work. However it will get the app into an infinite loop. Please don't do so.