Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Something like applicationWillResignActive?
-
@ts said:
I can’t correct my topic name
If you edit the first post of your topic, the title text becomes editable
-
You should be able to swizzle those methods. See my swizzle function in my objc_hacks repo.
-
@JonB So this is pretty new to me though I can sort of see what’s going on, however I’m not sure it will work (I’m not getting a a type_encoding), also can you still run the old method code and the new method code instead of replacing the old code?
sad = objc_util.UIApplication.sharedApplication().delegate() cls_p = objc_util.c.object_getClass(sad.ptr) cls = objc_util.ObjCInstance(cls_p) type_encoding = cls.instanceMethodSignatureForSelector_(objc_util.sel("applicationWillResignActive"))
-
@ts here a little example of how I use @jonB 's swizzle without declaring encoding and also calling original code (if you are interested by the script it-self, see the pointed topic at first line)
# https://forum.omz-software.com/topic/6244/reverse-engineering-challenge-to-cvp import swizzle from objc_util import * import urllib.parse def initWithURL_(_self,_sel, _url): '''called with an nsurl. lets try hijacking the url, to show google''' url = ObjCInstance(_url) print(url) if 'https' in str(url): i = str(url).find('http') t = str(url)[i:] url = nsurl(t) #print(url) elif 'myzip://' in str(url): i = str(url).find('myzip://') t = str(url)[i+2:] url = nsurl(t) #print(url) self=ObjCInstance(_self) # PA2QuickHelpContentViewController rtnval = self.originalinitWithURL_(url) return rtnval.ptr cls=ObjCClass('PA2QuickHelpContentViewController') swizzle.swizzle(cls,'initWithURL:',initWithURL_) cls2 = ObjCClass('PA2QuickHelpViewController') def setSearchResults_(_self,_sel,_search_results): self=ObjCInstance(_self) # PA2QuickHelpViewController search_term = str(self.searchTerm()).lower() search_results = ObjCInstance(_search_results) #print(search_results) new_search_results = [] for elem in search_results: new_search_results.append(ns(elem)) # Assume you have your own doc as zipped tree of html files doc_zip = '/private/var/containers/Bundle/Application/34BAEE1A-BC33-4D6F-A0C1-B733E4991F31/Pythonista3.app/Documentation.zip' import zipfile with zipfile.ZipFile(doc_zip, 'r') as zipObj: # Get list of files names in zip listOfiles = zipObj.namelist() for elem in listOfiles: if elem.startswith('py3') and elem.endswith('.html'): content = zipObj.read(elem).decode('UTF-8').lower() lines = content.split('\n') for line in lines: if line.find(search_term) >= 0: my_path = 'myzip://' + doc_zip + '/' + elem new_search_results.append(ns({'path':my_path, 'rank':10, 'title':search_term, 'type':'mod'})) #print(my_path,line) break # Assume you have your own doc on the web new_search_results.append(ns({'path':"https://github.com/mikaelho/pythonista-gestures", 'rank':10, 'title':"gestures", 'type':'mod'})) #print('search:',self.searchTerm(),'results=',new_search_results) self.originalsetSearchResults_(new_search_results) swizzle.swizzle(cls2,'setSearchResults:',setSearchResults_)
-
@ts on second thought, it would be safer to just register a notification observer, since otherwise you risk messing with whatever pythonista does when it calls that (saving files?)
I have a more general notification_capture.py that lets you capture Notifications for a time, and you can hit the hime button, etc, and see what gets posted.
But for this specifically, you can register for specific ones. You can use this, and just add your code into the
callback
method.from objc_util import * import time,ui import console from functools import partial NSNotificationCenter=ObjCClass('NSNotificationCenter') class WillResignNotificationObserver(object): def __init__(self, name=ns('UIApplicationWillResignActiveNotification')) : self.center=NSNotificationCenter.defaultCenter() self.name=name self._observer=None self._blk=None self.queue=ObjCClass('NSOperationQueue').new() self.queue.setName_(ns('test')) '''define your own callback here''' @ui.in_background def callback(self, name, obj, userInfo): print(time.asctime()+name) '''the objc block signature. do not modify''' def _block(self,_cmd,notification): try: name=str(ObjCInstance(notification).name()) obj=ObjCInstance(notification).object() userInfo=ObjCInstance(notification).userInfo() self.callback(name,obj, userInfo) except Exception as ex: print(ex) '''start observing''' def start(self): print(time.asctime()+ ' starting') if self._observer: raise Exception('observer already started') self._blk=ObjCBlock(self._block, restype=None, argtypes=[c_void_p,c_void_p]) self._observer = \ self.center.addObserverForName_object_queue_usingBlock_(self.name,None,self.queue,self._blk) retain_global(self) def stop(self): print(time.asctime()+ ' stopping') import objc_util if self._observer: self.center.removeObserver_(self._observer) self._observer=None release_global(self) if __name__=='__main__': g=WillResignNotificationObserver() g.start() print('observer started. type g.stop() to ensure you stop observing')
-
@cvp Oh I didn’t add the colon at the end of the selector name, now I have a type_encoding. Also I see now, that’s actually cool, I didn’t think about the “original” + selector addition (c.class_addMethod). In the method, you did
rtnval = self.originalinitWithURL_(url) return rtnval.ptr
Is this common to return the pointer or?
-
@JonB Woah! I just tried it out, that’s actually awesome! Thank you :)
-
@ts All ObjCInstance and ObjCClass have a .ptr attribute (in the python version of the object) that has a pointer to the actual ObjC object. In older versions of objc_util, you sometimes had to return ptr when you wanted to return an ObjCInstance. I think the underlying objc_util now implements the appropriate ctype methods so this gets automatically done, but there may still be some cases where you have to return the .ptr -- I forget.
-
@JonB masterful, as usual
-
So modified it a bit to allow other notifications
import objc_util import time class NotificationObserver: def __init__(self, name): self.name = objc_util.ns(name) self.center = objc_util.ObjCClass("NSNotificationCenter").defaultCenter() self._observer = None self._blk = None self.queue = objc_util.ObjCClass('NSOperationQueue').new() self.queue.setName_(objc_util.ns('test')) self.state = False '''define your own callback here''' @objc_util.ui.in_background def callback(self, name, obj, userInfo): print(time.asctime() + name) self.state = True '''the objc block signature. do not modify''' def _block(self, _cmd, notification): try: self.ni = objc_util.ObjCInstance(notification) self.ni_name = self.ni.name() self.ni_obj = self.ni.object() self.ni_ui = self.ni.userInfo() self.callback(self.ni_name.__str__(), self.ni_obj, self.ni_ui) except Exception as ex: print(ex) '''start observing''' def start(self): #print(time.asctime() + ' starting') if self._observer: raise Exception('observer already started') self._blk = objc_util.ObjCBlock(self._block, restype=None, argtypes=[objc_util.c_void_p, objc_util.c_void_p]) self._observer = self.center.addObserverForName_object_queue_usingBlock_(self.name, None, self.queue, self._blk) objc_util.retain_global(self) def stop(self): #print(time.asctime() + ' stopping') if self._observer: self.center.removeObserver_(self._observer) self._observer = None objc_util.release_global(self)
To test it, I did
from NotificationObserver import NotificationObserver observer_names = { "UIApplicationDidBecomeActiveNotification": "didBecomeActive", "UIApplicationDidEnterBackgroundNotification": "didEnterBackground", "UIApplicationWillEnterForegroundNotification": "willEnterForeground", "UIApplicationWillResignActiveNotification": "willResignActive" } for on in observer_names: name = observer_names[on] setattr(NotificationObserver, name, NotificationObserver(on)) getattr(NotificationObserver, name).start()
I also added applicationState() into the mix and got this in the console
sa.applicationState() = 0
...
sa.applicationState() = 1
sa.applicationState() = 1
willResignActive
sa.applicationState() = 1
...
sa.applicationState() = 2
sa.applicationState() = 2
didEnterBackground
sa.applicationState() = 2
...
willEnterForeground
sa.applicationState() = 1
...
sa.applicationState() = 0
sa.applicationState() = 0
didBecomeActive
sa.applicationState() = 0
...So really it’s not much different than
if not sa.applicationState():
Correction: The notification is still good to run code when the app goes into the inactive state, but in my case I actually need something else. Also forgot to mention this (when you are done)
for on in observer_names: getattr(NotificationObserver, observer_names[on]).stop()
-
Ok so far it’s working, though I’m a bit nervous on running .clear() at the wrong time (not sure if I can attach an observer to an object) or replace itself (the notification instance .object())
import objc_util import time class NotificationObserver: def __init__(self, name): self.name = objc_util.ns(name) self.center = objc_util.ObjCClass("NSNotificationCenter").defaultCenter() self._observer = None self._blk = None self.queue = objc_util.ObjCClass('NSOperationQueue').new() self.queue.setName_(objc_util.ns('test')) self.clear() def clear(self): self.ni = None self.ni_name = None self.ni_obj = None '''the objc block signature. do not modify''' def _block(self, _cmd, notification): try: self.ni = objc_util.ObjCInstance(notification) self.ni_name = self.ni.name() self.ni_obj = self.ni.object() except Exception as ex: print(ex) '''start observing''' def start(self): #print(time.asctime() + ' starting') if self._observer: raise Exception('observer already started') self._blk = objc_util.ObjCBlock(self._block, restype=None, argtypes=[objc_util.c_void_p, objc_util.c_void_p]) self._observer = self.center.addObserverForName_object_queue_usingBlock_(self.name, None, self.queue, self._blk) objc_util.retain_global(self) def stop(self): #print(time.asctime() + ' stopping') if self._observer: self.center.removeObserver_(self._observer) self._observer = None objc_util.release_global(self)
Using it within my script (a portion of it), it does work just as I originally wanted to (thanks again! @JonB)
from NotificationObserver import NotificationObserver observer_names = { "AVPlayerItemDidPlayToEndTimeNotification": "itemDidPlay" } for on in observer_names: name = observer_names[on] setattr(NotificationObserver, name, NotificationObserver(on)) getattr(NotificationObserver, name).start() def app_active(): return not sa.applicationState() == 2 #1 skip #return not sa.isSuspended() #1 skip #return not sa._isResigningActive() #3 skips def should_do_loop(): if keyboard.is_keyboard() or app_active(): return True else: return False def looper(player): while v.on_screen: if not paused and not wait: #if hasattr(player, "item_load_time") and player.player.rate() == 0.0 and should_do_loop(): pi = player.item if pi and pi is NotificationObserver.itemDidPlay.ni_obj: NotificationObserver.itemDidPlay.clear() player.seek(secs=0) player.player.play() player.playCount += 1 if player.layer: #console.hud_alert("vp.playCount = {}".format(player.playCount), duration=0.5) pass elif not inf: #Audio player has looped -> next item change_i_by_val(1) update_i_change() #console.hud_alert("ap.playCount = {}".format(player.playCount), duration=0.5) pass time.sleep(refresh_rate) else: time.sleep(refresh_rate_helper)
-
Edited this post because the “.rate() method” I added along with comparing the item (as an OR) is actually worse (I forgot both threads were out of sync lol) than using the applicationState (meaning it kept restarting on a cycle). This leaves me with 2 (maybe more?) options
- Just use
if sa.applicationState():
to control behavior - Somehow hook an observer to observe an object (via my AVPlayer.init()) rather than any (share the same observer)
Edit: I have another idea via same observer (I’ll try it later on)
- Just use
-
This seems about right. Instead of swapping out via same property, I just handle the item (one at a time) using a queue
import objc_util import time class NotificationObserver: def __init__(self, name): self.name = objc_util.ns(name) self.center = objc_util.ObjCClass("NSNotificationCenter").defaultCenter() self._observer = None self._blk = None self.queue = objc_util.ObjCClass('NSOperationQueue').new() self.queue.setName_(objc_util.ns('test')) self.ni_objs = [] '''the objc block signature. do not modify''' def _block(self, _cmd, notification): try: ni_obj = objc_util.ObjCInstance(notification).object() self.ni_objs.append(ni_obj) except Exception as ex: print(ex) '''start observing''' def start(self): #print(time.asctime() + ' starting') if self._observer: raise Exception('observer already started') self._blk = objc_util.ObjCBlock(self._block, restype=None, argtypes=[objc_util.c_void_p, objc_util.c_void_p]) self._observer = self.center.addObserverForName_object_queue_usingBlock_(self.name, None, self.queue, self._blk) objc_util.retain_global(self) def stop(self): #print(time.asctime() + ' stopping') if self._observer: self.center.removeObserver_(self._observer) self._observer = None objc_util.release_global(self)
Trying to make the seek call as quick as possible, also cleared the list (really should be removing the item that won’t exist in case you have other player objects) before the next item plays in case another (prior) notification(s) slips through
def looper(player): while v.on_screen: if not paused and not wait: #if hasattr(player, "item_load_time") and player.player.rate() == 0.0 and should_do_loop(): pi = player.item if pi and pi in NotificationObserver.itemDidPlay.ni_objs: NotificationObserver.itemDidPlay.ni_objs.remove(pi) player.seek(secs=0) player.player.play() player.playCount += 1 if player.layer: #console.hud_alert("vp.playCount = {}".format(player.playCount), duration=0.5) pass elif not inf: #Audio player has looped -> next item change_i_by_val(1) update_i_change() #console.hud_alert("ap.playCount = {}".format(player.playCount), duration=0.5) pass time.sleep(refresh_rate) else: time.sleep(refresh_rate_helper)