Saturday, December 26, 2009

iPhone SDK 3.0 API Explorer

The previous version is for OS 2.2 and is here
http://iphonesdkdev.blogspot.com/2008/12/iphone-sdk-api-explorer.html

It is now updated to support OS 3.0 and with on-line reference to Erica Sadun 3.1.2 class documentation

Installation Instructions:

Cydia Source : http://cydia.iphone.org.hk/apt/
Section: Utilities
Name: API Explorer 3.0

Updated source code for version 3.0 is here
svn checkout http://apiexplorer.googlecode.com/svn/trunk/apiexplorer3

or download it (revision 25) here
http://apiexplorer.googlecode.com/files/apiexplorer3.zip

The project can be built using Xcode or iphone gcc. The Makefile has been updated, so that it can be compiled under iphone gcc for 3.0 framework. Refer here for the 3.0 headers for iphone gcc.

The Cydia version did not have all the frameworks (e.g. GameKit and others). To explore more frameworks, you need to download the source code and build your own version.

For Xcode, just add the required frameworks before build. For iphone gcc, just add LDFLAGS (like the one below) in the Makefile and then make install.

LDFLAGS += -framework GameKit

If you want to have on-line reference to 3.0.0 framework instead of 3.1.2 from Erica's website, you should build your code.plist. Here is the script to dump the dictionary key-string pairs for 3.0.0

#!/bin/sh
curl -s -L http://ericasadun.com/iPhoneDocs300/annotated.html | grep "tr..td" | sed -e "s/^.*href=\"\(.*html\)\".\(.*\)\/a.*$/\<key\>\2\/key\>\<string\>\1\<\/string\>/g;"




This is how to dump the method name to class key-string values
method2class.plist Select all

#!/bin/sh
for page in "" _0x61 _0x62 _0x63 _0x64 _0x65 _0x66 _0x67 _0x68 _0x69 _0x6a _0x6b _0x6c _0x6d _0x6e _0x6f _0x70 _0x71 _0x72 _0x73 _0x74 _0x75 _0x76 _0x77 _0x78 _0x79 _0x7a ; do
curl -s -L http://ericasadun.com/iPhoneDocs312/functions_func${page}.html | awk '/^.li./{s=$0;next}{print s " "$0}' | sed -e "s/^.li.\(.*\)\s[:,].*a\sclass..el..href..*html.*\">\(.*\)<\/a>$/\<key\>\1\<\/key\>\<array\>\<string\>\2\<\/string\>\<\/array\>/g;" | grep "<key>" | sed -e "s/(.*)//g" | awk -F "</key>" '!arr[$1] {arr[$1] = $0; next} {arr[$1] = arr[$1] " " $2} END {for(i in arr) {print arr[i]}}' | sed -e "s/<\/array> <array>//g"
done



Latest screen dump for version 3.1 (not yet released)






Wednesday, December 9, 2009

[How-to] compile SmartScreen widget in iphone gcc

Here is a sample clock widget source from SmartScreen sdk http://media-phone.ch/en/software/smartscreen/sdk/ , I added a Makefile so that it can be compiled under iphone gcc

http://apiexplorer.googlecode.com/files/ClockWidget.zip

Make sure you have installed iphone gcc and 3.0 headers (refer here for the 3.0 headers) and also SmartScreen (Lite or full version) before your testing.

If you have installed toolchain and 3.0 headers under cygwin or linux, just modify the sdk location in the Makefile and it should compile as well.

SmartScreen widget will be installed under the folder /var/mobile/Library/SmartScreen

To compile and install this sample clock widget under iphone gcc

make
make install


To uninstall the clock widget

make uninstall


This is the updated Makefile for SmartScreen ClockWidget
Makefile Select all

# Makefile for gcc compiler for iPhone (SmartScreen Widget)

PROJECTNAME:=Clock
PRINCIPALCLASS:=$(PROJECTNAME)Widget
WIDGETFOLDER:=$(PROJECTNAME).wdgt
INSTALLFOLDER:=/var/mobile/Library/SmartScreen
MINIMUMVERSION:=3.0

CC:=arm-apple-darwin9-gcc
CPP:=arm-apple-darwin9-g++
LD:=$(CC)
SDK:=/var/toolchain/sys30

LDFLAGS = -arch arm -lobjc
LDFLAGS += -framework CoreFoundation
LDFLAGS += -framework Foundation
LDFLAGS += -framework UIKit
LDFLAGS += -framework CoreGraphics
//LDFLAGS += -framework AddressBook
//LDFLAGS += -framework AddressBookUI
//LDFLAGS += -framework AudioToolbox
//LDFLAGS += -framework AudioUnit
//LDFLAGS += -framework AVFoundation
//LDFLAGS += -framework CoreSurface
//LDFLAGS += -framework CoreAudio
//LDFLAGS += -framework CoreData
//LDFLAGS += -framework CFNetwork
//LDFLAGS += -framework GraphicsServices
//LDFLAGS += -framework OpenAL
//LDFLAGS += -framework OpenGLES
//LDFLAGS += -framework MediaPlayer
//LDFLAGS += -framework QuartzCore
//LDFLAGS += -framework Security
//LDFLAGS += -framework SystemConfiguration
//LDFLAGS += -framework WebCore
//LDFLAGS += -framework WebKit
//LDFLAGS += -framework SpringBoardUI
//LDFLAGS += -framework TelephonyUI
//LDFLAGS += -framework JavaScriptCore
//LDFLAGS += -framework PhotoLibrary
LDFLAGS += -L"$(SDK)/usr/lib"
LDFLAGS += -F"$(SDK)/System/Library/Frameworks"
LDFLAGS += -F"$(SDK)/System/Library/PrivateFrameworks"
LDFLAGS += -bind_at_load
LDFLAGS += -multiply_defined suppress
LDFLAGS += -march=armv6
LDFLAGS += -mcpu=arm1176jzf-s
LDFLAGS += -mmacosx-version-min=10.5
LDFLAGS += -dynamiclib

CFLAGS += -I"$(SDK)/usr/include"
CFLAGS += -DDEBUG -std=gnu99 -O0
CFLAGS += -Diphoneos_version_min=$(MINIMUMVERSION)
CFLAGS += -Wno-attributes -Wno-trigraphs -Wreturn-type -Wunused-variable

BUILDDIR=./build/$(MINIMUMVERSION)
SRCDIR=./
RESDIR=./Resources
OBJS+=$(patsubst %.m,%.o,$(wildcard $(SRCDIR)/*.m))
OBJS+=$(patsubst %.c,%.o,$(wildcard $(SRCDIR)/*.c))
OBJS+=$(patsubst %.mm,%.o,$(wildcard $(SRCDIR)/*.mm))
OBJS+=$(patsubst %.cpp,%.o,$(wildcard $(SRCDIR)/*.cpp))
OBJS+=$(patsubst %.m,%.o,$(wildcard ./*.m))
PCH=$(wildcard *.pch)
RESOURCES=$(wildcard $(RESDIR)/*)

CFLAGS += $(addprefix -I,$(SRCDIR))

CPPFLAGS=$CFLAGS

all: $(PROJECTNAME)

$(PROJECTNAME): $(OBJS) Makefile
    $(LD) $(LDFLAGS) $(filter %.o,$^) -o $@

%.o: %.m %.h $(PCH) $(filter-out $(patsubst %.o,%.h,$(OBJS)), $(wildcard $(SRCDIR)/*.h))
    $(CC) --include $(PCH) -c $(CFLAGS) $< -o $@

%.o: %.c %.h $(PCH)
    $(CC) --include $(PCH) -c $(CFLAGS) $< -o $@

%.o: %.mm %.h $(PCH) $(filter-out $(patsubst %.o,%.h,$(OBJS)), $(wildcard $(SRCDIR)/*.h))
    $(CPP) --include $(PCH) -c $(CPPFLAGS) $< -o $@

%.o: %.cpp %.h $(PCH)
    $(CPP) --include $(PCH) -c $(CPPFLAGS) $< -o $@

dist: $(PROJECTNAME) Info.plist Makefile $(RESOURCES)
    @rm -rf $(BUILDDIR)
    @mkdir -p $(BUILDDIR)/$(WIDGETFOLDER)
ifneq ($(RESOURCES),)
    @-cp -r $(RESOURCES) $(BUILDDIR)/$(WIDGETFOLDER)
    @-$(foreach DIR, .svn .DS_Store .git* , find $(BUILDDIR)/$(APPFOLDER) -name '$(DIR)' -prune -exec rm -rfv {} \;;)
endif
    @cp Info.plist $(BUILDDIR)/$(WIDGETFOLDER)/Info.plist
    @./plutil -key CFBundleExecutable -value $(PROJECTNAME) $(BUILDDIR)/$(WIDGETFOLDER)/Info.plist
    @./plutil -key CFBundleName -value $(PROJECTNAME) $(BUILDDIR)/$(WIDGETFOLDER)/Info.plist
    @./plutil -key NSPrincipalClass -value $(PRINCIPALCLASS) $(BUILDDIR)/$(WIDGETFOLDER)/Info.plist
    ldid -S $(PROJECTNAME)
    @mv $(PROJECTNAME) $(BUILDDIR)/$(WIDGETFOLDER)

install: dist
    @rm -fr $(INSTALLFOLDER)/$(WIDGETFOLDER)
    cp -r $(BUILDDIR)/$(WIDGETFOLDER) $(INSTALLFOLDER)/
    @chown -R mobile:mobile $(INSTALLFOLDER)/$(WIDGETFOLDER)
    @echo "Widget $(WIDGETFOLDER) installed, please respring device"
    @./respring

uninstall:
    @rm -fr $(INSTALLFOLDER)/$(WIDGETFOLDER)
    @./respring
    @echo "Widget $(WIDGETFOLDER) uninstalled, please respring device"

install_respring:
    cp ./respring /usr/bin/respring

distclean:
    @rm -rf $(BUILDDIR)

clean:
    @rm -f $(OBJS)
    @rm -rf $(BUILDDIR)
    @rm -f $(PROJECTNAME)

.PHONY: all dist install uninstall install_respring distclean clean





Wednesday, November 25, 2009

Google Wave, Drag and Drop ?

Want to enable drag and drop in google wave for your browser ?



Install google gears here

http://gears.google.com









Monday, November 23, 2009

Where is Cycript ?

Cycrypt is a new project that blends Objective-C and JavaScript to make it easier to implement aspects of both together.
and here it is

http://www.cycript.org/

Examples are here

http://www.cycript.org/examples/


Dial with Cycript: [SBTTYPromptAlert dialNumberPromptingIfNecessary:"+18885551234" addressBookUID:-1 urlAddition:"wasLockAlert=1"];






Sunday, November 22, 2009

How to create local and remote ssh git repo in Mac OS X

(1) Install git for Mac OS X

mkdir ~/src
cd ~/src/
curl -O http://kernel.org/pub/software/scm/git/git-1.6.5.3.tar.bz2
tar -xjvf git-1.6.5.3.tar.bz2
cd git-1.6.5.3
./configure --prefix=/usr/local
make
sudo make install
git --version


(2) Suppose you have a project in ~/myproject
and the remote ssh server login is user@xxx.xxx.xxx.xxx
both client and server have git installed

(3) Create localrepo in local harddisk

git mkdir -p ~/git
git clone --bare ~/myproject ~/git/myproject.git
touch ~/git/myproject.git/git-daemon-export-ok


push to localrepo

cd ~/myproject
git init
git add .
git rm -r --cache build     # ignore the build directory for iPhone project
git commit -m 'Initial commit'
git remote add localrepo ~/git/myproject.git
git push localrepo master
git log


(3) Create sshrepo in remote server over ssh

ssh user@xxx.xxx.xxx.xxx "mkdir -p /Volumes/HD/git/myproject.git; cd /Volumes/HD/git/myproject.git; git --bare init; touch git-daemon-export-ok"


check the location of remote git binary and the remote ssh login shell


ssh user@xxx.xxx.xxx.xxx "which git-upload-pack"
ssh user@xxx.xxx.xxx.xxx "echo \$PATH"


mine is /usr/local/bin/git-upload-pack


if the remote login shell does not include path of git, create ~/.bashrc in your remote ssh login shell


ssh user@xxx.xxx.xxx.xxx "echo 'export PATH=\${PATH}:/usr/local/bin' > ~/.bashrc"


push to sshrepo

cd ~/myproject
git remote add sshrepo ssh://user@xxx.xxx.xxx.xxx/Volumes/HD/git/myproject.git
git push sshrepo master
git log



Test git clone

cd ~
git clone ~/git/myproject.git workinglocal
cd workinglocal
git log


or


cd ~
git clone ssh://user@xxx.xxx.xxx.xxx/Volumes/HD/git/myproject.git workingremote
cd workingremote
git log


How to Branch

git branch -r                 # show branch in repo
git checkout -b todo origin/to-do-branch # checkout a new branch
git checkout master           # checkout the master branch

git checkout localrepo/master # checkout the master branch in localrepo
git checkout sshrepo/master   # checkout the master branch in sshrepo

git branch next               # create new branch
git add .
git commit -m 'commit nextbranch'
git push localrepo next

git branch -b cygwin
git branch -r
git add *
git commit -m 'commit cygwin branch'
git push origin cygwin


How to checkout a previous commit

git log # show log of previous commit
git log --format=oneline # show log of previous commit log hex digit
git checkout 838fbf2e9b050d2350694235bbf5e9a11fa7acea # commit log hexdigit


How to push to github

git config --global user.name "javacom"
git config --global user.email "javacomhk@yahoo.com"
git remote rm origin
git remote add origin git@github.com:javacom/toolchain4.git
git push origin master









Sunday, October 4, 2009

How to compile mobilesubstrate extension in iPhone gcc for OS 3.0

(1) You need iphone gcc installed in your jailbroken iPhone / iPod Touch with firmware 3.0 or above

iPhone gcc is available in Cydia. To install it in you need to do these


# assume you have installed APT 0.6 Transitional and Aptitude and wget in Cydia, so that you can use the command apt-get
# if libgcc is broken in Cydia, you have to install it manually before iphone-gcc
wget http://apt.saurik.com/debs/libgcc_4.2-20080410-1-6_iphoneos-arm.deb
dpkg -i libgcc_4.2-20080410-1-6_iphoneos-arm.deb
# install iphone-gcc
apt-get install iphone-gcc


Moreover, you need these utilities as well


apt-get install make ldid zip unzip wget


for editor in iPhone you can use vim or nano

(2) You need the header files of toolchain and SDK 3.0, libsubstrate and classdump the SpringBoard and UIKit headers

The building of iPhone gcc in Linux and header files (updated for OS 3.0) is here in
http://www.saurik.com/id/4

The updated mobilesubtrate header files are here

The classdump procedure is here

But all these involved a lot of downloading and patching, so I put all the downloading/classdump/patching in one zipped tar file (sys30.tgz). You can download it (about 96M) here

(3) copy and untar the required headers and libraries (say copy to /var/mobile/sys30.tgz) and install it in iPhone / iPod Touch say

mkdir /var/toolchain/
cd /var/toolchain/
tar -xzvf /var/mobile/sys30.tgz


(4) Use this sample mobilesubstrate extension (ExampleHook.zip) and unzip it to test. You need to apt-get install mobilesubstrate in iPhone / iPod Touch first

http://apiexplorer.googlecode.com/files/ExampleHook.zip

apt-get install mobilesubstrate
wget http://apiexplorer.googlecode.com/files/ExampleHook.zip
unzip ExampleHook.zip
cd ExampleHook

make (to compile and codesign)
make install (to install), after install you need respring the device to test
make uninstall (to uninstall), after uninstall you need respring as well

respring utility is available from my cydia source http://cydia.iphone.org.hk/apt/

(5) You can also checkout a copy of the iphone backgrounder to test your iphone gcc and mobilesubstrate development environment
http://code.google.com/p/iphone-backgrounder/source/checkout

Install subversion before using svn in iPhone / iPod Touch

apt-get install subversion
svn checkout http://iphone-backgrounder.googlecode.com/svn/trunk/ iphone-backgrounder
cd iphone-backgrounder



I use this modified Makefile to compile iphone-backgrounder
MakefileSelect all

NAME = Backgrounder
APP_ID = jp.ashikase.backgrounder

# These paths must be changed to match the compilation environment
TOOLCHAIN = /var/toolchain/sys30/dump
SYS_PATH = /var/toolchain/sys30
MS_PATH = /var/toolchain/sys30/mobilesubstrate
INC_PATH = /var/toolchain/sys30/usr/include
LDID = /usr/bin/ldid

CXX = arm-apple-darwin9-g++
CXXFLAGS = -g0 -O2 -Wall -Werror -Wno-write-strings -include common.h -DAPP_ID=\"$(APP_ID)\"
LD = $(CXX)
LDFLAGS = -march=armv6 \
-mcpu=arm1176jzf-s \
-bind_at_load \
-multiply_defined suppress \
-framework CoreFoundation \
-framework Foundation \
-framework UIKit \
-framework GraphicsServices \
-framework CoreGraphics \
-F$(SYS_PATH)/System/Library/Frameworks \
-F$(SYS_PATH)/System/Library/PrivateFrameworks \
-L$(SYS_PATH)/usr/lib -lsubstrate \
-lobjc

INCLUDES = -I$(INC_PATH) -I$(MS_PATH) -I$(TOOLCHAIN) \
-I./Classes

SUBDIRS = . Classes

DIRLIST := $(SUBDIRS:%=%)
SRCS := $(foreach dir,$(DIRLIST), $(wildcard $(dir)/*.mm))
HDRS := $(foreach dir,$(DIRLIST), $(wildcard $(dir)/*.h))
OBJS := $(SRCS:.mm=.o)

all: $(NAME).dylib

config:
ln -snf $(TOOLCHAIN) $(SYS_PATH)

# Replace 'iphone' with the IP or hostname of your device
install: config $(NAME).dylib
ssh root@iphone rm -f /Library/MobileSubstrate/DynamicLibraries/$(NAME).dylib
scp $(NAME).dylib root@iphone:/Library/MobileSubstrate/DynamicLibraries/
ssh root@iphone restart

$(NAME).dylib: $(OBJS) $(HDRS)
$(LD) -dynamiclib $(LDFLAGS) $(OBJS) -init _$(NAME)Initialize -o $@
$(LDID) -S $@

%.o: %.mm
$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@

clean:
rm -f $(OBJS) $(NAME).dylib


P.S. This is how to compile a standard c++ program in iPhone gcc
test.cppSelect all

arm-apple-darwin9-g++ -I"/var/toolchain/sys30/usr/include" -I"/var/toolchain/sys30/usr/include/c++/4.2.1" -I"/var/toolchain/sys30/usr/include/c++/4.2.1/armv6-apple-darwin9" -g0 -O2 -Wall -L"/var/toolchain/sys30/usr/lib" -march=armv6 -mcpu=arm1176jzf-s test.cpp -o test






Sunday, August 2, 2009

Compatible code for OS 2.2.1 and 3.0

Recently, the itts apple server has a number of changes and one of my favourite opensource app AppSales Mobile failed to get the sales data. It is now updated and thanks to the developer for the updating

http://github.com/omz/AppSales-Mobile/


However, the project was set to compile for SDK3.0, there are only a few changes that need to bring this app to be compatible to OS 2.2.1 and 3.0 at the same time.

Here are the steps involved

(1) Base SDK set to iPhone Device 3.0
(2) In Project Settings, iPhone OS Deployment Target set to iPhone OS 2.2.1
(3) Change the source code in RootViewController.m that have setFont or setImage methods, there are about 18 such changes to be done.


This is how to change the source code by adding respondsToSelector: test before using the deprecated method



change from


  [footer.titleLabel setFont:[UIFont systemFontOfSize:14.0]];


to


  if ( [footer respondsToSelector:@selector(setFont:)] ) {
    [footer setFont:[UIFont systemFontOfSize:14.0]];
  }
  else {
    [footer.titleLabel setFont:[UIFont systemFontOfSize:14.0]];
  }




change from

  cell.imageView.image = [UIImage imageNamed:@"Daily.png"];


to


  if ( [cell respondsToSelector:@selector(setImage:)] ) {
    cell.image = [UIImage imageNamed:@"Daily.png"];
  }
  else {
    cell.imageView.image = [UIImage imageNamed:@"Daily.png"];
  }


You may get warnings when build to actual 2.2.1 OS device, but this is normal. However, you cannot build to 2.2.1 Simulator.

The modified source is available here
http://code.google.com/p/apiexplorer/source/detail?r=20

Revision 20 is a modified source (as above) for version 2009/7

Revision 23 is the updated modification for version 2009/11/19

to checkout a revision

svn checkout -r 20 http://apiexplorer.googlecode.com/svn/branches/AppSalesMobile221 AppSalesMobile221r20
svn checkout -r 23 http://apiexplorer.googlecode.com/svn/branches/AppSalesMobile221 AppSalesMobile221r23



If you want to check out a previous version based on the commit hexdigit from the github repo, do this

git clone git://github.com/omz/AppSales-Mobile.git AppSales0731
cd AppSales0731
git checkout -b b0731 ff248e8e6c23386f867514c1c331a469b7d4cf45
git log






Saturday, August 1, 2009

javacom change name

I have been asked by Sun Microsystems to change my App Store name to others stating that Javacom is an Unauthorized use of Java Trademark.



Obviously, I don't have the ability to counter with their request. But I have a few words to say

First of all, my name in App Store is javacom not Javacom
Second, Java and Java applet are not supported by official iPhone App Store app or web app.
Third, I did not program in Java, I only drink a cup of java when I do programming.

May be I have to drink milk instead.... Coffee may not be good for me




Monday, July 27, 2009

Picture Icon in email signature for iPhone

How to add CSS signature to your iPhone ?

The previous hack was to change the Preferences Settings for jailbreaked iPhone. But I have a better approach for non-jailbreaked iPhone, and with custom picture email signature as well. It works for 2.x firmware.

This will be a new functionality for my app Touch Dial Emoji (version 2.0) in App Store.


See screenshots here

Sunday, July 12, 2009

How to build a single iPhone application support both 2.x and 3.0 at the same time

I asked Apple Technical Support on how to build an iPhone application that runs on iPhone OS 2.x and yet uses iPhone OS 3.0 features if they are available.

They replied with a solution called weak linking

What is weak linking as quoted from http://devworld.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html

When a symbol in a framework is defined as weakly linked, the symbol does not have to be present at runtime for a process to continue running. The static linker identifies a weakly linked symbol as such in any code module that references the symbol. The dynamic linker uses this same information at runtime to determine whether a process can continue running. If a weakly linked symbol is not present in the framework, the code module can continue to run as long as it does not reference the symbol. However, if the symbol is present, the code can use it normally.

Here's the basic steps:

1. Use the latest tools with the latest SDK.

2. In your project, set the IPHONEOS_DEPLOYMENT_TARGET build setting to the oldest OS you want to support (say iPhone OS 2.0). And use GCC 4.2




3. If you use frameworks that are not present on that older OS, set the frameworks to be weak imported. Do this using the Linked Libraries list in the General tab of the target info window.




if you use Makefile to build the app add this in linker flag
  LDFLAGS += -weak_framework MessageUI

and add this key in Info.plist

  <key>MinimumOSVersion</key>
  <string>2.0</string>



This is how to test whether framework is available or not


#import <MessageUI/MessageUI.h>
#include <dlfcn.h>

    if ( dlsym(RTLD_DEFAULT, "MFMailComposeErrorDomain") != NULL ) {
        NSLog(@"%@", @"MessageUI framework is available");
        NSLog(@"MFMailComposeErrorDomain = %@", MFMailComposeErrorDomain);
    } else {
        NSLog(@"%@", @"MessageUI framework is not available");
    }



4. For places where you use C-style imports that aren't present on older systems, check whether the import is present before using it.

5. If you use Objective-C methods that aren't present on older systems, use -respondsToSelector: to verify that the methods are present before calling them.


if ( [[UIApplication sharedApplication] respondsToSelector:@selector(canOpenURL:)] ) {
  if ( [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"tel:996-1010"]] ) {
    NSLog(@"%@", @"tel URL is supported");
  } else {
    NSLog(@"%@", @"tel URL is not supported");
  }
} else {
  NSLog(@"%@", @"-canOpenURL: not available");
}


6. If you use Objective-C classes that aren't present on older systems, you can't just use the class directly. For example:

obj = [[NSUndoManager alloc] init];

will cause your application to fail to launch on iPhone OS 2.x, even if you weak link to the framework. Rather, you have to do the following:


NSUndoManager undoManager;
Class cls;
cls = NSClassFromString(@"NSUndoManager");
  if (cls != nil) {
    undoManager = [[[cls alloc] init] autorelease];
    NSLog(@"%@", @"NSUndoManager is available");

    // This tests whether we have access to NSUndoManager's selectors.

    [undoManager beginUndoGrouping];
    [undoManager endUndoGrouping];
  } else {
    NSLog(@"%@", @"NSUndoManager not available");
  }
  undoManager = nil;


7. Test, test, test!

Updated:
There is sample source code for the Mail Composer in Developer site
http://developer.apple.com/iphone/library/samplecode/MailComposer/index.html



Sunday, July 5, 2009

How to compile command line utility for iPhone using XCode gcc

hello.c

echo 'main() { printf("Hello, world!\n"); }' > hello.c


compile hello.c

/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.0 -arch armv6 -mthumb -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk -o hello hello.c


compile with framework

/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.0 -arch armv6 -mthumb -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk -framework Foundation -framework CoreFoundation -lobjc -std=c99 main.m -o main


codesign in Mac, if you have developer or self-signed certificate (can't codesign it when in ssh session)

export CODESIGN_ALLOCATE=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate; codesign -f -s "iPhone Developer" hello


or you can codesign in iPhone, that is, send the binary to iPhone and then fake codesign it

ldid -S hello



This is how to use as (assembler) to compile hello.s to ARM thumb binary
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/as -arch armv6 hello.s -o hello


# GAS filename : hello.s
# use as to compile
# /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/as -arch armv6 hello.s -o hello
# use otool to disassemble
# otool -tv hello

  .globl _main
  .code 16
  .thumb_func _main
_main:
  push {r7, lr}
  add r7, sp, #0
  add r3, pc
  mov ip, r3
  mov r3, ip
  mov r0, r3
  pop {r7, pc}


.
.
.

Saturday, June 20, 2009

Use XCode 3.1.2 to build SDK 3.0 app to 3.0 Device without provisioning profile

The trick to skip Provisioning Profile for the new XCode 3.1.3 (iPhone SDK3.0) does not work now. The only thing you can do is to use the old XCode 3.1.2 <Old XCode Dir> to build app for 3.0 device if you don't have the official provisioning profile, until new method to skip provisioning profile can be found.

What you need is to install the new iPhone SDK 3.0 (XCode 3.1.3) in a new directory (non-default dir) <New XCode Dir> and copy the SDK3.0 and the necessary device support files to XCode 3.1.2 and use it build app to 3.0 device.

You need to do the followings:

(1) Create a symbolic link from <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS3.0.sdk
to <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/

(2) Create a symbolic link from <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/DeviceSupport/3.0 (7A341)
to <Old XCode Dir>Developer/Platforms/iPhoneOS.platform/DeviceSupport/

(3) Backup the folder of <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr

(4) Replace the folder of <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr
by <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/usr

(5) Edit the <Old XCode Dir>/Developer/Platforms/iPhoneOS.platform/Info.plist to have the magic keys, may be you have already done so. You can refer to this for instruction.

Info.plist:Select all

<key>OverrideProperties</key>
<dict>
<key>CODE_SIGN_CONTEXT_CLASS</key>
<string>XCiPhoneOSCodeSignContext</string>
<key>DEBUG_INFORMATION_FORMAT</key>
<string>dwarf-with-dsym</string>
<key>EMBEDDED_PROFILE_NAME</key>
<string>embedded.mobileprovision</string>
<key>SDKROOT</key>
<string>iphoneos2.2.1</string>
<key>PROVISIONING_PROFILE_ALLOWED</key>
<string>NO</string>
<key>PROVISIONING_PROFILE_REQUIRED</key>
<string>NO</string>

</dict>


(6) Create a self signed identity say "iPhone Pwned Developer" in your Mac (you probably have this already) see Apple Guide here

(7) Remember to restart Xcode (your old Xcode 3.1.2)

(8) Install the Installd Patch for OS 3.0 to your 3.0 device. This package is in Tweaks Section from the Cydia Source http://iphone.org.hk/apt/ which is released by me. Please reboot device after installation of this package.

(9) Create New Project in in XCode 3.1.2 and in Info.plist, add the magic key of SignerIdentity, you probably know this already. (update, you don't need this after installd patch and for OS 3.0 or above)

<key>SignerIdentity</key>
<string>Apple iPhone OS Application Signing</string>


(10) Use the codesign identity as created in step 6 above to codesign the binary and Build and Go to 3.0 device

P. S. I did not copy the Simulator SDK 3.0, because I can use the iPhone SDK 3.0 in <New XCode Dir> to test 3.0 Simulator. If you want to update the documentation in your old XCode, it is in <New XCode Dir>/Developer/Platforms/iPhoneOS.platform/Developer/Documentation

You can use move instead of symbolic link, if you want to uninstall the <New XCode Dir>


For those who wants to get the iPhone SDK 2.2.1 (XCode 3.1.2) , try the direct download link here (you need to login your developer account)

iPhone SDK 2.2.1 direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_for_iphone_os_2.2.1__9m2621a__final/iphone_sdk_for_iphone_os_2.2.19m2621afinal.dmg

iPhone SDK 2.2 direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_for_iphone_os_2.2__9m2621__final/iphone_sdk_for_iphone_os_2.2_9m2621_final.dmg


iPhone SDK 3.0 (Xcode 3.1.3) Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.0__final/iphone_sdk_3.0__leopard__9m2736__final.dmg

iPhone SDK 3.0 (Xcode 3.2) Snow Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.0__final/iphone_sdk_3.0__snow_leopard__final.dmg


iPhone SDK 3.1 with Xcode 3.1.4 Leopard direct download link
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1__final/iphone_sdk_3.1_with_xcode_3.1_final__leopard__9m2809.dmg

iPhone SDK 3.1 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1__final/iphone_sdk_3.1_with_xcode_3.2_final__snow_leopard__10a432.dmg

iPhone SDK 3.1.2 with XCode 3.1.4 for Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.2__final/iphone_sdk_3.1.2_with_xcode_3.1.4__leopard__9m2809.dmg

iPhone SDK 3.1.2 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.2__final/iphone_sdk_3.1.2_with_xcode_3.2.1__snow_leopard__10m2003.dmg

iPhone SDK 3.1.3 with XCode 3.1.4 for Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.1.4__leopard__9m2809a.dmg

iPhone SDK 3.1.3 with XCode 3.2.1 for Snow Leopard
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.1.3__final/iphone_sdk_3.1.3_with_xcode_3.2.1__snow_leopard__10m2003a.dmg

iPhone SDK 3.2 beta 4 with Xcode 3.2.2 (Snow Leopard)
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.2_beta_4/iphone_sdk_3.2_beta_4_with_xcode_3.2.2.dmg

iPhone SDK 3.2 with Xcode 3.2.2 (Snow Leopard)
http://developer.apple.com/iphone/download.action?path=/iphone/iphone_sdk_3.2__final/xcode_3.2.2_and_iphone_sdk_3.2_final.dmg

Updates: use this script to patch "iPhoneOS\ Build\ System\ Support". If you think the use of dd utility is fragile, don't use it.

patch.sh Select all

#!/bin/bash
cd /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Plug-ins/iPhoneOS\ Build\ System\ Support.xcplugin/Contents/MacOS/

dd if=iPhoneOS\ Build\ System\ Support of=working bs=500 count=255
printf "\x8f\x2a\x00\x00" >> working
dd if=iPhoneOS\ Build\ System\ Support of=working bs=1 skip=127504 seek=127504
/bin/mv -n iPhoneOS\ Build\ System\ Support iPhoneOS\ Build\ System\ Support.original
/bin/mv working iPhoneOS\ Build\ System\ Support
chmod a+x iPhoneOS\ Build\ System\ Support



Enjoy.
.
.
.

Friday, May 15, 2009

OpenGL ES for iPhone : Part 4 with More Drawings and OpenGL Screen Save

In some OpenGL 1.x books you might notice that the gl commands (like belows) are within the code block of glBegin() and glEnd() pair. These gl* commands must be converted to Vertices Array in order to be useful for OpenGL ES for iPhone.


glBegin(GL_LINE_STRIP);
z = -50.0f;
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { x = 50.0f*sin(angle); y = 50.0f*cos(angle); // Specify the point and move the z value up a little glVertex3f(x, y, z); z += 0.5f; } // Done drawing points glEnd();


Typically, you remove the glBegin() and glEnd() commands (that is immediate mode) and create the vertices array and implement the vertices position calculation (if any) inside the setupView and then remove other unsupported gl* commands before putting them to the iPhone OpenGL ES project code.

This is an example converting from the immediate mode to vertex arrays

immediate mode

glBegin(GL_TRIANGLES);
glVertex3f(-2.0, 0.5, 0.0);
glVertex3f(0.0, 4.0, -2.0);
glVertex3f(1.5, 2.5, -0.5);
glEnd();


vertex arrays

GLfloat vertices[] = { -2.0, 0.5, 0.0, 0.0, 4.0, -2.0, 1.5, 2.5, -0.5 };
glVertexPointer(3, GL_FLOAT, 0, vertices);
glDrawArrays(GL_TRIANGLES, 0, 3);


OpenGL ES does not support the full set of vertex array functions or parameters present in OpenGL






























FunctionNotes
glBegin()Not supported.
glEnd()Not supported.
glEdgeFlag[v]()Not supported.
glVertex{234}{sifd}[v]()Not supported.
glNormal3f()Supported.
glNormal3{bsifd}[v]()Not supported.
glNormal3{bsifd}[v]()Not supported.
glTexCoord{1234}{sifd}[v]()Not supported.
glMultiTexCoord4f()Supported.
glMultiTexCoord{1234}{sifd}[v]()Not supported.
glColor4f()Supported.
glColor{34}{bsifd ub us ui}[v]()Not supported.
glIndex{sifd ub}[v]()Not supported.
glVertexPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glNormalPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but support for
GL_BYTE has been added.
glColorPointer()Supported.
Type cannot be GL_INT or GL_DOUBLE, but
support for GL_UNSIGNED_BYTE has been added.
In addition, the alpha value must be included with all colors;
there is no support for specifying only the RGB values.
glIndexPointer()Not supported.
glTexCoordPointer()Supported.
 Type cannot be GL_INT or GL_DOUBLE, but support
for GL_BYTE has been added. Also, because there is no support
for 1D textures, at least 2 texture coordinates must be provided
per vertex.
glEdgeFlagPointer()Not supported.
glInterleavedArrays()Not supported.
glArrayElement()Not supported.
glDrawArrays()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not supported.
glDrawElements()GL_POINTS, GL_LINES, GL_LINE_LOOP, GL_LINE_STRIP, GL_TRIANGLES,
GL_TRIANGLE_STRIP, and GL_TRIANGLE_FAN are supported.
GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON are not
supported. Type must either be GL_UNSIGNED_BYTE or
GL_UNSIGNED_SHORT (not GL_UNSIGNED_INT).
glDrawRangeElements()Supported.
glEnableClientState()Valid for all supported attributes.
glDisableClientState()Valid for all supported attributes.


Here are some of the typical drawings in 3D. This one is for a rotating Spiral


To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode.

EAGLView.m (for Spiral) Select all

//
// EAGLView.m
// Spiral
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// A class extension to declare private methods
@interface EAGLView ()

@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;

- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;

@end


@implementation EAGLView

@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;


// You must implement this method
+ (Class)layerClass {
return [CAEAGLLayer class];
}

#define kAnimationFrequency 60.0


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

if ((self = [super initWithCoder:coder])) {
// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);

glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c = 0;
z = -50.0f;

// Loop around in a circle three times
for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f) { // Calculate x and y values on the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bump up the z value z += 0.5f; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 0.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 0.0f, 0.0f, 0.0f); glRotatef(yRot, 1.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Set Line Width glLineWidth(3.0f); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; xRot += 0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (void)layoutSubviews { [EAGLContext setCurrentContext:context]; [self destroyFramebuffer]; [self createFramebuffer]; [self drawView]; } - (BOOL)createFramebuffer { glGenFramebuffersOES(1, &viewFramebuffer); glGenRenderbuffersOES(1, &viewRenderbuffer); glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); if (USE_DEPTH_BUFFER) { glGenRenderbuffersOES(1, &depthRenderbuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); } if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); return NO; } return YES; } - (void)destroyFramebuffer { glDeleteFramebuffersOES(1, &viewFramebuffer); viewFramebuffer = 0; glDeleteRenderbuffersOES(1, &viewRenderbuffer); viewRenderbuffer = 0; if(depthRenderbuffer) { glDeleteRenderbuffersOES(1, &depthRenderbuffer); depthRenderbuffer = 0; } } - (void)startAnimation { self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; } - (void)stopAnimation { self.animationTimer = nil; } - (void)setAnimationTimer:(NSTimer *)newTimer { [animationTimer invalidate]; animationTimer = newTimer; } - (void)setAnimationInterval:(NSTimeInterval)interval { animationInterval = interval; if (animationTimer) { [self stopAnimation]; [self startAnimation]; } } - (void)dealloc { [self stopAnimation]; if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } [context release]; [super dealloc]; } @end


And this one is for a rotating Fanned Circle.



EAGLView.m (Fanned Circle) Select all

//
// EAGLView.m
// Fanned Circle
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// A class extension to declare private methods
@interface EAGLView ()

@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;

- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;

@end


@implementation EAGLView

@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;


// You must implement this method
+ (Class)layerClass {
return [CAEAGLLayer class];
}

#define kAnimationFrequency 60.0


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

if ((self = [super initWithCoder:coder])) {
// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=1.2 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } - (void)layoutSubviews { [EAGLContext setCurrentContext:context]; [self destroyFramebuffer]; [self createFramebuffer]; [self drawView]; } - (BOOL)createFramebuffer { glGenFramebuffersOES(1, &viewFramebuffer); glGenRenderbuffersOES(1, &viewRenderbuffer); glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight); if (USE_DEPTH_BUFFER) { glGenRenderbuffersOES(1, &depthRenderbuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer); glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight); glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer); } if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) { NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)); return NO; } return YES; } - (void)destroyFramebuffer { glDeleteFramebuffersOES(1, &viewFramebuffer); viewFramebuffer = 0; glDeleteRenderbuffersOES(1, &viewRenderbuffer); viewRenderbuffer = 0; if(depthRenderbuffer) { glDeleteRenderbuffersOES(1, &depthRenderbuffer); depthRenderbuffer = 0; } } - (void)startAnimation { self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES]; } - (void)stopAnimation { self.animationTimer = nil; } - (void)setAnimationTimer:(NSTimer *)newTimer { [animationTimer invalidate]; animationTimer = newTimer; } - (void)setAnimationInterval:(NSTimeInterval)interval { animationInterval = interval; if (animationTimer) { [self stopAnimation]; [self startAnimation]; } } - (void)dealloc { [self stopAnimation]; if ([EAGLContext currentContext] == context) { [EAGLContext setCurrentContext:nil]; } [context release]; [super dealloc]; } @end


And also I have found a very nice method saveCurrentScreenToPhotoAlbum to capture the OpenGL view screen here. And below is an implementation on how to capture the screen in iPhone Simulator. You just touch/click the info button at the lower left bottom of iPhone Screen to trigger the screen capture to the Photo Album (Simulator or actual device).

To use the source codes here, you just need to create a new project from OpenGL ES Application template of XCode and copy the source codes of EAGLView.m from below and paste them for Build & Go in XCode. For this screenshot functionality, you need to add the CoreGraphics Framework to the Xcode Project before build & go.




EAGLView.m (Fanned Circle with Screen Capture) Select all

//
// EAGLView.m
// Fanned Circle with Screen Capture
//
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>

#import "EAGLView.h"

#define USE_DEPTH_BUFFER 0

// A class extension to declare private methods
@interface EAGLView ()

@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;

- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;

@end


@implementation EAGLView

@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;


// You must implement this method
+ (Class)layerClass {
return [CAEAGLLayer class];
}

#define kAnimationFrequency 60.0


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

if ((self = [super initWithCoder:coder])) {
// Get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}

animationInterval = 1.0 / kAnimationFrequency;
[self setupView];
}
return self;
}


#define GL_PI 3.1415f
GLfloat linesVertices[186];

- (void)setupView {

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(saveCurrentScreenToPhotoAlbum) forControlEvents:UIControlEventTouchUpInside];
infoButton.alpha = 0.5f;
infoButton.frame = CGRectMake(17, self.bounds.size.height-33, 33, 33);
[self addSubview:infoButton];
[self bringSubviewToFront:infoButton];

// setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

//glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GLfloat nRange = 100.0f;
GLfloat w = 320.0f, h = 480.0f;
glOrthof (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);


glMatrixMode(GL_MODELVIEW);

GLfloat x,y,z,angle; // Storage for coordinates and angles
int c;
z = 0.0f;
c = 0;
for(angle = 0.0f; angle <= GL_PI; angle += (GL_PI / 20.0f)) { // Top half of the circle x = 50.0f*sin(angle); y = 50.0f*cos(angle); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; // Bottom half of the circle x = 50.0f*sin(angle+GL_PI); y = 50.0f*cos(angle+GL_PI); // glVertex3f(x, y, z); linesVertices[c++] = x; linesVertices[c++] = y; linesVertices[c++] = z; } } - (void)drawView { static GLfloat xRot = 0.0f; static GLfloat yRot = 0.0f; static GLfloat zRot = 1.0f; const GLubyte linesColors[] = { 0.0f, 1.0f, 0.0f, 1.0f, }; [EAGLContext setCurrentContext:context]; glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glViewport(0, 0, backingWidth, backingHeight); // Clear background color glClearColor(0.5f, 0.5f, 0.5f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Setup and render the points glEnable(GL_POINT_SMOOTH); glPointSize(1.0); glVertexPointer(3, GL_FLOAT, 0, linesVertices); // Set drawing color to green glColor4f(0.0f, 1.0f, 0.0f, 0.0f); glColorPointer(4, GL_UNSIGNED_BYTE, 0, linesColors); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_LINES, 0, 189); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; static NSTimeInterval lastDrawTime; if (lastDrawTime) { NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime; zRot+=0.1 * timeSinceLastDraw; } lastDrawTime = [NSDate timeIntervalSinceReferenceDate]; } // callback for CGDataProviderCreateWithData void releaseScreenshotData(void *info, const void *data, size_t size) { free((void *)data); }; // callback for UIImageWriteToSavedPhotosAlbum - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { NSLog(@"ScreenSave finished\n"); [image release]; // release image } - (void)saveCurrentScreenToPhotoAlbum { NSInteger myDataLength = backingWidth * backingHeight * 4; // allocate array and read pixels into it. GLuint *buffer = (GLuint *) malloc(myDataLength); glReadPixels(0, 0, backingWidth, backingHeight, GL_RGBA, GL_UNSIGNED_BYTE, buffer); // gl renders “upside down” so swap top to bottom into new array. for(int y = 0; y < x =" 0;" top =" buffer[y" bottom =" buffer[(backingHeight" provider =" CGDataProviderCreateWithData(NULL," bitspercomponent =" 8;" bitsperpixel =" 4" bytesperrow =" 4" colorspaceref =" CGColorSpaceCreateDeviceRGB();" bitmapinfo =" kCGBitmapByteOrderDefault;" renderingintent =" kCGRenderingIntentDefault;" imageref =" CGImageCreate(320," myimage =" [[UIImage" viewframebuffer =" 0;" viewrenderbuffer =" 0;" depthrenderbuffer =" 0;" animationtimer =" [NSTimer" animationtimer =" nil;" animationtimer =" newTimer;" animationinterval =" interval;">


In case you might also want to know how to do screenshot for a non-OpenGL ES content programatically. This is the code for viewcontroller. If you put the code in a UIView object change self.view to self

- (void) snapUIView
{
UIGraphicsBeginImageContext(self.view.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(myImage, nil, nil, nil);
}





Wednesday, May 13, 2009

How to classdump SpringBoard header files and patch it

To classdump the SpringBoard header,

class-dump-x /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.2.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -H -o Springboard

class-dump-x is available in Mac OS X and iPhone here

class-dumped header files are not directly usable and you should use this patch script to change the import statement of the dumped header files.

springboardheaderpatch.sh Select all

#/bin/sh
cd SpringBoard
perl -w -i -p -e "s/#import \"ISDownload.h\"/#import \<iTunesStore\/ISDownload.h\>/g" *.h
perl -w -i -p -e "s/#import \"ISNetworkMonitor-Protocol.h\"/#import \<iTunesStore\/ISNetworkMonitor-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSArray.h\"/#import \<Foundation\/NSArray.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSCharacterSet.h\"/#import \<Foundation\/NSCharacterSet.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSDictionary.h\"/#import \<Foundation\/NSDictionary.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSMutableArray.h\"/#import \<Foundation\/NSMutableArray.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSObject.h\"/#import \<Foundation\/NSObject.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSObject-Protocol.h\"/#import \<Foundation\/NSObject-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSOperation.h\"/#import \<Foundation\/NSOperation.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSString.h\"/#import \<Foundation\/NSString.h\>/g" *.h
perl -w -i -p -e "s/#import \"NSUserDefaults.h\"/#import \<Foundation\/NSUserDefaults.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlert.h\"/#import \<SpringBoard\/SBAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertDisplay.h\"/#import \<SpringBoard\/SBAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertInputView.h\"/#import \<SpringBoard\/SBAlertInputView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertItem.h\"/#import \<SpringBoard\/SBAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBAlertWindow.h\"/#import \<SpringBoard\/SBAlertWindow.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBApplication.h\"/#import \<SpringBoard\/SBApplication.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBApplicationIcon.h\"/#import \<SpringBoard\/SBApplicationIcon.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBCallAlertDisplay.h\"/#import \<SpringBoard\/SBCallAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBDismissOnlyAlertItem.h\"/#import \<SpringBoard\/SBDismissOnlyAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBDisplay.h\"/#import \<SpringBoard\/SBDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBIcon.h\"/#import \<SpringBoard\/SBIcon.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBIconList.h\"/#import \<SpringBoard\/SBIconList.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBPhoneAlertItem.h\"/#import \<SpringBoard\/SBPhoneAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBPlatformController.h\"/#import \<SpringBoard\/SBPlatformController.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBRingingAlertItem.h\"/#import \<SpringBoard\/SBRingingAlertItem.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMLockEntryAlertDisplay.h\"/#import \<SpringBoard\/SBSIMLockEntryAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitAlert.h\"/#import \<SpringBoard\/SBSIMToolkitAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitGetInputDisplay.h\"/#import \<SpringBoard\/SBSIMToolkitGetInputDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSIMToolkitTextAlertDisplay.h\"/#import \<SpringBoard\/SBSIMToolkitTextAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSlidingAlertDisplay.h\"/#import \<SpringBoard\/SBSlidingAlertDisplay.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBSoundPreferences.h\"/#import \<SpringBoard\/SBSoundPreferences.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBStatusBarContentView.h\"/#import \<SpringBoard\/SBStatusBarContentView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBStatusBarInCallView.h\"/#import \<SpringBoard\/SBStatusBarInCallView.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBTVOutController.h\"/#import \<SpringBoard\/SBTVOutController.h\>/g" *.h
perl -w -i -p -e "s/#import \"SBUSSDAlert.h\"/#import \<SpringBoard\/SBUSSDAlert.h\>/g" *.h
perl -w -i -p -e "s/#import \"SpringBoard.h\"/#import \<SpringBoard\/SpringBoard.h\>/g" *.h
perl -w -i -p -e "s/#import \"TPPhonePad.h\"/#import \<TelephonyUI\/TPPhonePad.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIAlertSheetTableCell.h\"/#import \<UIKit\/UIAlertSheetTableCell.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIApplication.h\"/#import \<UIKit\/UIApplication.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIControl.h\"/#import \<UIKit\/UIControl.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIImageView.h\"/#import \<UIKit\/UIImageView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIModalView.h\"/#import \<UIKit\/UIModalView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIModalViewDelegate-Protocol.h\"/#import \<UIKit\/UIModalViewDelegate-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPageControl.h\"/#import \<UIKit\/UIPageControl.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPasscodeField.h\"/#import \<UIKit\/UIPasscodeField.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPreferencesTableCell.h\"/#import \<UIKit\/UIPreferencesTableCell.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIPushButton.h\"/#import \<UIKit\/UIPushButton.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIScroller.h\"/#import \<UIKit\/UIScroller.h\>/g" *.h
perl -w -i -p -e "s/#import \"UITextField.h\"/#import \<UIKit\/UITextField.h\>/g" *.h
perl -w -i -p -e "s/#import \"UITextFieldDelegate-Protocol.h\"/#import \<UIKit\/UITextFieldDelegate-Protocol.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIView.h\"/#import \<UIKit\/UIView.h\>/g" *.h
perl -w -i -p -e "s/#import \"UIWindow.h\"/#import \<UIKit\/UIWindow.h\>/g" *.h



Here is another version that will dump and patch the 3.0 SDK SpringBoard headers using sed instead of perl

springboard30.sh Select all

#/bin/sh
SDKVER=3.0
rm -f pringBoard/* SpringBoard/*
class-dump-x /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${SDKVER}.sdk/System/Library/CoreServices/SpringBoard.app/SpringBoard -H -o pringBoard
mkdir -p SpringBoard
for i in pringBoard/*.h
do
sed "s/\(#import \)\"\(NS.*\.h\)\"/\1\<Foundation\/\2\>/g;"\
"s/\(#import \)\"\(UI.*\.h\)\"/\1\<UIKit\/\2\>/g;"\
"s/\(#import \)\"\(IS.*\.h\)\"/\1\<iTunesStore\/\2\>/g;"\
"s/\(#import \)\"\(SB.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(TP.*\.h\)\"/\1\<TelephonyUI\/\2\>/g;"\
"s/\(#import \)\"\(SpringBoard.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(SPDaemon.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(VS.*\.h\)\"/\1\<VoiceServices\/\2\>/g;"\
"s/\(#import \)\"\(APS.*\.h\)\"/\1\<ApplePushService\/\2\>/g" $i > S$i
done
grep "import \".*h\"" SpringBoard/*.h



class dump and patch the 3.0 SDK UIKit headers using sed in iPhone

uikit30.sh Select all

#/bin/sh
class-dump /System/Library/Frameworks/UIKit.framework/UIKit -H -o IKit
mkdir -p UIKit
for i in IKit/*.h
do
sed "s/\(#import \)\"\(NS.*\.h\)\"/\1\<Foundation\/\2\>/g;"\
"s/\(#import \)\"\(UI.*\.h\)\"/\1\<UIKit\/\2\>/g;"\
"s/\(#import \)\"\(IS.*\.h\)\"/\1\<iTunesStore\/\2\>/g;"\
"s/\(#import \)\"\(SB.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(TP.*\.h\)\"/\1\<TelephonyUI\/\2\>/g;"\
"s/\(#import \)\"\(SpringBoard.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(SPDaemon.*\.h\)\"/\1\<SpringBoard\/\2\>/g;"\
"s/\(#import \)\"\(VS.*\.h\)\"/\1\<VoiceServices\/\2\>/g;"\
"s/\(#import \)\"\(APS.*\.h\)\"/\1\<ApplePushService\/\2\>/g;"\
"s/\(#import \)\"\(WebView.*\.h\)\"/\1\<WebKit\/\2\>/g;"\
"s/\(#import \)\"\(WebFrame.*\.h\)\"/\1\<WebKit\/\2\>/g;"\
"s/\(#import \)\"\(CA.*ayer.*\.h\)\"/\1\<QuartzCore\/\2\>/g;"\
"s/\(#import \)\"\(DOM.*\.h\)\"/\1\<WebKit\/\2\>/g" $i > U$i
done
grep "import \".*h\"" UIKit/*.h

Saturday, May 2, 2009

XCode GCC section missing in build settings

If you encounter this that the GCC 4 section was missing in XCode



All you need to do is to set the Active SDK to the same value as the Project Base SDK



Then you will see the GCC section again.

But it will disappear when you set it to Simulator again, this is a bug.



 
 
 

Wednesday, April 15, 2009

APNS : Pushing tweets to iPhone

see here :
http://arstechnica.com/apple/guides/2009/04/pushing-tweets-to-your-iphone-with-apple-push-notifications.ars

You can modify it to get the RSS feed as well. see code sample here

pushtweet.m Select all

while (1 > 0)
{

TreeNode *root = [[XMLParser sharedInstance] parseXMLFromURL: [NSURL URLWithString:URL_STRING]];
TreeNode *found = nil;
for (TreeNode *node in [root children])
{
if (![[node key] isEqualToString:@"channel"]) continue;
if ([[node key] isEqualToString:@"channel"])
{
found = nil;
for (TreeNode *node2 in [node children]) {
// [node2 dump];
if ([[node2 key] isEqualToString:@"item"]) {
found = node2;
break;
}
}
if (found) break;
}
}

if (found)
{
NSString *testString = [NSString stringWithFormat:@"%@:%@", [found leafForKey:@"title"], [found leafForKey:@"link"]];
NSString *prevString = [NSString stringWithContentsOfFile:TWEET_FILE encoding:NSUTF8StringEncoding error:nil];
if (![prevString isEqualToString:testString])
{
// Update with the new tweet information
NSLog(@"\nNew RSS title from %@:\n \"%@\"\n\"%@\"\n", [found leafForKey:@"title"], [[found leafForKey:@"description"] substringToIndex:30], [found leafForKey:@"link"]);

// Save the unmessed tweet to the ~/.tweet file
[testString writeToFile:TWEET_FILE atomically:YES encoding:NSUTF8StringEncoding error:nil];

// handle reserved stuff. There's got to be a better way to escape
testString = [testString stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
testString = [testString stringByReplacingOccurrencesOfString:@"'" withString:@""];
testString = [testString stringByReplacingOccurrencesOfString:@":" withString:@"-"];
testString = [testString stringByReplacingOccurrencesOfString:@"{" withString:@"("];
testString = [testString stringByReplacingOccurrencesOfString:@"}" withString:@")"];

// push it
system([PUSH_CMD UTF8String]);
}
}

[NSThread sleepForTimeInterval:(double) delay];
if (SHOW_TICK) printf("tick\n");
}


There is also a Mac Xcode project sample on how to push from Desktop App here
http://stefan.hafeneger.name/download/PushMeBabySource.zip
 
 
 

Monday, April 13, 2009

APNS : How to generate JSON payload in C

For the communication program with APNS, you have many implementation choices, either php, perl, python, ruby or even C#.

You may wonder why the sample of raw interface given by Apple is a C function.
static bool sendPayload(SSL *sslPtr, char *deviceTokenBinary, char *payloadBuff, size_t payloadLength)

Would there be many implementations that use C /C++ or Objective C ? I guess the number will be increasing if the developer is looking for scalability and performance.

If you would like to implement it in C variant, you need to implement a raw TLS/SSL socket program (and may be with threading) and the handling of JSON payload. One of the possibilities is to use open source JSON C Library, but I think it is too heavy to use it here as the communication program needs to construct the Payload message only. So I write this function (genPayloadData ) to generate the payload message for the APNS.

This source code includes the JSON escape string function and the C structure to generate the APNS Payload.

apns.c Select all

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <stdbool.h>

#define Debug 1
#define logerror dprintf
#define dprintf if (Debug) printf

#define DEVICE_BINARY_SIZE 32
#define MAXPAYLOAD_SIZE 256

typedef struct
{
char* alert;
int badge;
char* sound;
char* name_key[4]; //custom key
char* str_value[4]; // custom key value
int int_value[4]; // custom key int
char* action_loc_key; // for custom button label
char* loc_key; // formatted localized string
char* loc_args[4]; // formatted localized string arguments
} PayloadData;

/* string escaping */
const char *json_number_chars = "0123456789.+-eE";
const char *json_hex_chars = "0123456789abcdef";

char *json_escape_str(char *str)
{
char *results = (char*)malloc(200);
char *resultsPt = results;
int pos = 0, start_offset = 0;
unsigned char c;
do {
c = str[pos];
switch(c) {
case '\0':
break;
case '\b':
case '\n':
case '\r':
case '\t':
case '"':
case '\\':
case '/':
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos - start_offset;}
if(c == '\b') {memcpy(resultsPt, "\\b", 2); resultsPt+=2;}
else if(c == '\n') {memcpy(resultsPt, "\\n", 2); resultsPt+=2;}
else if(c == '\r') {memcpy(resultsPt, "\\r", 2); resultsPt+=2;}
else if(c == '\t') {memcpy(resultsPt, "\\t", 2); resultsPt+=2;}
else if(c == '"') {memcpy(resultsPt, "\\\"", 2); resultsPt+=2;}
else if(c == '\\') {memcpy(resultsPt, "\\\\", 2); resultsPt+=2;}
else if(c == '/') {memcpy(resultsPt, "\\/", 2); resultsPt+=2;}
start_offset = ++pos;
break;
default:
if(c < ' ') {
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos-start_offset;}
sprintf(resultsPt, "\\u00%c%c",
json_hex_chars[c >> 4],
json_hex_chars[c & 0xf]);
start_offset = ++pos;
} else pos++;
}
} while(c);
if(pos - start_offset > 0)
{memcpy(resultsPt, str + start_offset, pos - start_offset); resultsPt+=pos-start_offset;}
memcpy(resultsPt, "\0", 1);
dprintf("results:%s\n",results);
return results;
return 0;
}



#define APSHEAD "{\"aps\":{"
#define APSTAIL "}"


void genPayloadData(PayloadData myPaylodData, char *msgbuf) {
char *alert = (char*)malloc(200);
char *message = (char*)malloc(200);
char *sound = (char*)malloc(20);
char *badge = (char *)malloc(20);
char *msgbufPt = msgbuf;
bool isAlert=false, isBadge=false, isSound=false;
bool isToken=false;
int len;
int i;

if (myPaylodData.alert) {
if (myPaylodData.action_loc_key) {
sprintf(alert, "\"alert\":{\"body\":\"%s\",\"action-loc-key\":\"%s\"}",json_escape_str(myPaylodData.alert),json_escape_str(myPaylodData.action_loc_key));
}
else {
sprintf(alert, "\"alert\":\"%s\"",json_escape_str(myPaylodData.alert));
}
isAlert = true;
} else if (myPaylodData.loc_key) {
isToken=false;
sprintf(alert, "\"alert\":{\"loc-key\":\"%s\"",json_escape_str(myPaylodData.loc_key));
for (i=0 ; i < 4; i++) {
if (myPaylodData.loc_args[i]) {
if (isToken) {
sprintf(alert, "%s,\"%s\"",alert,json_escape_str(myPaylodData.loc_args[i]));
}
else {
sprintf(alert, "%s,\"loc-args\":[\"%s\"",alert,json_escape_str(myPaylodData.loc_args[i]));
}
isToken=true;
}
}
if (isToken) {
sprintf(alert, "%s]}",alert);
}
isAlert = true;
}
if (myPaylodData.badge > 0) {
sprintf(badge, "\"badge\":%d",myPaylodData.badge);
isBadge = true;
}
if (myPaylodData.sound) {
sprintf(sound, "\"sound\":\"%s\"",myPaylodData.sound);
isSound = true;
}
if (isAlert | isBadge | isSound) {
len = strlen(APSHEAD);
memcpy(msgbufPt, APSHEAD, len);
msgbufPt += len;
}
else {
memcpy(msgbufPt, "{", 1);
msgbufPt += 1;
}
if (isAlert) {
len = strlen(alert);
memcpy(msgbufPt, alert, len);
msgbufPt += len;
}
if (isBadge) {
if (isAlert) {
memcpy(msgbufPt++, ",", 1);
}
len = strlen(badge);
memcpy(msgbufPt, badge, len);
msgbufPt += len;
}
if (isSound) {
if (isAlert | isBadge) {
memcpy(msgbufPt++, ",", 1);
}
len = strlen(sound);
memcpy(msgbufPt, sound, len);
msgbufPt += len;
}
if (isAlert | isBadge | isSound) {
len = strlen(APSTAIL);
memcpy(msgbufPt, APSTAIL, len);
msgbufPt += len;
isToken = true;
}
isToken=false;
if (msgbufPt-msgbuf < MAXPAYLOAD_SIZE) {
for (i=0; i < 4; i++) {
if (myPaylodData.name_key[i]) {
if (myPaylodData.str_value[i]) {
sprintf(message, "%s\"%s\":\"%s\"",(isToken?",":""),json_escape_str(myPaylodData.name_key[i]),json_escape_str(myPaylodData.str_value[i]));
len = strlen(message);
if (msgbufPt-msgbuf+len<MAXPAYLOAD_SIZE) {
memcpy(msgbufPt, message, len);
msgbufPt += len;
isToken = true;
}
else {
dprintf("\n!!!!Warnings: Total Payload message overlimit (>%d) when processing %s",MAXPAYLOAD_SIZE,message);
}
}
if (myPaylodData.int_value[i]) {
sprintf(message, "%s\"%s\":%d",(isToken?",":""),json_escape_str(myPaylodData.name_key[i]),myPaylodData.int_value[i]);
len = strlen(message);
if (msgbufPt-msgbuf+len<MAXPAYLOAD_SIZE) {
memcpy(msgbufPt, message, len);
msgbufPt += len;
isToken = true;
}
else {
dprintf("\n!!!!Warnings: Total Payload message overlimit (>%d) when processing %s",MAXPAYLOAD_SIZE,message);
}
}
}
}
}
len = strlen(APSTAIL);
memcpy(msgbufPt, APSTAIL, len);
msgbufPt += len;
memcpy(msgbufPt, "\0", 1);
dprintf("\nconstructed message:%s\n",msgbuf);
if (strlen(msgbuf) > MAXPAYLOAD_SIZE) {
dprintf("\n!!!!Warnings: Payload (>%d) : %d\n",MAXPAYLOAD_SIZE,(unsigned)strlen(msgbuf));
}
else {
dprintf("\nPayload (<=%d) size : %d\n",MAXPAYLOAD_SIZE,(unsigned)strlen(msgbuf));
}
free(alert);
free(badge);
free(sound);
free(message);
}

int main(){
char msgbuf[512]; /* payload messages */

PayloadData myPaylodData = {0};
myPaylodData.badge = 3;
myPaylodData.alert = "Message from javacom";
// myPaylodData.action_loc_key = "";
// myPaylodData.loc_args[0] = "";
// myPaylodData.loc_args[1] = "";
// myPaylodData.loc_args[2] = "";
myPaylodData.sound = "received3.caf";
myPaylodData.name_key[0] = "test1";
myPaylodData.str_value[0] = "Hello iPhone";
genPayloadData(myPaylodData,msgbuf);
printf("%s\n",msgbuf);
return 0;

}


 
 
 

Friday, April 10, 2009

APNS Client Development Certificate Available Now



(1) You need to create an App ID without .* in the Program Portal (that means one cert for one app)

(2) Generate a certificate signing request from your Mac's keychain and save to disk

(3) Upload the CertificateSigningRequest.certSigningRequest to the Program Portal

(4) Wait for the generation of cert (about 1 min). Download the certificate (aps_developer_identity.cer) from the Program Portal (If you need to renew this cert, it is under the App ID that you created in step 1, and choose Action Configure)

(5) Keep (or rename them if you prefer) these 2 files (steps 2 and 4) in a safe place. You might need the CertificateSigningRequest.certSigningRequest file to request a new cert for a new app in the future or renew the old cert when expired.

(6) Suppose you have imported the aps_developer_identity.cer to the keychain. Then you have to export these new cert and the private key of this cert (not the public key) and saved as .p12 files.

(7) Then you use these commands to generate the cert and key in Mac's Terminal for PEM format (Privacy Enhanced Mail Security Certificate)

openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12
openssl pkcs12 -nocerts -out key.pem -in key.p12


(8) The cert.pem and key.pem files will be used by your own php script communicating with APNS.

(9) If you want to remove the passphase of private key in key.pem, do this

openssl rsa -in key.pem -out key.unencrypted.pem


Then combine the certificate and key

cat cert.pem key.unencrypted.pem > ck.pem


But please set the file permission of this unencrypted key by using chmod 400 and is only readable by root in a sever configuration.

(10) The testing APNS is at ssl://gateway.sandbox.push.apple.com:2195

(11) For the source codes to push payload message to the APNS, you can find them in the Developer Forum. This is the one that I used, for php script. Run this (after obtaining the device token from the testing device and with iPhone Client program setup)
php -f apns.php "My Message" 2

or if you put this php script and the ck.pem in a local web server, you can use this to test
http://127.0.0.1/apns/apns.php?message=test%20from%20javacom&badge=2&sound=received5.caf

Please be patient to get message from the sandbox server. Normally, you need 10 minutes+ to get the first message from push notification testing.

apns.php Select all

#!/usr/bin/env php
<?php
$deviceToken = '02da851dXXXXXXXXb4f2b5bfXXXXXXXXce198270XXXXXXXX0d3dac72bc87cd60'; // masked for security reason
// Passphrase for the private key (ck.pem file)
// $pass = '';

// Get the parameters from http get or from command line
$message = $_GET['message'] or $message = $argv[1] or $message = 'Message received from javacom';
$badge = (int)$_GET['badge'] or $badge = (int)$argv[2];
$sound = $_GET['sound'] or $sound = $argv[3];

// Construct the notification payload
$body = array();
$body['aps'] = array('alert' => $message);
if ($badge)
$body['aps']['badge'] = $badge;
if ($sound)
$body['aps']['sound'] = $sound;


/* End of Configurable Items */

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);

$fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
// for production change the server to ssl://gateway.push.apple.com:2195
if (!$fp) {
print "Failed to connect $err $errstr\n";
return;
}
else {
print "Connection OK\n";
}

$payload = json_encode($body);
$msg = chr(0) . pack("n",32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n",strlen($payload)) . $payload;
print "sending message :" . $payload . "\n";
fwrite($fp, $msg);
fclose($fp);
?>


(12) For iPhone Client Program, you need to edit the bundle identifier to the App ID that you created and imported the new provisioning profile for that APP ID to the XCode and iPhone. And codesign with that new provisioning profile. Then implement the following methods in AppDelegate to Build & Go

AppDelegate.m Select all

- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSLog(@"Registering Remote Notications");

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];

// Override point for customization after app launch
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}


// Delegation methods
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
const void *devTokenBytes = [devToken bytes];
// self.registered = YES;
NSLog(@"deviceToken: %@", devToken);
// [self sendProviderDeviceToken:devTokenBytes]; // custom method
}

- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSLog(@"Error in registration. Error: %@", err);
}



(13) Additional tips for sandbox testing
- The feedback service is feedback.sandbox.push.apple.com:2195
- Send your messages to gateway.sandbox.push.apple.com:2195



Here is the feedback server request php code. For the sandbox feedback server, you have to create a second dummy app to make the first one works. May be the sandbox feedback server is buggy as the production push and feedback servers does not have this problem.

php -f feedback.php

feedback.php Select all

#!/usr/bin/env php
<?php

$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
// assume the private key passphase was removed.
// stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);


$fp = stream_socket_client('ssl://feedback.sandbox.push.apple.com:2196', $error, $errorString, 60, STREAM_CLIENT_CONNECT, $ctx);
// production server is ssl://feedback.push.apple.com:2196

if (!$fp) {
print "Failed to connect feedback server: $err $errstr\n";
return;
}
else {
print "Connection to feedback server OK\n";
}

print "APNS feedback results\n";
while ($devcon = fread($fp, 38))
{
$arr = unpack("H*", $devcon);
$rawhex = trim(implode("", $arr));
$feedbackTime = hexdec(substr($rawhex, 0, 8));
$feedbackDate = date('Y-m-d H:i', $feedbackTime);
$feedbackLen = hexdec(substr($rawhex, 8, 4));
$feedbackDeviceToken = substr($rawhex, 12, 64);
print "TIMESTAMP:" . $feedbackDate . "\n";
print "DEVICE ID:" . $feedbackDeviceToken. "\n\n";
}
fclose($fp);
?>



Hints: If you want to test the production push and feedback servers, use the adhoc distribution certificate and adhoc build to your devices. Moreover, the device tokens are different for the same device under sandbox and production servers.