import karamba import re from commands import getoutput ########################################################################### # This is a collection of functions trying to work around the problems with dcop usage # in SuperKaramba. For the most part, just giving DCOP commands works, # from both: command line and importing kde/qt libs methodn. # what does NOT WORK : asking superkaramba itself through dcop for answers. # Using normal methods, comand like # "dcop sk-1234 qt find SomeWidget" will lock up SK theme solid. # # There is only one, true and tested approach to asking something from SuperKaramba # through DCOP - pushing the requests out of karamba.executeInteractive # and catching and assembling the responses by hand. # # This module tries to make it easier to ask particularly common questions # from SuperKaramba over DCOP: # - What other superkaramba themes are running in ALL of superkaramba threads? # - Is there a copy on this same theme running somewhere in other SuperKaramba thread? # - How can I close the right theme, or control how many copies of this # theme are running at the same time? # - Is there life on Mars? errr... wait a minute... print "dcophelper imported" class executedThread: ''' This class name is a bit counter-intuitive. It is not the thread itself, but the setup for processing of what was returnd by a thread. As you can see, we don't run the actual thread here. An instance of this class only behaves as an instruction on what to do when the labeled thread returns. You can assign these classes as labels to a dict type of running-threads database inventory. ''' def __init__(self, arguments=None): ''' In here the most important part is the set up of the function that will be run when out thread is signalled as "returned" arguments - a dict type with only 2 keys: 'fn' - will hold a function object 'args' - will hold a dict of arguments specific for that funciton. please, make sure that function is ready to process arguments passed as one dict object. ''' ###print "executedThread: initing args ", arguments self.function = None # will be assigned a funciton object. Make sure NOT to pass () as part of function name. self.args = {} # will be assigned a dict type with vars to be passed to function when run. if arguments: self.function = arguments['fn'] if arguments.has_key('args'): self.args.update(arguments['args']) def returned(self, arguments=None): ''' This is the most important part of this class. Whatever processes the data, after it returns, it calls this method of that particular instance of this class. Note the self.function assignment in the __init__ section. ''' ###print "executedThread: returning args ", arguments if arguments: self.args.update(arguments) if self.function: self.function(self.args) class dcophelper: def __init__(self, widget, function2call): ###print "widget is ", widget, hex(widget) # public, can rely on. self.ready = False self.homethread = '' self.themename_unique = '' # this will be filled in automatically based on marker. self.themename = '' # the long name is "unique" as it contains " - \number" at the end. self.themelist = {} self.function2call = function2call # the most important part. That is how we know the vales are ready. self.competitor = {} # will be used as signal that other threads of same theme are running. # private, may change self.__skrawdata__ = {} # will be populated by collectOutput self.__threads__ = {} # these are querry thread tags for catching executeInteractive output self.__sknames__ = getoutput('dcop | grep superkaramba').splitlines() self.__markerObj__ = karamba.createMenu(widget) schstring = '(?<=qt/karamba - )[^/]+(?=/unnamed\d+\(KPopupMenu, '+hex(self.__markerObj__)+')' # print 'dcophelper: __sknames__: ', self.__sknames__ # testin if all lines of output start with "superkaramba" line = self.__sknames__.__len__() - 1 trash = [] while line > -1: if self.__sknames__[line][:12] != "superkaramba": trash.append(self.__sknames__.pop(line)) line -= 1 if trash: print "Output polluted by the following message:\n", '\n'.join(trash) self.__sknames__ = [] args = { 'widget':widget, 'message':'Output polluted. Stopping.', 'message_long':'Your system is broken.\nCommand line output is polluted by the following message:\n'+'\n'.join(trash)+'\nFix your system, complain to its packager, and try again.', } self.__reportBack__(args, flag='warning') # adding dcop requests as separate threads to the SuperKaramba's executeInteractive # each time one thread returns, __collectOutput__ is called # note each thread may produce multiple bursts of output for the same request. for skname in self.__sknames__: #print 'dcophelper: starting queries, skname is ', skname mycommand = ["dcop", skname, "qt", "find", "KPopupMenu"] self.__threads__[karamba.executeInteractive(widget, mycommand)] = executedThread({'fn':self.__collectOutput__,'args':{'widget':widget,'skname':skname}}) # while threads are working, we will do extra computations. self.__nameExtractor__ = re.compile('(?<=qt/karamba - )[^/]+(?=/unnamed)') self.__markerExtractor__ = re.compile(schstring) def __collectOutput__(self, args): # print '\n__collectOutput__:\n', args, '\n' # just storing the current output chunk in history. if self.__skrawdata__.has_key(args['skname']): # sometimes the output is split and comes in in portions # so, we need to reassemble the parts self.__skrawdata__[args['skname']]= self.__skrawdata__[args['skname']]+'\n'+args['output'] # print "collectOutput: detected multi-chunk output for ", args['skname'] else: self.__skrawdata__[args['skname']] = args['output'] # print "collectOutput: detected first chunk output for ", args['skname'] # Parsing ONLY CURRENT output for theme names (and end of output marker) # and adding them to the self.themelist # it is a dict type with "long" theme names as keys, # and superkaramba thread names as values. # example: {'A-Foto -1':'superkaramba-12354','A-Foto - 2':'superkaramba-43323'} # this will be exposed to the user. last = '' for line in args['output'].splitlines(): # print "collectOutput: name-scanning :", line if line.find('qt/ThemesLayout/unnamed') > -1: # end of output for this thread try: self.__sknames__.remove(args['skname']) except: pass # we will test for the lendth of __sknames__ later and quit if 0 # print "collectOutput: detected the end of thread's output" break else: try: name = self.__nameExtractor__.search(line).group() except: name = None # print "collectOutput: name is :", name if name and name != last: # print "collectOutput: new theme name. Documenting." self.themelist.update({name:args['skname']}) last = name #print "collectOutput: themelist is ", self.themelist, type(self.themelist) # this is ran on every Output chunk, until we find out "this is us" marker. # print "collectOutput: themename exists ", bool(self.themename_unique) if not self.themename_unique: if self.__markerExtractor__.search(args['output']): self.themename_unique = self.__markerExtractor__.search(args['output']).group() self.themename = self.themename_unique.split()[0] self.homethread = args['skname'] # print "collectOutput: found our OWN name: ", self.themename_unique # assuming "this is us" marker is set, this is ran after every output chunk came in. # self.themelist is update with new names every time so we just check there. # our goal is to detect a presence of another theme with the same "short" name # we are looking for name, with no " - \[number]" in the end. # DETECTING IF WE ARE NOT ALONE if (not self.competitor) and self.themename_unique and self.themelist.__len__() > 1: if ','.join(self.themelist.keys()).count(self.themename_unique.split()[0]) > 1: self.competitor['skThread'] = None # we need to short-assign it here because of threading # if we don't do it in time, another thread will start the same. # at least that will evaluate to True # print "collectOutput: found a competing afoto theme" self.competitor['skThread'] = self.findTwin() args['message'] = 'Another A-Foto detected.' args['message_long'] = 'A-Foto is already running\nUse main SuperKaramba window to start one more A-Foto.' self.__reportBack__(args, 'warning') # DETECTING IF THIS IT THE END OF OUTPUT if self.__sknames__.__len__() < 1: self.__reportBack__(args, 'done') def __reportBack__(self, args, flag='done'): try: competeThread = self.competitor['skThread'] except: competeThread = None values = { 'themenameunique':self.themename_unique, 'themename':self.themename, 'themelist':self.themelist, 'homethread':self.homethread, 'competeThread':competeThread, 'flag':flag, } args.update(values) if flag == 'done': karamba.deleteMenu(args['widget'], self.__markerObj__) self.__markerObj__ = None # this will be our "already reported" marker. self.function2call(args['widget'], args) def findTwin(self): # note, we are INTOLERANT of theme names with spaces. Spaces are name-cutting markers. # if spaces are used in theme name, we are screwed across the board. values = None for theme in self.themelist.keys(): if theme.split()[0] == self.themename: # we found our guy. Now we make sure it's not us. :) if theme != self.themename_unique: values = self.themelist[theme] return values def __exec__(self, mycommand): ''' Takes a string and passes it as a command ''' #karamba.execute('dcop '+sk+' KarambaIface closeTheme "'+appName+'"') #karamba.execute('dcop '+sk+' KarambaIface quit') karamba.execute(mycommand) def closeTheme(self, themename=None): ''' Really tries to guess what you are trying to say. Returns bool for success / fail. If themename is not passed, killing ourselves. ''' if not themename: if self.themename: # killing ourselves mycommand = 'dcop '+self.homethread+' KarambaIface closeTheme '+self.themename self.__exec__(mycommand) else: keys = self.themelist.keys() #keys.__delitem__(keys.index(self.themename_unique)) if ','.join(keys).count(themename) == 1: mycommand = 'dcop '+self.themelist[themename]+' KarambaIface closeTheme '+themename self.__exec__(mycommand) # ###########################################################################