Advertising

Ajax Animator

A few months ago I wrote about a necessity for HTML5's success: a design tool similar to Flash.

Well, here it is: Ajax Animator. Test it:

About me

Getting Objective-C 2.0 to work on Debian's GNUstep with clang

If you are a Cocoa or Cocoa Touch developer, you may have attempted to use features such as properties in GNUstep, only to be surprised that these don't seem to be supported. This is because these are Objective-C 2.0 features.

To get the new features, the only way is to use a different compiler called clang. You may have seen this compiler used in newer releases of Xcode. This is a compiler that targets a virtual machine called LLVM before producing native code.

UPDATE May 4th 2011: GCC 4.6 has got the Objective-C 2.0 treatment, and since Debian includes GCC 4.6, I'd recommend you to try compiling your software that way. Not because it's a better compiler -- I have no idea which one works better -- but because it's there. Also, consider compiling GNUstep from trunk using GCC 4.6; it's rather easy to do. (CC=gcc-4.6 ./configure, whenever compiling a component of GNUstep).

Let's presume you managed to run an Objective-C program with GNUstep; that is, let's presume you are aware of Project Center, or GNUmakefiles. If you are didn't use GNUmakefiles, you should know that Project Center generates these in order to build your app.

Now you want to switch to clang, and you want to do so on your favorite operating system, Debian GNU/Linux. Step 1:

apt-get install clang

Step 2: Since some GNUstep files refer to , we need to have that file installed as well. Sadly it's not installed as part of any runtime; it is, instead, installed as part of gobjc-x.x package. This may change by the time you use these instructions; see: http://packages.debian.org/file:objc.h for a list of packages that contain this file. As of Dec 6th 2010, this is the package that needs to be installed:

apt-get install gobjc-4.3

You could also install gobjc-4.2, but let's go with 4.3 for now.

Step 3: We need to tell GNUstep to use the new compiler. We'll need to edit one of template makefiles that GNUmakefile files use.

sudo $EDITOR /usr/share/GNUstep/Makefiles/config.make

Yes, that's right: the easiest way I could find involves changing stuff in /usr (meaning upon package upgrade this might get overwritten -- go figure).

Step 4: Just below the license text, you should find the section "Binary and compile tools" which sets various variables such as CC, OBJCFLAGS, et cetera. Edit the relevant lines accordingly!

CC = clang
OBJCFLAGS = -I/usr/lib/gcc/i486-linux-gnu/4.3/include
CPP = clang -E

(Note the fugly path that we add to include path by setting OBJCFLAGS: We need to give clang the path to objc/objc.h; if one of them is different, edit the architecture name and gobjc version accordingly)

That's it! @property, @synthesize and other friends you made while using Objective-C 2.0 under OS X are back in da hood, makin' programming enjoyable once again. Plus, you get the extra build speed that clang provides, being somewhat faster than GCC.


rest of the post

Simple multiuser chat for POSIX systems

Here's a little multiuser chat server written for various POSIX-compatible operating systems. Written and tested on Mac OS X 10.6, but it should work on your favorite Linux, too.

Placed in public domain, use it for whatever you want (since it's so simple). 177 lines of pure C powah, dood.

Code follows after the break. server.c:

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SLOTCOUNT 300

int listener = 0;
int slots[SLOTCOUNT] = {0};
int maxslot = 0;
int usage(char* call)
{
  char* fn = strrchr(call, '/')+1;
  if(!fn)
    fn = call;
  printf("usage: %s port\n", fn);
  return 1;
}

void broadcast_message(int sender, char* msg)
{
  int i;
  for(i = 0; i < SLOTCOUNT; i++)
  {
    if(!slots[i])
      continue;
    if(i == sender)
      continue;

    send(i, msg, strlen(msg), 0);
  }

  if(sender != 0)
    printf("%s", msg);
}

int work()
{
  maxslot = listener;
  do
  {
    fd_set set;
    int i;

    FD_ZERO(&set);

    FD_SET(1,        &set);
    FD_SET(listener, &set);
    for(i=0; i < SLOTCOUNT; i++)
    {
      if(slots[i])
      {
        FD_SET(slots[i], &set);
      }
    }

    int koliko = select(maxslot+1, &set, NULL, NULL, NULL);
    if(koliko==-1)
      err(5, "select()");
    
    if(FD_ISSET(1, &set))
    {

      int size;
      if(ioctl(1, FIONREAD, &size) != -1){
        char *buf;

    //    printf("Reading %d bytes...\n", size);
        buf = malloc(size+1);
        read(1, buf, size);
        buf[size] = 0;
    //    printf("Received %s\n", buf);

        broadcast_message(0, buf);

        free(buf);
      }
      else
        err(7, "ioctl() stdio");

    }
    if(FD_ISSET(listener, &set))
    {
      int accepted = accept(listener, NULL, NULL);
      if(accepted == -1)
        err(6, "accept()");

      slots[accepted] = accepted;
      if(accepted > maxslot)
        maxslot = accepted;
    }
  
    for(i = 0; i < SLOTCOUNT; i++)
    {
      if(slots[i] && FD_ISSET(slots[i], &set))
      {
    //    printf("Received on %d\n", i);


        int size;


        if(ioctl(i, FIONREAD, &size) != -1){

          if(size > 0)
          {
            char *buf;

    //        printf("Reading %d bytes...\n", size);
            buf = malloc(size+1);
            read(i, buf, size);
            buf[size] = 0;
    //        printf("Received %s\n", buf);
            
            broadcast_message(i, buf);

            free(buf);
          }
          else
          {
            shutdown(i, SHUT_RDWR);
            close(i);

            slots[i] = 0;

    //        printf("Disconnect on %d\n", i);
          }
        }
        else
          err(8, "ioctl() net");


      }
    }
    
  } while(1);
  return 0;
}


int main(int argc, char** argv)
{
  if(argc < 2)
    return usage(argv[0]);

  if((listener = socket(PF_INET, SOCK_STREAM, 0))==-1)
    err(1, "socket()");
  
  int one=1;
  setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  
  struct sockaddr_in socketAddress;
  memset(&socketAddress, 0, sizeof(socketAddress));
  socketAddress.sin_len = sizeof(socketAddress);
  socketAddress.sin_family = AF_INET;                   // Address family (IPv4 vs IPv6)
  socketAddress.sin_port = htons(atoi(argv[1]));        // If we passed 0, the actual port would get assigned automatically by kernel and we'd have to retrieve it. Also, we use network byte order for 16bit numbers here by using htons
  socketAddress.sin_addr.s_addr = htonl(INADDR_ANY);    // We must use "network byte order" format (big-endian) for the 32bit value here by using htonl

  printf("Listener: %d\n", listener);
  if(bind(listener, (struct sockaddr*) &socketAddress, sizeof(socketAddress))==-1)
    err(2, "bind()");

  if(listen(listener, 1) == -1)
    err(3, "listen()");

  return work();

}

Makefile:

CFLAGS=-g3
LDFLAGS=-g3

all: server

server: server.o
  gcc server.o $(LDFLAGS) -o server

clean:
  -rm server server.o

Note, in Makefiles, instead of two spaces, use tabs. You need to.

Try it out:

make
./server 1337

And on a few other terminals (or other machines):

telnet localhost 1337

Works over the network, too, of course.

Nota bene, you will probably need to install telnet on your Linux machine. Also, telnet does not come with latest Windows editions (Vista and 7); you will need to use putty over there.


rest of the post

Toying with iChat's AV over XMPP, part one

I added fake caps&features to Z-XMPP to simulate iChat's AV support. List of the caps&features that I added:

this.features.push("apple:profile:bundle-transfer"); this.features.push("apple:profile:efh-transfer"); this.features.push("apple:profile:transfer-extensions:rsrcfork"); this.features.push("http://www.apple.com/xmpp/message-attachments"); this.featuresExt["ice"] = ["apple:iq:vc:ice"]; this.featuresExt["recauth"] = ["apple:iq:vc:recauth"]; this.featuresExt["rdserver"] = ["apple:iq:rd:server"]; this.featuresExt["maudio"] = ["apple:iq:vc:multiaudio"]; this.featuresExt["audio"] = ["apple:iq:vc:audio"]; this.featuresExt["rdclient"] = ["apple:iq:rd:client"]; this.featuresExt["mvideo"] = ["apple:iq:vc:multivideo"]; this.featuresExt["auxvideo"] = ["apple:iq:vc:auxvideo"]; this.featuresExt["rdmuxing"] = ["apple:iq:rd:muxing"]; this.featuresExt["avcap"] = ["apple:iq:vc:capable"]; this.featuresExt["avavail"] = ["apple:iq:vc:available"]; this.featuresExt["video"] = ["apple:iq:vc:video"];

And here's what I get when I press call:

0
62706c6973743030d501020304050607081a1a5b564353657373696f6e4944595643494345446174615e5643496e7669746565734c6973745f101156435365637572697479456e61626c65645e56434f72646572497346696e616c121c3b4d784f11020b040b73747265616d747970656481e803840140848484134e534d757461626c6544696374696f6e6172790084840c4e5344696374696f6e617279008484084e534f626a65637400858401690392848484084e53537472696e67019584012b11636f6e6e656374696f6e2068656c7065728692849396019284979807436f6d6d4e41548692848484084e534e756d626572008484074e5356616c7565009584012a849696008686928497980776657273696f6e8692849b9b9d96028692849798075349502d4943458692848484064e534461746100959681280184065b323936635d00000000000000010000000115f9ddce00000000002d000000000000656e3100000000000000000000000000c0a8018400000000d69a20fffe63f7424012000000000000656e3100000000000000000000000000c0a8018400000000d69a20fffe63f7424012000000000000656e317e0000000000000000000000003f57fe7bffffffff2965df00019c08bd40120000000000000000000000000003000000012b8105b7005d2440004600000000000065787465726e616c00030000000000005d8b25d300000000bea594be2d8d3240c326000000000000656e3100000000000000000000000000c0a8018400000000d69a20fffe63f7424012000000000000656e317e0000000000000000000000003f57fe7bffffffff2965df00019c08bd4012000080681f408686a2091cdb0a0b0c0d0e0f101112131415161718161518151a1b1556726561736f6e59696e76697465644279597663506172747949445c73656e64696e67566964656f5c70726573656e74697479494457617264526f6c655c73656e64696e67417564696f557374617465587573696e674943455e70726573656e746974794e616d65556572726f7210005f101e69767563696361407468652d6576696c2d6d6163626f6f6b2e6c6f63616c5f101f69767563696361407468652d6576696c2d6d6163626f6f6b2e6c6f63616c310909086b004900760061006e002000560075010d006900630061db0a0b0c0d0e0f101112131415161d181f1518211823155f101e706572696361407468652d6576696c2d6d6163626f6f6b2e6c6f63616c32095f101d706572696361407468652d6576696c2d6d6163626f6f6b2e6c6f63616c0910020956706572696361080800080013001f00290038004c005b0060026f027202890290029a02a402b102be02c602d302d902e202f102f702f9031a033c033d033e033f0356036d038e038f03af03b003b203b303ba03bb00000000000002010000000000000026000000000000000000000000000003bc
4

The long thing is a hexadecimal (yes, hex -- strangely not base64) string which, when decoded, is pretty much binary looking. There are a few things that seem to be strings. Let's try to find something readable in the string:

(removed since the binary text included some characters that confused various XML decoders; use a hex to string converter such as this one)

This mentions the caller and the receiver of the call. We can also see several interesting strings such as NSMutableDictionary, NSDictionary and NSObject; that is the inheritance path for NSMutableDictionary in Objective-C. This invitation looks like something serialized in Objective-C into some binary format, then converted into hex, inserted in XMPP XML, and sent over the wire. In fact... "bplist"... that sounds oddly similar to b-plist... binary plist? That could probably be easily decoded with an Objective-C based XMPP client!

Not only that: decoding that with, for example, Python (import binascii, binascii.unhexlify(data)) and writing to file with extension .plist shows that we can open this in Apple's Property List Editor, uncovering VCICEData (a large binary blob - "data"), VCInviteesList (an array), VCOrderIsFinal (a boolean set to false), VCSecurityEnabled (a boolean set to false), and VCSessionID (a "number").

VCInviteesList members are two dictionaries. In the dictionaries, we have these items: ardRole (number 0), error (number 0), invitedBy (string with caller jid), presentityID (string with caller or callee jid), presentityName (string with caller's or callee's name), sendingAudio (boolean set to true), sendingVideo (boolean set to false), state (0 with caller, 2 with callee), usingICE (boolean set to false with caller, true with callee), and finally vcPartyID (string with entity's jid suffixed by 1 or 2, depending on place in array).

Surprisingly, iChat ignores completely when an iq-error stanza is sent to signify that the feature needed to parse iq-query is not supported.

Googling for AVChatConferenceData surprisingly uncovers nothing. Could it really be that noone has yet tried to discover how iChat's AV works in combination with XMPP?

Oh, and canceling the call goes like this: 1

I'm not sure I'll keep on pursuing this, especially since I didn't write a client to play with. I'm not keen on studying Telepathy, Gabble, Sofia-SIP and Farsight, closest thing one could find to something opensource that could be used to implement iChat's SIP-based AV. I don't exactly have the time required to write a full XMPP client for desktops or iOS just to BEGIN trying to figure out iChat's AV -- maybe I'll be crazy enough some time in the future :-)

Have fun!


rest of the post

Getting started with Objective-C?

If you want to get started with Objective-C, and you have some background in C/C++, read this neat post I randomly stumbled upon: "Why Objective-C is cool". It's a pretty nice description and hopefully removes a lot of WTFs from a newbie.

Then, read CocoaLab's free e-book "Become an Xcoder".

Finally, this is the longest route, but the one I went with: watch all 18 hours + extras of CS193P iPhone Application Development 2009 (and then go watch some of the 2010 lessons, too). It takes a long time to watch, but it's worth it.


rest of the post

Some tips on building fuse-python on Mac OS X

I can't document everything from scratch since I don't have a machine that's "virgin" enough for me to document that. This machine has been touched by Fink, MacPorts, Rudix, and several hand-build packages; I'm not sure if MacFUSE installs the necessary stuff for pkg-config.

But, let's presume you: a) installed macfuse, b) fetched pkg-config source code from freedesktop.org (I took 0.25) c) built it and installed it (./configure, make, sudo make install) d) switched to pristine Python 2.6 that Apple ships with Snow Leopard.

I'm also obviously using Snow Leopard, so no guarantees about other OS X versions.

Ok, and now come the tips...

Fixing pkg-config spec

You need to screw around with /usr/local/lib/pkgconfig/fuse.pc. Last line should be:

Cflags: -I${includedir}/fuse -D_FILE_OFFSET_BITS=64

change that into:

Cflags: -I${includedir}/fuse -D__FreeBSD__=10 -D_FILE_OFFSET_BITS=64

Now for something undocumented. We're still not done fixing fuse.pc. Apart form adding above reference to FreeBSD, add this (source: Erik Larsson):

Cflags: -I${includedir}/fuse -D__FreeBSD__=10 -D_FILE_OFFSET_BITS=64
-D__DARWIN_64_BIT_INO_T=0

Attempting to build fuse-python-hg

Hopefully you fetched it from upstream Mercurial repo: http://mercurial.creo.hu/repos/fuse-python/hg since that's what I did. Current tip is changeset 111:c490d831ae11 -- if something breaks for you, try switching to that changeset.

So now you run:

/usr/bin/python setup.py build

(full path to Python specified, since you want to be sure you're running Apple's Python)

Now you get a few interesting errors:

running build
running build_py
running build_ext
building 'fuseparts._fusemodule' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/usr/local/include/fuse -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c fuseparts/_fusemodule.c -o build/temp.macosx-10.6-universal-2.6/fuseparts/_fusemodule.o -D__FreeBSD__=10 -D_FILE_OFFSET_BITS=64
In file included from /System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/Python.h:58,
from fuseparts/_fusemodule.c:35:
/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/pyport.h:513:23: error: osreldate.h: No such file or directory
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
In file included from /System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/Python.h:58,
from fuseparts/_fusemodule.c:35:
/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/pyport.h:513:23: error: osreldate.h: No such file or directory
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
In file included from /System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/Python.h:58,
from fuseparts/_fusemodule.c:35:
/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/pyport.h:513:23: error: osreldate.h: No such file or directory
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
lipo: can't open input file: /var/folders/mc/mcBRWcxRFySy2lZ7jzugjU+++TI/-Tmp-//ccXQWCqD.out (No such file or directory)
error: command 'gcc-4.2' failed with exit status 1

Heh, would you look at that -- we're missing a file.

/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/pyport.h:513:23: error: osreldate.h: No such file or directory

Now how are we going to fix that? Motokazu Nishimura has simply touch'ed this file. Let's do that -- I would suggest you do remove it afterwards.

The-Evil-MacBook:fuse-python-hg ivucica$ /usr/bin/python setup.py  buildrunning build
running build_py
running build_ext
building 'fuseparts._fusemodule' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/usr/local/include/fuse -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c fuseparts/_fusemodule.c -o build/temp.macosx-10.6-universal-2.6/fuseparts/_fusemodule.o -D__FreeBSD__=10 -D_FILE_OFFSET_BITS=64
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c: In function ‘Fuse_main’:
fuseparts/_fusemodule.c:1009: warning: assignment from incompatible pointer type
fuseparts/_fusemodule.c:1011: warning: assignment from incompatible pointer type
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/fuseparts/_fusemodule.o -L/usr/local/lib -lfuse -liconv -o build/lib.macosx-10.6-universal-2.6/fuseparts/_fusemodule.so

Ok, let's remove that file -- it's always a bad idea to modify stuff in /System on Mac!

The-Evil-MacBook:fuse-python-hg ivucica$ sudo rm /System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6/osreldate.h

How to switch to Apple's Python?

Any installer that I had didn't touch Python that was installed by Apple. So probably all you need to do is switch back to using "python" executable installed in /usr/bin.

export PATH=/usr/bin:$PATH

Note, if you've overridden anything else commonly used from /usr/bin by placing path of alternative program before /usr/bin, that will be restored now as well. For example, if you have non-system ls (for whatever reason), and you added that by placing /usr/local/bin before /usr/bin... well, now you're back to Apple's original variant.

Where to get various libs?

While google-docs-fs manual already specifies which libs you need, they didn't mention where to get 'em EXACTLY, and which version&variant.

pkg-config

grab 0.25 from http://pkg-config.freedesktop.org/releases/. Then: tar xvvfz pkg-config-0.25.tar.gz; cd pkg-config-0.25; ./configure; make; sudo make install

MacFUSE

grab latest release from http://code.google.com/p/macfuse/downloads/list. Just install it through GUI.

Fuse-Python

After installing Mercurial for OS X through GUI, fetch Fuse-Python by doing:

hg clone http://mercurial.creo.hu/repos/fuse-python/hg fuse/python

Install it as explained in section "Attempting to build fuse-python-hg", above.

gdata-python-client

Grab gdata-2.0.12.tar.gz from http://code.google.com/p/gdata-python-client/downloads/list

tar xvvfz gdata-2.0.12.tar.gz
cd gdata-2.0.12
sudo /usr/bin/python setup.py install

Installing google-docs-fs

I've worked on a few fixes for google-docs-fs. They're available in my repository clone. You can fetch it by doing:

 hg clone https://ivucica-gdocsfs-mac.googlecode.com/hg/ ivucica-gdocsfs-mac

To build, do as you would with mainline:

/usr/bin/python setup.py build

I'm not sure you should do this, but you can install systemwide:

sudo /usr/bin/python setup.py install

I'll instead keep on running from the development directory, thank you very much ;)

Note that my clone will almost certainly not be updated later on; you could also pull-and-merge from the main repository, and pray that there are no conflicts.

Testing

Initially I didn't bother getting gmount script to work. Unless you use my clone, it probably won't work. You could test by going to google-docs-fs directory, and:

/usr/bin/python gmount.py username@gmail.com somedir/

If you use my clone, then use

./gmount username@gmail.com somedir/

To see debug info, add "-d" to end of that command line.

I am happy to report that I got google-docs-fs to work acceptably on my Mac in changeset c4da54c09b5c in my clone :-)

Major issues

  1. Don't bother using any Mac program that utilizes NSDocument APIs to save directly to the gmount'ed volume. TextEdit, for example, will fail. It requires ability to move around/write randomly-named filenames, something google-docs-fs does not support.
  2. google-docs-fs is EXTREMELY slow; every operation is done directly on server, and not queued for later execution. This means you should most definitely turn off icon previews in Finder as soon as you can, and you should do your work elsewhere, only using google-docs-fs as a handly way to do uploading to Google Docs. Forget about directly working on the remote volume.

10/10/2013: Updated formatting and nothing else


rest of the post