I want to create a restart addon: please help me

sometimes krita looses connection to the wintab driver and sometimes i have a strange performance issue and lag , only a restart of krita helps. But it is tedious to save all the files. In Blender i have a restart addon and its a god sent when anything goes wrong.

i already found several restart codes which kinda work. but id like to save the session first and reload the session exactly how it was before the restart. (also without the restore window)

means i have to

1)run the backup functions (which krita function to save a whole session ? or if not possible how to list all opened documents ?)
2)collect the filenames of the backups
3) restart krita with those files as arguments (lets see hmm)

if we could walk through this would be very nice. Its only a few lines and one loop i guess. but extremely powerfull and worryfree for the user if a bug arises.

edit: if we could add a menu item to the file menu that would be the icing on the cake :slight_smile:

You are aware that Krita has a session saving option? But do a forum-search, it’s discussed just a few days ago, if I remember correctly.

Michelist

Edit: Although I believe this is not available until Krita 5.x. I have just not considered that.

yes i need to trigger a session save . ill search for it

I found one Topic about the restart thing >> Restart Krita but there were more this year.

Michelist

This is just me saying that it would be nice to have krita restart as a core feature.
But there is no info about session saving option.

Krita.instance().action('file_sessions').trigger()
This opens the sessions window.

You can also get the file names that are closed via:
Krita.instance().notifier().imageClosed.connect(function)

Though easiest way you can also get them all is: (the get the fileName() )
Krita.instance().documents()

But do note that if the document was never saved, it would have a blank name. So you would have to make temp saves.

Krita has a commandline to start:

But it may make more sense to just open the documents via API

Krita.instance().openDocument

1 Like

cool thanx for the reply , yes if i cannot just restart krita with a few files as arguments, saving and loading a txt file with the filenames in it might be the second best solution

the session loader doesnt bring back unsaved files …

Krita.instance().documents also just returns saved documents … seems harder to impossible atm … i also struggle becasue i cant do “Krita. tab” anywhere. how do it get all the methods and properties i could eventually use?

Then you will have to take care of it yourself via:

for doc in Krita.instance().documents():
    doc.saveAs("FILE NAME")

You can figure out what is saved or not via:

for doc in Krita.instance().documents():
    if doc.fileName():
        print (doc.fileName()," IS A SAVED FILE")
    else:
        print ( "THIS DOC IS UNSAVED!" )

As for getting all the properties of everything, try this:

or git source.

1 Like

ok

Krita.instance().documents()[0]

has a filename bu does it also have a backupFileName?

and then theres

Krita.instance().action(‘save_incremental_backup’).trigger()

how do they relate how to get the backupfilename ? and which logic does krita use to reload files after program crash ? right i cant even find a backup file in my temp folder lol

You have to implement this by yourself
Basically :

  • list documents in krita

  • do a save as for each document
    – you have to define a backup name by yourself

  • save list of closed document list in a text file

  • restart krita

  • at startup, if text file with list exist
    – load documents designed in list
    – delete text file

Grum999

The first is the API of Krita, the 2nd is using Krita’s internal actions. You trigger an incremental backup.

Overall, as mentioned it is probably best if you make your own backups.

You just have to make your own decisions on how you plan to handle some of the things such as:

  1. Do you also make a backup of existing files, or do you just save over the originals.
  2. Where you wish to store the backups

As for where Krita does their saving, it is mentioned here:

Also, you have the option to use writeSettings and readSettings for storing the locations of the files.

A quick plugin, to restart Krita and reopen documents

Provided “provided as is” , no file to download, you have to create plugin files by yourself

Code is under GPL v3 license :slight_smile:

Installation

In krita/pykrita directory

Step 1)

Create file restart.desktop with following content

Code
[Desktop Entry]
Type=Service
ServiceTypes=Krita/PythonPlugin
X-KDE-Library=restart
X-Python-2-Compatible=false
X-Krita-Manual=Manual.html
Name=Restart
Comment=Restart plugin

Step 2)

Create sub-directory restart

Step 3)

In sub-directory restart, create file __init__.py with following content

Code
from .restart import Restart

# And add the extension to Krita's list of extensions:
app = Krita.instance()
# Instantiate your class:
extension = Restart(parent=app)
app.addExtension(extension)

Step 4)

In sub-directory restart, create file restart.py with following content

Code
from PyQt5.Qt import *
from krita import *

import re
import os
import sys
import random
import subprocess

EXTENSION_ID = 'pykrita_restart'
MENU_ENTRY = 'Restart Krita'

class Restart(Extension):

    ACTION_CANCEL=0
    ACTION_SAVE_DOC=1
    ACTION_IGNORE_DOC=2
    
    __FILE_AFTER_RESTART="filesAfterRestart.txt"

    def __init__(self, parent):
        """Initialise plugin"""
        super().__init__(parent)
        # determinate path where to save temp files 
        # prefer a local applicaion directory rather than a /tmp directory
        paths=QStandardPaths.standardLocations(QStandardPaths.AppDataLocation)
        if len(paths):
            self.__tempPath=paths[0] 
        else:
            self.__tempPath=QStandardPaths.standardLocations(QStandardPaths.HomeLocation)[0]
        self.__fileAfterRestart=os.path.join(self.__tempPath, Restart.__FILE_AFTER_RESTART)
        # openeded documents dictionnary 
        self.__docs={}

    def setup(self):
        """Executed at Krita startup, beofre main window is created"""
        # define a trigger, to let plugin reopen document as soon as window is created
        Krita.instance().notifier().windowCreated.connect(self.onWindowCreated)        

    @pyqtSlot()
    def onWindowCreated(self):
        """Slot executed when Krita window is created"""
        if os.path.isfile(self.__fileAfterRestart):
            # if a file with list of document exists, process it to open documents
            Krita.instance().activeWindow().qwindow().setCursor(Qt.WaitCursor)
            
            with open(self.__fileAfterRestart, 'r') as file:
                fileContent=file.read()
            
            # there's one file per line
            files=fileContent.split("\n")
            for fileName in files:
                # just be sure to avoid empty lines..
                if fileName!='':
                    # open document and attach it to current window
                    doc=Krita.instance().openDocument(fileName)
                    Krita.instance().activeWindow().addView(doc)
                    
                    if re.search(r"\\tempDocToDelete_\d{4}_\d{6}\.kra$", fileName):
                        # this is a temp file, delete it
                        os.remove(fileName)
                        # remove name (as it was when Krita has been restarted)
                        # -- works on Windows, not on Linux...
                        # -- need to find a way to fix this in Linux
                        doc.setFileName("")
            
            # remove file with list of document (be sure to not reopened it on next "normal" Krita close/start)
            os.remove(self.__fileAfterRestart)
            
            Krita.instance().activeWindow().qwindow().unsetCursor()

    def createActions(self, window):
        """Add menu entry for plugin"""
        action = window.createAction(EXTENSION_ID, MENU_ENTRY, "tools/scripts")
        action.triggered.connect(self.actionRestart)        
        
    def actionRestart(self):
        """Execute restart action"""
        # check if documents are opened, and ask for user confirmation
        action=self.__checkOpenedDocuments()
        if action==Restart.ACTION_CANCEL:
            # do not restart... 
            return
        elif action==Restart.ACTION_SAVE_DOC:
            # user asked for an automatic save of documents
            self.__saveDocuments()
        
        # close documents before restart (and define file with list of document to reopen)
        self.__closeDocuments()
            
        # -- restart --
        if sys.platform=='win32':
            # running on Windows
            readyToRestart=self.__restartOsWindow()
        elif sys.platform=='linux':
            readyToRestart=self.__restartOsLinux()
        else:
            # need to implement restart process for Linux, MacOS
            readyToRestart=False
        
        if readyToRestart:
            # ok, possible to restart
            QApplication.quit()
        else:
            QMessageBox.warning(None, "Restart Krita", "Unable to initialise restart process\nAction cancelled")


    def __restartOsLinux(self):
        """Linux specific process to restart Krita 
        
        Might be OK on *nix environment...
        """
        kritaPid=os.getpid()
        pidCheckCmd=f"ps -p {kritaPid} -o cmd --no-headers"
        kritaPath=os.popen(pidCheckCmd).read().replace("\n","")
        
        shCmd=f"sh -c 'while [ $({pidCheckCmd}) ]; do sleep 0.5; done; {kritaPath}&'&"
        os.system(shCmd)
        
        return True


    def __restartOsWindow(self):
        """Windows 10 specific process to restart Krita 
        
        Might be OK on Windows 11, maybe Ok on Windows 7...
        """
        kritaPid=os.getpid()
        kritaPath=sys.executable

        # note: 
        #   following "EncodedCommand":
        #       cABhAHIAYQBtACAAKAANAAoAIAAgAFsAUABhAHIAYQBtAGUAdABlAHIAKABNAGEAbgBkAGEAdABvAHIAeQA9ACQAdAByAHUAZQApAF0AWwBpAG4AdABdACQAawByAGkAdABhAFAAaQBkACwADQAKACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAPQAkAHQAcgB1AGUAKQBdAFsAcwB0AHIAaQBuAGcAXQAkAGsAcgBpAHQAYQBQAGEAdABoAA0ACgApAA0ACgANAAoAWwBiAG8AbwBsAF0AJABsAG8AbwBwAD0AJABUAHIAdQBlAA0ACgB3AGgAaQBsAGUAKAAkAGwAbwBvAHAAKQAgAHsADQAKACAAIAAgACAAdAByAHkAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAJABwAD0ARwBlAHQALQBQAHIAbwBjAGUAcwBzACAALQBpAGQAIAAkAGsAcgBpAHQAYQBQAGkAZAAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAJwBTAHQAbwBwACcADQAKACAAIAAgACAAIAAgACAAIABTAHQAYQByAHQALQBTAGwAZQBlAHAAIAAtAE0AaQBsAGwAaQBzAGUAYwBvAG4AZABzACAANQAwADAADQAKACAAIAAgACAAfQANAAoAIAAgACAAIABjAGEAdABjAGgAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAIwAgAG4AbwB0ACAAZgBvAHUAbgBkAA0ACgAgACAAIAAgACAAIAAgACAAIwAgAGUAeABpAHQAIABsAG8AbwBwAA0ACgAgACAAIAAgACAAIAAgACAAJABsAG8AbwBwAD0AJABGAGEAbABzAGUADQAKACAAIAAgACAAfQANAAoAfQANAAoADQAKAEkAbgB2AG8AawBlAC0ARQB4AHAAcgBlAHMAcwBpAG8AbgAgACQAawByAGkAdABhAFAAYQB0AGgA
        #   is a base64 encoded powershell script
        #   """
        #   param (
        #     [Parameter(Mandatory=$true)][int]$kritaPid,
        #     [Parameter(Mandatory=$true)][string]$kritaPath
        #   )
        #
        #   [bool]$loop=$True
        #   while($loop) {
        #       try {
        #           $p=Get-Process -id $kritaPid -ErrorAction 'Stop'
        #           Start-Sleep -Milliseconds 500
        #       }
        #       catch {
        #           # not found
        #           # exit loop
        #           $loop=$False
        #       }
        #   }
        #
        #   Invoke-Expression $kritaPath
        #   """
        #
        #   ==> recommended: decode Base64 string by yourself to be sure of its content :-P
        #   
        cmdParameters=f"/c powershell -noprofile -ExecutionPolicy bypass -command '{kritaPid}', '{kritaPath}' | powershell -noprofile -ExecutionPolicy bypass -EncodedCommand cABhAHIAYQBtACAAKAANAAoAIAAgAFsAUABhAHIAYQBtAGUAdABlAHIAKABNAGEAbgBkAGEAdABvAHIAeQA9ACQAdAByAHUAZQApAF0AWwBpAG4AdABdACQAawByAGkAdABhAFAAaQBkACwADQAKACAAIABbAFAAYQByAGEAbQBlAHQAZQByACgATQBhAG4AZABhAHQAbwByAHkAPQAkAHQAcgB1AGUAKQBdAFsAcwB0AHIAaQBuAGcAXQAkAGsAcgBpAHQAYQBQAGEAdABoAA0ACgApAA0ACgANAAoAWwBiAG8AbwBsAF0AJABsAG8AbwBwAD0AJABUAHIAdQBlAA0ACgB3AGgAaQBsAGUAKAAkAGwAbwBvAHAAKQAgAHsADQAKACAAIAAgACAAdAByAHkAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAJABwAD0ARwBlAHQALQBQAHIAbwBjAGUAcwBzACAALQBpAGQAIAAkAGsAcgBpAHQAYQBQAGkAZAAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAJwBTAHQAbwBwACcADQAKACAAIAAgACAAIAAgACAAIABTAHQAYQByAHQALQBTAGwAZQBlAHAAIAAtAE0AaQBsAGwAaQBzAGUAYwBvAG4AZABzACAANQAwADAADQAKACAAIAAgACAAfQANAAoAIAAgACAAIABjAGEAdABjAGgAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAIwAgAG4AbwB0ACAAZgBvAHUAbgBkAA0ACgAgACAAIAAgACAAIAAgACAAIwAgAGUAeABpAHQAIABsAG8AbwBwAA0ACgAgACAAIAAgACAAIAAgACAAJABsAG8AbwBwAD0AJABGAGEAbABzAGUADQAKACAAIAAgACAAfQANAAoAfQANAAoADQAKAEkAbgB2AG8AawBlAC0ARQB4AHAAcgBlAHMAcwBpAG8AbgAgACQAawByAGkAdABhAFAAYQB0AGgA"
        return QProcess.startDetached("cmd", [cmdParameters])

    def __checkOpenedDocuments(self):
        """Check if there's documents opened and ask user for confirmation
        
        Build a dictionnary of documents name <> document instance
        """
        self.__docs={}
        numTempDoc=1
        numModified=0
        for doc in Krita.instance().documents():
            if doc.fileName()=='':
                # doc not yet saved, produce a tmp filename
                fileName=os.path.join(self.__tempPath, f"tempDocToDelete_{numTempDoc:04}_{random.randint(1,999999):06}.kra")
                self.__docs[fileName]=doc
            else:
                self.__docs[doc.fileName()]=doc
            
            if doc.modified():
                numModified+=1

        if numModified==0:
            userAnswer=QMessageBox.question(None, "Restart Krita", "Do you really want to restart Krita?", QMessageBox.Yes|QMessageBox.No)
            if userAnswer==QMessageBox.No:
                return Restart.ACTION_CANCEL
            else:
                # no documents modified, ignore 
                return Restart.ACTION_IGNORE_DOC
        else: 
            userAnswer=QMessageBox.question(None, "Restart Krita", "There's some unsaved documents\nSave documents before restarting Krita?", QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel)
            if userAnswer==QMessageBox.Cancel:
                return Restart.ACTION_CANCEL
            elif userAnswer==QMessageBox.Yes:
                return Restart.ACTION_SAVE_DOC
            else:
                return Restart.ACTION_IGNORE_DOC

    def __closeDocuments(self):
        """Close all documents from dictionnary
        
        Save document list in temporary text file
        """
        docList=[]
        for docName in self.__docs:
            if doc:=self.__docs[docName]:
                if doc.fileName()!='':
                    docList.append(doc.fileName())
                doc.close()
                
        if len(docList)>0:
            with open(self.__fileAfterRestart, 'w') as file:
                file.write('\n'.join(docList))

    def __saveDocuments(self):
        """Save opened document that are in modified status"""
        for docName in self.__docs:
            if doc:=self.__docs[docName]:
                if doc.modified():
                    if doc.fileName()=='':
                        doc.setFileName(docName)
                    doc.save()

Step 5)

Start Krita, go in Settings > Configure Krita, then Python Plugin Manager and activate Restart plugin

Restart Krita

Plugin is now active; go in Tools > Script > Restart Krita to restart Krita


Plugin is practically complete
Maybe some bugs to fix, or some stuff to improve, especially concerning “not saved” document
But you now have a complete basis to build your own plugin


Works on Windows 10 & Linux
Not tested on OSX

Grum999

4 Likes

wow dude amazing ! thanx ill try it out later when im back at my pc …

I just forgot, this works only on Krita 5
It will not work with Krita 4

Grum999

dude you are amazing , it does everything … especially the unsaved docs. Thank you so much !

it would probably be cooler to also restart the already named files from a temporary backup too. replace filename with real filename after restart and you could save whenever you want after the restart(or not). would need a json export or reading every second line in the textfile or something though for saving both the backup filename and the real filename.

but thats nitpicking. there are many equal options like saving an incremental version or whatever
do you make a github repo?

edit: with a bit more work it would also be a better session restorer (restore after crash , pc crash or program crash) than the build in one to be honest. that one often fails to work and doesnt restore your whole stuff after crash anyways.

That’s why I wrote the plugin is practically complete, because many things can be improved sure :sweat_smile:

But also, the reason why I didn’t provided it as a “ready to install” solution on my github repo is because I currently don’t have the time to work on a such plugin, and not have the time to ensure maintenance, improvement and user help for that.

I’ve provided a basic implementation with solution for a restart on Linux/Windows (the most “complicated” thing of this plugin) + a basic reopen document management as example

Maybe one day I’ll have time to work on that, but currently I’ll let anybody interested in this idea to improve it :slight_smile:

My github if you’re interested:

Grum999

ahh ok you´re the krita guru ^^.

ok ive now implemented the json export/import and few other small tweaks .

but now im stuck again, id like to save the modified state and set it after a file has been reloaded … is there any way to set it to True?? ive tried with setproperty and setattr but to no avail.

if theres no function for this maybe i could just toggle another arbitrary value back and forth to fake it?

I’m not :sweat_smile:

@KnowZero does more than me with Krita :slight_smile:

You can determinate if file is in modified state or not with modified() method of a document.

But there’s no possibility to set it manually through python API.

The only solution you have is to modify a document without “impact”.

One method to modify a document without any impact is:

from krita import *

d=Krita.instance().activeDocument()
d.setAnnotation("Temp annotation to delete just to set modify state", "temp", b'')
d.removeAnnotation("Temp annotation to delete just to set modify state")

So with this, the document is in modified state, but nothing has been really modified.

Grum999

1 Like

ok thank you! nice now i have the script exactly like i wanted to :

modified and unsaved documents get saved to a tempfile, saved unmodified documents (just opened and not touched) get loaded from the original location/file … it all runs without any user interaction , i dont wanna be forced to choose a filename when im restarting , krita just opens like it closed . the only thing missing is the history , but thats expected …

thats one script down… the next one ive always missed in every other app is a document specific colorhistory, colorhistory is the most undervalued digital painting asset imo , if its possible to read and write the history it should also be easy to program i hope.

edit: but the restarting function doesnt work on my workstation pc , only on my laptop, both win10. tommorrow ill look into it

edit2: nahh i somehow have trouble posting the code here … ill upload to github if its ok for you

1 Like