• mcriley821

    Hey all,

    I’m modifying the keyboard example “Special Characters.py” to include all Unicode characters. I did this by scraping the Unicode code point range and category name from Wikipedia. It has a navigation view with all the categories in a scroll view, and then presents all the Unicode characters of that category.

    Anyhow, the problem is that I get spotty printability, but since the character is a valid Unicode character I can’t prevent it from displaying as 🠂. I don’t understand the problem since sys.maxunicode (1114111) suggests this shouldn’t be a problem...

    Is it due to the possibility that the iOS system font doesn’t have a rendering of these glyphs? How can I install Google Noto and access these glyphs (if that’s the issue).

    Code:

    #! python3
    import keyboard
    import ui
    
    with open('uni_blocks.txt', 'r') as block_file:
        blocks = block_file.readlines()
    
    
    class BlocksView (ui.View):
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.glyph_sv = None
            self.cat_sv = ui.ScrollView(
                flex='WH',
                frame=self.bounds
            )
            self.buttons = []
            for block in blocks:
                cells = block.split(' ')
                start = cells[0]
                end = cells[1]
                name = ' '.join(cells[2:])
                btn = self.create_button(name)
                btn.action = self.cat_selected
                btn.start = start
                btn.end = end
                self.cat_sv.add_subview(btn)
                self.buttons.append(btn)
            self.add_subview(self.cat_sv)
        
        def layout(self):
            if self.cat_sv.on_screen:
                bw = ui.get_screen_size()[0]
                x, y = 2, 2
                for button in self.buttons:
                    button.frame = (x, y, bw, 20)
                    y += 24
                self.cat_sv.content_size = (0, (len(self.buttons) + 1) * 24 + 40)
        
        def cat_selected(self, sender):
            # present another scroll view of unicode titled buttons
            self.glyph_sv = ui.ScrollView(
                name=sender.title,
                frame=self.bounds,
                flex='WH'
            )
            w = ui.get_screen_size()[0]
            x = y = 2
            rows = 1
            for i in range(int(sender.start, 16), int(sender.end, 16)):
                char = chr(i)
                if char.isprintable() and not char.isspace():
                    btn = self.create_button(chr(i))
                    btn.action = self.insert_char
                    btn.number = i
                    btn.frame = (x, y, 35, 35)
                    x += 39
                    if x > w - 39:
                        x = 2
                        y += 39
                        rows += 1
                    self.glyph_sv.add_subview(btn)
            self.glyph_sv.content_size = (w, (rows + 1) * 39 + 40)
            self.navigation_view.push_view(self.glyph_sv, False)
        
        def create_button(self, name):
            btn = ui.Button(title=name)
            btn.font = ('<System>', 18)
            btn.background_color = (1, 1, 1, 0.1)
            btn.tint_color = 'white'
            btn.corner_radius = 4
            btn.size_to_fit()
            return btn
    
        def insert_char(self, sender):
            if keyboard.is_keyboard():
                keyboard.insert_text(sender.title)
            else:
                print(sender.title, sender.number)
    
    
    def main():
        w = ui.get_screen_size()[0]
        v = BlocksView(
            frame=(0, 0, w, 133),
            name='Categories',
            flex='WH'
        )
        nav_view = ui.NavigationView(v)
        nav_view.title_color = 'white'
        if keyboard.is_keyboard():
            keyboard.set_view(nav_view, 'expanded')
        else:
            # For debugging in the main app:
            nav_view.bg_color = 'black'
            nav_view.bar_tint_color='black'
            nav_view.present('fullscreen', hide_title_bar=True)
    
    
    if __name__ == '__main__':
        main()
    
    

    Source .txt:
    https://pastebin.com/raw/Y9Fsuykq

    Thanks!

    posted in Pythonista read more
  • mcriley821

    @mikael @JonB

    I think I figured out a solution that will work. Since the doubletap gesture was recognizing double-taps and gain access to self by inlining the objc method, I realized that’s all I really needed. I can initialize the TextView with editable=False, enable editing inside the objc method, and start editing. Then in the textview’s delegate, when editing is done, disable editing!

    Thank you guys for the help!

    Here’s the code if anyone is interested:

    from objc_util import *
    import ui
    
    UITap = ObjCClass('UITapGestureRecognizer')
    
    class Node (ui.View):
        class TextViewDelegate (object):
            def textview_did_change(self, tv):
                try:
                    if '\n' == tv.text[-1]:
                        tv.text = tv.text[:-1]
                        tv.end_editing()
                except IndexError:
                    pass
            
            def textview_did_end_editing(self, tv):
                # do whatever with the text
                tv.editable = False
        
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.frame = (0,0,*(ui.get_screen_size()))
            self.tv = ui.TextView(
                name='node_label',
                frame=(100, 100, 50, 50),
                delegate=self.TextViewDelegate(),
                bg_color='#b9b9ff',
                editable=False
            )
            
            tvObj = self.tv.objc_instance
            
            def handleTap_(_slf, _cmd, _gesture):
                self.tv.editable = True
                self.tv.begin_editing()
                
            self.target = create_objc_class(
                'fake_target',
                methods=[handleTap_]
                ).new()
        
            self.doubletap = UITap.alloc().initWithTarget_action_(
                self.target,
                'handleTap:'
            )
            self.doubletap.setNumberOfTapsRequired_(2)
            tvObj.addGestureRecognizer_(
                self.doubletap
            )
            self.add_subview(self.tv)
    
        def will_close(self):
            self.doubletap.release()
            self.target.release()
    
    if __name__ == '__main__':
        w, h = ui.get_screen_size()
        view = Node(bg_color='white')
        view.present()
    
    

    posted in Pythonista read more
  • mcriley821

    @cvp TextField is even odder, having no gestureRecognizers. It probably has a view hierarchy with a TextView. Maybe I need to remove gestures from all underlying views of my TextView?

    posted in Pythonista read more
  • mcriley821

    @JonB I’ve seen @mikael’s repo, but based on personal reasons I’d rather not use it. I want to understand how things are working instead of importing.

    Anyhow, inlining the method did not fix the issue. Making tv a self attribute, and changing the method to:

    def handleTap_(_slf, _cmd, _gesture):
        self.tv.begin_editing()
    

    After the first double tap, a single tap suffices to start editing again. I also retained target (self.target).

    posted in Pythonista read more
  • mcriley821

    I'm doing a project with ui and objc_util (typical). I'm getting this strange behavior when overriding a ui.TextView's gestures; the gesture's view is somehow re-overriding the gestures.
    The simplest code example I can muster is as follows:

    from objc_util import *
    import ui
    
    
    UITap = ObjCClass("UITapGestureRecognizer")
    
    
    class Node (ui.View):
        class TextViewDelegate (object):
            def textview_did_change(self, tv):
                if '\n' == tv.text[-1]:
                    tv.text = tv.text[:-1]
                    tv.end_editing()
    
        def __init__(self, *args, **kwargs):
            ui.View.__init__(self, *args, **kwargs)
            self.frame = (0, 0, *(ui.get_screen_size()))
            tv = ui.TextView(
                name="node_label",
                frame=(100, 100, 50, 50),
                bg_color="blue",
                delegate=self.TextViewDelegate()
            )
            # this is where the interesting bit starts
            tvObj = tv.objc_instance
            # remove all the gestures of tv
            for gesture in tvObj.gestureRecognizers():
                tvObj.removeGestureRecognizer_(gesture)
    
            # for a new gesture, we need a target and action
            # how to make self a target? not really sure
            # maybe making a "blank" target with the method 
            # would be enough? since self carries?
            # I tried making a target of self, but crashes
            target = create_objc_class(
                "self",
                methods=[self.handleTap_]
            ).alloc().init()
            # now we have a target and action
            # let's make the actual gesture
            doubletap = UITap.alloc().initWithTarget_action_(
                target,
                "handleTap:"
            )
            # make into an actual doubletap, and add to view
            doubletap.setNumberOfTapsRequired_(2)
            tvObj.addGestureRecognizer_(doubletap)
    
            # add the tv subview with a single gesture: doubletap
            # can confirm only one gesture by uncommenting below
            #print(self.objc_instance.gestureRecognizers())  # None
            #print(tvObj.gestureRecognizers())  # doubletap
            self.add_subview(tv)
    
    # Now, without @static_method, everything is fine up until
    # the below function is called -> results in TypeError of passing
    # 4 args to a 3 arg function, since the first arg is self. However,
    # with @static_method, we have to do some round-about trickery
    # to do what we want. 
        @static_method
        def handleTap_(_slf, _cmd, _gesture):
            gesture = ObjCInstance(_gesture)
            view = gesture.view()
    # More interesting stuff happens now. On the first call of handleTap_,
    # the next line prints only doubletap. On next calls, all the gestures 
    # have been reset
            print(view.gestureRecognizers())
    # we can only start editing now by becoming the first responder,
    # since we can't access self
            view.becomeFirstResponder()
    
    # I assume here that the call to becomeFirstResponder instantiates
    # a new TextView object somehow, yet this new object still contains
    # a doubletap gesture. Re-tapping the TextView in the UI will start
    # editing - no double tapping needed. 
    
    
    if __name__ == "__main__":
        w, h = ui.get_screen_size()
        view = Node(bg_color="white")
        view.present()
    
    

    What can I do to make self my target? Or how can I pass self to my method? Can I create a wrapper for the @static_method wrapper and pass self still? I'm stuck on what to do, since any direction I go seems to be a dead-end:

    • make self into a target = crash
    • @static_method = no reference to the true self instance
    • no @static_method = TypeError
    • can't use global variables, since I hope to have ~10 of these Nodes at a time

    Also, I'd prefer to not use a TextField since they have that awful bounding box. I also think this issue would carry to a TextField anyhow.

    Any ideas are greatly appreciated! I'm stumped!

    posted in Pythonista read more
  • mcriley821

    No. You can’t turn off WiFi via Pythonista, unless you use Shortcuts.

    What problems are you having with Shortcuts?

    posted in Pythonista read more
  • mcriley821

    @ccc
    /bin contains:
    bash bunzip2 bzcat bzip2 bzip2recover cat chgrp chmod chown cp date dd df dir echo egrep false fgrep grep gunzip gzexe gzip kill launchctl ln ls mkdir mknod mktemp mv ps pwd readlink rm rmdir sed sh sleep stty su sync tar touch true uname uncompress vdir zcat zcmp zdiff zegrep zfgrep zforce zgrep zless zmore znew

    /sbin contains:
    dmesg dynamic_pager fsck fsck.sbin fsck_apfs fsck_apfs.sbin fsck_exfat fsck_exfat.sbin fsck_hfs fsck_hfs.sbin fsck_msdos fsck_msdos.sbin fstyp fstyp_msdos fstyp_ntfs fstyp_udf halt launchctl launchd launchd.sbin mount mount.sbin mount_apfs mount_apfs.sbin mount_devfs mount_fdesc mount_hfs mount_hfs.sbin mount_nfs newfs_apfs newfs_apfs.sbin newfs_hfs newfs_hfs.sbin pfctl pfctl.sbin quotacheck umount umount.distrib

    /usr/bin contains:
    7z 7za DumpBasebandCrash IOMFB_FDR_Loader PerfPowerServicesExtended [ abmlite appsearch apt apt-cache apt-cdrom apt-config apt-extracttemplates apt-ftparchive apt-get apt-key apt-mark apt-sortpkgs arch asn1Coding asn1Decoding asn1Parser autopoint awk b2sum base32 base64 basename basenc bashbug brctl c_rehash cap_mkdb captoinfo certtool cfversion chcon chflags chfn chown chsh cksum clear cmp comm compress csplit cut cycc cynject db_archive db_checkpoint db_convert db_deadlock db_dump db_hotbackup db_load db_log_verify db_printlog db_recover db_replicate db_stat db_tuner db_upgrade db_verify deviceinfo diff diff3 dircolors dirmngr dirmngr-client dirname doNotify dpkg dpkg-deb dpkg-divert dpkg-maintscript-helper dpkg-query dpkg-split dpkg-statoverride dpkg-trigger dselect du dumpsexp ecidecid env envsubst event_rpcgen.py expand expr factor faker file fileproviderctl find finger fmt fold footprint funzip gawk getconf getopt gettext gettext.sh gettextize getty gnupg2 gnutls-cli gnutls-cli-debug gnutls-serv gpg gpg-agent gpg-connect-agent gpg-error gpg-error-config gpg-wks-server gpgconf gpgparsemail gpgrt-config gpgscm gpgsm gpgtar gpgv groups gssc head hidutil hmac256 hostid hostinfo hpmdiagnose id idn2 infocmp infotocap install iomfsetgamma ip-print ipcrm ipcs join kbdebug kbxutil killall ksba-config last ldid ldrestart libassuan-config libgcrypt-config link locale locate login logname lsdiagnose lsvfs lz4 lz4c lz4cat lzcat lzcmp lzdiff lzegrep lzfgrep lzgrep lzless lzma lzmadec lzmainfo lzmore md5sum mkfifo mktemp mpicalc msgattrib msgcat msgcmp msgcomm msgconv msgen msgexec msgfilter msgfmt msggrep msginit msgmerge msgunfmt msguniq ncurses6-config ncursesw6-config nettle-hash nettle-lfib-stream nettle-pbkdf2 nfsstat ngettext nice nl nohup notificationWatcher nproc npth-config numfmt ocsptool od openURL openssl p11-kit p11tool pager pagesize passwd paste pathchk pax pbcopy pbpaste pinky pkcs1-conv play plistutil plutil powerlogHelperd pr printenv printf psktool ptx quota realpath recode-sr-latin renice reset restart runcon say sbdidlaunch sbreload scp script sdiff seq sexp-conv sftp sha1sum sha224sum sha256sum sha384sum sha512sum shred shuf snappy sort split srptool ssh ssh-add ssh-agent ssh-keygen ssh-keyscan stat stdbuf sum sw_vers sysdiagnose tabs tac tail tailspin tar taskinfo tee test tic time timeout toe toggleTether tput tr truncate trust tset tsort tty uicache uiduid uiopen unexpand uniq unlink unlz4 unlzma unrar unxz unzip unzipsfx update-alternatives updatedb uptime urlclip users vm_stat watchgnupg wc wget which who whoami xargs xgettext xz xzcat xzcmp xzdec xzdiff xzegrep xzfgrep xzgrep xzless xzmore yat2m yes zip zipcloak zipnote zipsplit zprint

    /usr/sbin contains:
    BTAvrcp BTAvrcp.sbin BTLEServer BTLEServer.sbin BTMap BTMap.sbin BTPbap BTPbap.sbin BlueTool BlueTool.sbin WiFiNetworkStoreModel.momd WirelessRadioManagerd WirelessRadioManagerd.sbin absd absd.sbin ac accton addNetworkInterface addNetworkInterface.sbin addgnupghome applecamerad applygnupgdefaults aslmanager aslmanager.sbin bluetoothd bluetoothd.sbin cfprefsd cfprefsd.sbin chown chroot ckksctl dev_mkdb distnoted distnoted.sbin edquota fairplayd.H2 fdisk filecoordinationd filecoordinationd.sbin hdik ioreg ioreg.sbin iostat ipconfig ipconfig.sbin mDNSResponder mDNSResponder.sbin mDNSResponderHelper mDNSResponderHelper.sbin mediaserverd mediaserverd.sbin mkfile nologin notifyd notifyd.sbin nvram nvram.sbin otctl pppd
    pppd.sbin pwd_mkdb quotaon racoon racoon.sbin reboot repquota rtadvd rtadvd.sbin scutil scutil.sbin spindump spindump.sbin sshd startupfiletool sysctl syslogd syslogd.sbin vifs vipw vsdbutil wifid wifid.sbin zdump zic

    Would that be the best way to do background cli commands to StaSh and report the stdout? Is there another way without launching a Web server?

    posted in Pythonista read more
  • mcriley821

    @ccc I can attest that I can navigate the entire file system with StaSh now.

    Is there a way to use StaSh in a headless manner? I want to make a file browser and editor, since similar packages in Cydia are not yet supported for iOS 13

    posted in Pythonista read more
  • mcriley821

    I recently jailbroke my device running iOS 13.5.1 to allow for tethering (data should be free imo). Anyhow, I really love Pythonista and I'm wondering if the jailbreak gives me any extra capabilities now in Pythonista? I'm really curious to start changing the plist file since that's what stunted a lot of my projects. Excited to hear what you guys can think of!

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!