Forum Archive

Something like applicationWillResignActive?

ts

Does anyone know a replacement for

objc_util.UIApplication.sharedApplication().applicationState()

I want to check right before you leave Pythonista or before the application is put under sleep mode, cause currently with applicationState, if you pull down the Notification Center, control center, about to switch apps, or in multitasking view, its value changes from 0 to 1

Edit 1: Oh wait there’s another value, 2, for when you leave the app and enter sleep mode

Edit 2: Still brings it back to my initial issue (for when I leave the app / enter sleep), as in it passes my logic when it’s not suppose to (its value is not 2)

ts

Oh lol, thanks @cvp

cvp

@ts said:

I can’t correct my topic name

If you edit the first post of your topic, the title text becomes editable

JonB

You should be able to swizzle those methods. See my swizzle function in my objc_hacks repo.

ts

@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"))
cvp

@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_)
JonB

@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')
ts

@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?

ts

@JonB Woah! I just tried it out, that’s actually awesome! Thank you :)

JonB

@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.

cvp

@JonB masterful, as usual

ts

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()
ts

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)
ts

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

  1. Just use if sa.applicationState(): to control behavior
  2. 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)

ts

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)