Plugin improvment - plugin manager

Hi,

Following discussion started in Plugin improvment, I create a new topic to talk about the plugin installation system.

Here some point from original topic:

From this, I started to write a plugin manager.

Please note:

  • This is an earlier version of the plugin, maybe more a Proof Of Concept than a real plugin, there’s some bug and missing functionalities (example: currently don’t manage plugin provided with Krita)
  • The plugin works but there’s still many work to do before having something that I can publish in v1.0.0 (ie: version that you can use)
  • Do not use it on Krita installation you’re using to draw: install and test it on a testing instance

This plugin has been written:

  • To understand how Krita manage the plugin
  • To provide a concrete foundation around which we can work to propose and find solutions

Plugin presentation

The plugin interface looks like this:

Basically:

  • A list of installed plugins
  • A preview to provided Manual
  • 3 Buttons to:
    – Install a new plugin from zip archive
    – Uninstall a plugin
    – Activate/Deactivate a plugin (note: you can also check/uncheck checbox from list)

What’s working?

Consider that plugin xxxx is managed

Install a plugin

The plugin manager doesn’t reinvent wheel, and I mostly be inspired by the Python Plugin Importer:

  1. Load a ZIP archive
  2. Check content validity (xxxx.desktop and xxxx/__init__.py files)
  3. Unzip files to the right place
    – xxxx.desktop in pykrita directory
    – xxxx.action in actions directory (if file exists)
    – xxxx/* in pykrita directory
  4. Activate plugin automatically after installation

Uninstall a plugin

This function is not complete, but currently:

  1. Deactivate plugin if activated
  2. Delete all plugins’ files from disk
    – xxxx.desktop from pykrita directory
    – xxxx.action from actions directory (if file exists)
    – xxxx/* from pykrita directory

Activate a plugin

Ok, here it start to be tricky…
When a plugin is activated:

  1. Update kritarc file using Krita.instance().writeSetting() method to set plugin active
[python]
enable_xxxx=true
  1. Load module (xxxx/__init__.py file)
  2. Analyse loaded extensions, and for extensions that has been loaded from plugin xxxx
    – Execute setup() method if exists
    – Execute createActions() method if exists
  3. Probably, the plugin have created a new entry in menu…
    – Search for new action(s)
    – If found, do the freaky tweak to add action in menu
    (because it seems that actions are created only at Krita’s startup)

From here, the plugin is loaded and ready to be used by user in Krita, without need to restart :slight_smile:

Note

  • For plugins using Docker, unfortunately I’m currently not able to activate docker without having to restart Krita
  • Looking in Settings > Configure Krita > Python Plugin Manager the newly installed/activated plugin is not available yet: I think the plugin list is loaded at startup and is not refreshed when opened

Deactivate plugin

This function is not completely operational

  1. Update kritarc file using Krita.instance().writeSetting() method to set plugin inactive
[python]
enable_xxxx=false
  1. Unload all modules from plugin xxxx
    This is not fully operational, and might never be fully operational

Note:

  • Once again, concerning plugins with Docker, I currently can’t do anything (no time to dig more)
  • Removing action from menu is not possible because there’s currently no way to determinate which plugin added which menu entry
    – I can implement a method, but it will work only for plugin that have been installed/activated by plugin manager…
  • Looking in Settings > Configure Krita > Python Plugin Manager the newly uninstalled/deactivated plugin status is not available yet: I think the plugin list is loaded at startup and is not refreshed when opened

What can be improved in Krita?

Now, to be able to continue on this plugin, I think we need to think further about some points.

From my point of view, are currently concerned:

  • The Extension class
  • The DockWidget class
  • The Window class
  • The Settings > Configure Krita > Python Plugin Manager window

The Extension class

Currently, the Extension class provide 2 methods (that does nothing by default):

  • setup()
  • createActions()

Here is a proposition of methods that an Extension could provides:

Extension.id()
New method

  • Return the plugin Id that have created the extension
  • Note: a plugin can create more than one extension class, but all have the same plugin id
  • The goal is to be able to identify easily which plugin create which extension

Extension.actions()
New method

  • Return list of actions created by extension (this will allow to remove them from menu easily when plugin is deactivated)

Extension.addAction(id, name, location)
New method

  • Create a new action for extension
  • Basically:
    – do a call to Window.createAction()
    – store action in a list internal to extension
    – return action

Extension.removeAction(id)
New method

  • Remove action created by extension and identified by id (does nothing if tries to remove an actions that has not been created by extension)

Extension.createActions()
Unchanged method

  • Called at Krita’s startup
  • Called when plugin is activated

Extension.setup()
Unchanged method

  • Called at Krita’s startup
  • Called when plugin is activated

Extension.cleanup()
new method

  • Called when plugin is deactivated
  • Allows plugin to made some cleanup and avoid having stuff created by plugin everywhere
    For example, if plugin have created files and/or directories to work, delete them

Example of an extension:

class MyExtension(Extension):
    def __init__(self, parent, pluginId=None):
        super().__init__(parent, pluginId)
        
    def setup(self):
        # executed when plugin is activated
        pass
        
    def cleanup(self):
        # executed when plugin is deactivated
        pass

    def createActions(self, window)
        # ==> unchanged, except the method to create an action
        action = self.addAction("my_extension_id", "My extension")
        action.triggered.connect(self.do_some_stuff)

Krita.instance().addExtension(MyExtension(Krita.instance(), 'my_extension_id))

The DockWidget class

Currently I’m not able to propose anything as I never used them.
But from what I saw with activation/deactivation plugin, we need something that allow to create/delete docker dynamically, not only at Krita startup.

The Window class

For now, I think to a have function that allows to apply change to window menu.
Something like menuUpdate()
Anyway, a function that is able to apply changes to menu and add/remove actions that has been added/removed by plugin

The Settings > Configure Krita > Python Plugin Manager window

This window is currently not aware of changes.
So, when the window is displayed, it should:

  • update the list of installed plugins
  • take in account changes made in kritarc file (plugins enabled/disabled)

Another thing is:

Don’t have the time now the formalise this point properly, but to ensure that a plugin manager can manage all plugins, there’s might be some norms to write about a plugin zip archive content.
I think that 95% already exists (desktop file, init.py file, …) but I have in mind few additional things that I’ll try to expose later.


Any idea/comment to improve how plugins can be installed/deinstalled without having to restart are welcome :slight_smile:

@EyeOdin, concerning docker I think you’re the one who have created the most docker plugin?
If you already have some idea about improvment of DockWidget class for a better integration in a plugin management system, please share :wink:

@Kapyia I don’t know if you have wishes about this part? Concerning API bugs&improvements, I will create a new topic later (I need some times to formalize what’s in my mind) but you can always create a topic and/or prepare your wishlist to share it when the topic will be created :wink:

Note: topic is loooong to read, sorry :slight_smile:

Grum999

4 Likes

Item to add to list (improve Python Plugins/Extensions/Dockers):

  • Krita.loadPlugin(“PluginName”): makes sure that plugin with given name is loaded. Currently plugin dependancy asks user to load some plugin & restart Krita. (User may become bit angry when PluginA asks PluginB ask PluginC asks PluginD asks …) and each step user must click enabled & restart.

/AkiR

Sorry for the late response I did not notice the question for me when I got the notice.

What comes more up to mind is more on the teaching how to use the docker class in the Krita site where I think is a very convoluted method and not practical. Mostly how the code is distributed through out the various files and how to load the UI. I just think the UI file should be preserved and not converted to a python file as they say only on smaller scripts it is worth.

Some other stuff not sure if it helps with Dockers directley but I guess might for Plugins in general:

  • load addon without reset but I think it might be impossible request due to PyQt5. but lowering the amount of reboots after installing would be amazing, and having the “Import Python Plugin” active by default would be nice. Sometimes I go to a live session of Linux to test something and I need to brute force through a bunch of reboots (x3reboots) and the inability to boot Krita as it keeps crashing constantly (x20crashes x3reboots = 60reboots). Just One reboot or without reboot would be so so nice but everyone knows about it already.
  • the docker name changes when it is floating. goes from “Plugin” to “&Plugin”. it is really strange behaviour I know it is nice to identificate it’s state but it is not intuitive. strangely on Linux I don’t see it on the Header wich is curious. Also non floating dockers work faster for some weird reason and I don’t know why.
  • Restricted to signals inside the docker you create. But I think it is just a PyQt5 thing that is hard coded on hard parenting works there. I was still hoping to have the API open those Qt events like brush up and down. Dockers are like a isolated island.
  • One thing that has crossed my mind before is to make like 2 dockers connect to each other. I know this sounds stupid but might unlock some stuff. But might be a silly idea. So like pre register or a code for another another class or something so they could talk? Don’t ask I wake up with weird ideas when I wake up.
  • One thing not related to dockers that has been on my mind recently is “Modal Operators” like in blender. I made a post already but I think it is really hard to implement :frowning: I thought about it to make stuff like on mouse cardinal options and other things that run operators until the user gives a input. The event system on Blender is just so much more better.
  • Another thing is having Plugins organized each in their own folders like all files inside it and not have the desktop file floating around mingling. I think this would help creation as much as distribuition. for the user it would be unzip and drag the folder here. for the creator it would help it sync with github since everything was contained inside the project folder.