Initial checkin of a native Mac OS X port, sharing most of its code
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 15 Feb 2005 21:45:50 +0000 (21:45 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 15 Feb 2005 21:45:50 +0000 (21:45 +0000)
with the Unix port and layering a Cocoa GUI on top. The basics all
work: there's a configuration panel and a terminal window, the
timing interface works and the select interface functions. The same
application can run both SSH (or other network) connections and
local pty sessions, and multiple sessions in the same process are
fully supported.

However, it's horribly unfinished in a wide variety of other ways;
anyone interested is invited to read README.OSX and wince at the
length and content of its `unfinished' list.

git-svn-id: svn://svn.tartarus.org/sgt/putty@5308 cda61777-01e9-0310-a592-d414129be87e

12 files changed:
Recipe
macosx/Makefile [new file with mode: 0644]
macosx/README.OSX [new file with mode: 0644]
macosx/osx.h [new file with mode: 0644]
macosx/osxclass.h [new file with mode: 0644]
macosx/osxctrls.m [new file with mode: 0644]
macosx/osxdlg.m [new file with mode: 0644]
macosx/osxmain.m [new file with mode: 0644]
macosx/osxsel.m [new file with mode: 0644]
macosx/osxwin.m [new file with mode: 0644]
mkfiles.pl
puttyps.h

diff --git a/Recipe b/Recipe
index ce9d052..9fc231a 100644 (file)
--- a/Recipe
+++ b/Recipe
 !makefile lcc windows/Makefile.lcc
 !makefile gtk unix/Makefile.gtk
 !makefile mpw mac/Makefile.mpw
+!makefile osx macosx/Makefile
 # Source directories.
 !srcdir charset/
 !srcdir windows/
 !srcdir unix/
 !srcdir mac/
+!srcdir macosx/
 
 # Help text added to the top of each Makefile, with /D converted
 # into -D as appropriate for the particular Makefile.
@@ -174,6 +176,9 @@ install:
 install-strip:
        $(MAKE) install INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s"
 !end
+!begin osx
+CFLAGS += -DMACOSX
+!end
 
 # ------------------------------------------------------------
 # Definitions of object groups. A group name, followed by an =,
@@ -190,8 +195,9 @@ GUITERM  = TERMINAL window windlg winctrls sizetip winucs winprint
          + winutils wincfg
 
 # Same thing on Unix.
-UXTERM   = TERMINAL gtkwin gtkdlg gtkcols gtkpanel gtkcfg uxcfg uxucs uxprint
-         + xkeysym timing
+UXTERM   = TERMINAL uxcfg uxucs uxprint timing
+GTKTERM  = UXTERM gtkwin gtkcfg gtkdlg gtkcols gtkpanel xkeysym
+OSXTERM  = UXTERM osxwin osxdlg osxctrls
 
 # Non-SSH back ends (putty, puttytel, plink).
 NONSSH   = telnet raw rlogin ldisc pinger
@@ -212,6 +218,7 @@ SFTP     = sftp int64 logging
 MISC     = timing misc version settings tree234 proxy
 WINMISC  = MISC winstore winnet cmdline windefs winmisc pproxy wintime
 UXMISC   = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy time
+OSXMISC  = MISC uxstore uxsel osxsel uxnet uxmisc uxproxy time
 MACMISC  = MISC macstore macnet mtcpnet otnet macmisc macabout pproxy
 
 # Character set library, for use in pterm.
@@ -251,11 +258,11 @@ puttygen : [G] winpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshpubk sshaes sshsh512 import winutils puttygen.res tree234
         + notiming LIBS wintime
 
-pterm    : [X] UXTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
+pterm    : [X] GTKTERM uxmisc misc ldisc settings uxpty uxsel BE_NONE uxstore
          + uxsignal CHARSET cmdline uxpterm version time
-putty    : [X] UXTERM uxmisc misc ldisc settings uxsel BE_ALL uxstore
+putty    : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_ALL uxstore
          + uxsignal CHARSET uxputty NONSSH UXSSH UXMISC ux_x11
-puttytel : [X] UXTERM uxmisc misc ldisc settings uxsel BE_NOSSH
+puttytel : [X] GTKTERM uxmisc misc ldisc settings uxsel BE_NOSSH
         + uxstore uxsignal CHARSET uxputty NONSSH UXMISC
 
 plink    : [U] uxplink uxcons NONSSH UXSSH BE_ALL logging UXMISC uxsignal ux_x11
@@ -277,3 +284,6 @@ PuTTYtel : [M] terminal wcwidth ldiscucs logging BE_NOSSH mac macdlg
 PuTTYgen : [M] macpgen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version
          + sshrand macnoise sshsha macstore misc sshrsa sshdss macmisc sshpubk
          + sshaes sshsh512 import macpgen.rsrc macpgkey macabout
+
+PuTTY    : [MX] osxmain OSXTERM OSXMISC CHARSET BE_ALL NONSSH UXSSH
+         + ux_x11 uxpty uxsignal testback
diff --git a/macosx/Makefile b/macosx/Makefile
new file mode 100644 (file)
index 0000000..3fb6f09
--- /dev/null
@@ -0,0 +1,789 @@
+# Makefile for putty under Mac OS X.
+#
+# This file was created by `mkfiles.pl' from the `Recipe' file.
+# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.
+#
+# Extra options you can set:
+#
+#  - VER=-DSNAPSHOT=1999-01-25
+#      Generates executables whose About box report them as being a
+#      development snapshot.
+#
+#  - VER=-DRELEASE=0.43
+#      Generates executables whose About box report them as being a
+#      release version.
+#
+#  - COMPAT=-DAUTO_WINSOCK
+#      Causes PuTTY to assume that <windows.h> includes its own WinSock
+#      header file, so that it won't try to include <winsock.h>.
+#
+#  - COMPAT=-DWINSOCK_TWO
+#      Causes the PuTTY utilities to include <winsock2.h> instead of
+#      <winsock.h>, except Plink which _needs_ WinSock 2 so it already
+#      does this.
+#
+#  - COMPAT=-DNO_SECURITY
+#      Disables Pageant's use of <aclapi.h>, which is not available
+#      with some development environments (such as older versions of
+#      the Cygwin/mingw GNU toolchain). This means that Pageant
+#      won't care about the local user ID of processes accessing it; a
+#      version of Pageant built with this option will therefore refuse
+#      to run under NT-series OSes on security grounds (although it
+#      will run fine on Win95-series OSes where there is no access
+#      control anyway).
+#
+#  - COMPAT=-DNO_MULTIMON
+#      Disables PuTTY's use of <multimon.h>, which is not available
+#      with some development environments. This means that PuTTY's
+#      full-screen mode (configurable to work on Alt-Enter) will
+#      not behave usefully in a multi-monitor environment.
+#
+#      Note that this definition is always enabled in the Cygwin
+#      build, since at the time of writing this <multimon.h> is
+#      known not to be available in Cygwin.
+#
+#  - COMPAT=-DNO_IPV6
+#      Disables PuTTY's ability to make IPv6 connections, enabling
+#      it to compile under development environments which do not
+#      support IPv6 in their header files.
+#
+#  - COMPAT=-DMSVC4
+#  - RCFL=-DMSVC4
+#      Makes a couple of minor changes so that PuTTY compiles using
+#      MSVC 4. You will also need /DNO_SECURITY and /DNO_MULTIMON.
+#
+#  - RCFL=-DASCIICTLS
+#      Uses ASCII rather than Unicode to specify the tab control in
+#      the resource file. Probably most useful when compiling with
+#      Cygnus/mingw32, whose resource compiler may have less of a
+#      problem with it.
+#
+#  - XFLAGS=-DTELNET_DEFAULT
+#      Causes PuTTY to default to the Telnet protocol (in the absence
+#      of Default Settings and so on to the contrary). Normally PuTTY
+#      will default to SSH.
+#
+#  - XFLAGS=-DDEBUG
+#      Causes PuTTY to enable internal debugging.
+#
+#  - XFLAGS=-DMALLOC_LOG
+#      Causes PuTTY to emit a file called putty_mem.log, logging every
+#      memory allocation and free, so you can track memory leaks.
+#
+#  - XFLAGS=-DMINEFIELD
+#      Causes PuTTY to use a custom memory allocator, similar in
+#      concept to Electric Fence, in place of regular malloc(). Wastes
+#      huge amounts of RAM, but should cause heap-corruption bugs to
+#      show up as GPFs at the point of failure rather than appearing
+#      later on as second-level damage.
+#
+CC = $(TOOLPATH)gcc
+
+CFLAGS = -O2 -Wall -Werror -g -I.././ -I../charset/ -I../windows/ -I../unix/ \
+               -I../mac/ -I../macosx/
+MLDFLAGS = -framework Cocoa
+ULDFLAGS =
+all: PuTTY plink pscp psftp puttygen
+CFLAGS += -DMACOSX
+
+PuTTY.app:
+       mkdir -p $@
+PuTTY.app/Contents: PuTTY.app
+       mkdir -p $@
+PuTTY.app/Contents/MacOS: PuTTY.app/Contents
+       mkdir -p $@
+PuTTY: PuTTY.app/Contents/MacOS/PuTTY $(PuTTY_extra)
+
+PuTTY.app/Contents/MacOS/PuTTY: PuTTY.app/Contents/MacOS be_all.o config.o \
+               cproxy.o dialog.o fromucs.o ldisc.o ldiscucs.o localenc.o \
+               logging.o macenc.o mimeenc.o minibidi.o misc.o osxctrls.o \
+               osxdlg.o osxmain.o osxsel.o osxwin.o pinger.o portfwd.o \
+               proxy.o raw.o rlogin.o sbcs.o sbcsdat.o settings.o slookup.o \
+               ssh.o sshaes.o sshblowf.o sshbn.o sshcrc.o sshcrcda.o \
+               sshdes.o sshdh.o sshdss.o sshmd5.o sshpubk.o sshrand.o \
+               sshrsa.o sshsh512.o sshsha.o sshzlib.o telnet.o terminal.o \
+               testback.o time.o timing.o toucs.o tree234.o utf8.o ux_x11.o \
+               uxagentc.o uxcfg.o uxmisc.o uxnet.o uxnoise.o uxprint.o \
+               uxproxy.o uxpty.o uxsel.o uxsignal.o uxstore.o uxucs.o \
+               version.o wcwidth.o wildcard.o x11fwd.o xenc.o
+       $(CC) $(MLDFLAGS) -o $@ be_all.o config.o cproxy.o dialog.o \
+               fromucs.o ldisc.o ldiscucs.o localenc.o logging.o macenc.o \
+               mimeenc.o minibidi.o misc.o osxctrls.o osxdlg.o osxmain.o \
+               osxsel.o osxwin.o pinger.o portfwd.o proxy.o raw.o rlogin.o \
+               sbcs.o sbcsdat.o settings.o slookup.o ssh.o sshaes.o \
+               sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \
+               sshdss.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh512.o \
+               sshsha.o sshzlib.o telnet.o terminal.o testback.o time.o \
+               timing.o toucs.o tree234.o utf8.o ux_x11.o uxagentc.o \
+               uxcfg.o uxmisc.o uxnet.o uxnoise.o uxprint.o uxproxy.o \
+               uxpty.o uxsel.o uxsignal.o uxstore.o uxucs.o version.o \
+               wcwidth.o wildcard.o x11fwd.o xenc.o 
+
+plink: be_all.o cmdline.o cproxy.o ldisc.o logging.o misc.o pinger.o \
+               portfwd.o proxy.o raw.o rlogin.o settings.o ssh.o sshaes.o \
+               sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \
+               sshdss.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh512.o \
+               sshsha.o sshzlib.o telnet.o time.o timing.o tree234.o \
+               ux_x11.o uxagentc.o uxcons.o uxmisc.o uxnet.o uxnoise.o \
+               uxplink.o uxproxy.o uxsel.o uxsignal.o uxstore.o version.o \
+               wildcard.o x11fwd.o
+       $(CC) $(ULDFLAGS) -o $@ be_all.o cmdline.o cproxy.o ldisc.o \
+               logging.o misc.o pinger.o portfwd.o proxy.o raw.o rlogin.o \
+               settings.o ssh.o sshaes.o sshblowf.o sshbn.o sshcrc.o \
+               sshcrcda.o sshdes.o sshdh.o sshdss.o sshmd5.o sshpubk.o \
+               sshrand.o sshrsa.o sshsh512.o sshsha.o sshzlib.o telnet.o \
+               time.o timing.o tree234.o ux_x11.o uxagentc.o uxcons.o \
+               uxmisc.o uxnet.o uxnoise.o uxplink.o uxproxy.o uxsel.o \
+               uxsignal.o uxstore.o version.o wildcard.o x11fwd.o 
+
+pscp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pinger.o \
+               portfwd.o proxy.o pscp.o settings.o sftp.o ssh.o sshaes.o \
+               sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \
+               sshdss.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh512.o \
+               sshsha.o sshzlib.o time.o timing.o tree234.o uxagentc.o \
+               uxcons.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \
+               uxsftp.o uxstore.o version.o wildcard.o x11fwd.o
+       $(CC) $(ULDFLAGS) -o $@ be_none.o cmdline.o cproxy.o int64.o \
+               logging.o misc.o pinger.o portfwd.o proxy.o pscp.o \
+               settings.o sftp.o ssh.o sshaes.o sshblowf.o sshbn.o sshcrc.o \
+               sshcrcda.o sshdes.o sshdh.o sshdss.o sshmd5.o sshpubk.o \
+               sshrand.o sshrsa.o sshsh512.o sshsha.o sshzlib.o time.o \
+               timing.o tree234.o uxagentc.o uxcons.o uxmisc.o uxnet.o \
+               uxnoise.o uxproxy.o uxsel.o uxsftp.o uxstore.o version.o \
+               wildcard.o x11fwd.o 
+
+psftp: be_none.o cmdline.o cproxy.o int64.o logging.o misc.o pinger.o \
+               portfwd.o proxy.o psftp.o settings.o sftp.o ssh.o sshaes.o \
+               sshblowf.o sshbn.o sshcrc.o sshcrcda.o sshdes.o sshdh.o \
+               sshdss.o sshmd5.o sshpubk.o sshrand.o sshrsa.o sshsh512.o \
+               sshsha.o sshzlib.o time.o timing.o tree234.o uxagentc.o \
+               uxcons.o uxmisc.o uxnet.o uxnoise.o uxproxy.o uxsel.o \
+               uxsftp.o uxstore.o version.o wildcard.o x11fwd.o
+       $(CC) $(ULDFLAGS) -o $@ be_none.o cmdline.o cproxy.o int64.o \
+               logging.o misc.o pinger.o portfwd.o proxy.o psftp.o \
+               settings.o sftp.o ssh.o sshaes.o sshblowf.o sshbn.o sshcrc.o \
+               sshcrcda.o sshdes.o sshdh.o sshdss.o sshmd5.o sshpubk.o \
+               sshrand.o sshrsa.o sshsh512.o sshsha.o sshzlib.o time.o \
+               timing.o tree234.o uxagentc.o uxcons.o uxmisc.o uxnet.o \
+               uxnoise.o uxproxy.o uxsel.o uxsftp.o uxstore.o version.o \
+               wildcard.o x11fwd.o 
+
+puttygen: cmdgen.o import.o misc.o notiming.o sshaes.o sshbn.o sshdes.o \
+               sshdss.o sshdssg.o sshmd5.o sshprime.o sshpubk.o sshrand.o \
+               sshrsa.o sshrsag.o sshsh512.o sshsha.o time.o tree234.o \
+               uxcons.o uxgen.o uxmisc.o uxnoise.o uxstore.o version.o
+       $(CC) $(ULDFLAGS) -o $@ cmdgen.o import.o misc.o notiming.o sshaes.o \
+               sshbn.o sshdes.o sshdss.o sshdssg.o sshmd5.o sshprime.o \
+               sshpubk.o sshrand.o sshrsa.o sshrsag.o sshsh512.o sshsha.o \
+               time.o tree234.o uxcons.o uxgen.o uxmisc.o uxnoise.o \
+               uxstore.o version.o 
+
+be_all.o: ../be_all.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+be_none.o: ../be_none.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+be_nossh.o: ../be_nossh.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+cmdgen.o: ../cmdgen.c ../putty.h ../ssh.h ../puttyps.h ../network.h \
+               ../misc.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+cmdline.o: ../cmdline.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+config.o: ../config.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+cproxy.o: ../cproxy.c ../putty.h ../ssh.h ../network.h ../proxy.h \
+               ../puttyps.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+dialog.o: ../dialog.c ../putty.h ../dialog.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+fromucs.o: ../charset/fromucs.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+gtkcfg.o: ../unix/gtkcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+gtkcols.o: ../unix/gtkcols.c ../unix/gtkcols.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+gtkdlg.o: ../unix/gtkdlg.c ../unix/gtkcols.h ../unix/gtkpanel.h ../putty.h \
+               ../storage.h ../dialog.h ../tree234.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+gtkpanel.o: ../unix/gtkpanel.c ../unix/gtkpanel.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+gtkwin.o: ../unix/gtkwin.c ../putty.h ../terminal.h ../puttyps.h \
+               ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+import.o: ../import.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \
+               ../network.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+int64.o: ../int64.c ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+ldisc.o: ../ldisc.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \
+               ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+ldiscucs.o: ../ldiscucs.c ../putty.h ../terminal.h ../ldisc.h ../puttyps.h \
+               ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+localenc.o: ../charset/localenc.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+logging.o: ../logging.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+mac.o: ../mac/mac.c ../mac/macresid.h ../putty.h ../ssh.h ../terminal.h \
+               ../mac/mac.h ../puttyps.h ../network.h ../misc.h \
+               ../puttymem.h ../int64.h ../tree234.h ../charset/charset.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macabout.o: ../mac/macabout.c ../putty.h ../mac/mac.h ../mac/macresid.h \
+               ../puttyps.h ../network.h ../misc.h ../charset/charset.h \
+               ../tree234.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macctrls.o: ../mac/macctrls.c ../putty.h ../mac/mac.h ../mac/macresid.h \
+               ../dialog.h ../tree234.h ../puttyps.h ../network.h ../misc.h \
+               ../charset/charset.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macdlg.o: ../mac/macdlg.c ../putty.h ../dialog.h ../mac/mac.h \
+               ../mac/macresid.h ../storage.h ../puttyps.h ../network.h \
+               ../misc.h ../charset/charset.h ../tree234.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macenc.o: ../charset/macenc.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macevlog.o: ../mac/macevlog.c ../putty.h ../mac/mac.h ../mac/macresid.h \
+               ../terminal.h ../puttyps.h ../network.h ../misc.h \
+               ../charset/charset.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macmisc.o: ../mac/macmisc.c ../putty.h ../mac/mac.h ../ssh.h ../puttyps.h \
+               ../network.h ../misc.h ../charset/charset.h ../tree234.h \
+               ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macnet.o: ../mac/macnet.c ../putty.h ../network.h ../mac/mac.h ../ssh.h \
+               ../puttyps.h ../misc.h ../charset/charset.h ../tree234.h \
+               ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macnoise.o: ../mac/macnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macpgen.o: ../mac/macpgen.c ../mac/macpgrid.h ../putty.h ../ssh.h \
+               ../mac/mac.h ../puttyps.h ../network.h ../misc.h \
+               ../puttymem.h ../int64.h ../charset/charset.h ../tree234.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macpgkey.o: ../mac/macpgkey.c ../putty.h ../mac/mac.h ../mac/macpgrid.h \
+               ../ssh.h ../puttyps.h ../network.h ../misc.h \
+               ../charset/charset.h ../tree234.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macstore.o: ../mac/macstore.c ../putty.h ../storage.h ../mac/mac.h \
+               ../mac/macresid.h ../puttyps.h ../network.h ../misc.h \
+               ../charset/charset.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macterm.o: ../mac/macterm.c ../mac/macresid.h ../putty.h \
+               ../charset/charset.h ../mac/mac.h ../terminal.h ../puttyps.h \
+               ../network.h ../misc.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+macucs.o: ../mac/macucs.c ../putty.h ../terminal.h ../misc.h ../mac/mac.h \
+               ../puttyps.h ../network.h ../tree234.h ../puttymem.h \
+               ../charset/charset.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+mimeenc.o: ../charset/mimeenc.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+minibidi.o: ../minibidi.c ../misc.h ../puttymem.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+misc.o: ../misc.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+mtcpnet.o: ../mac/mtcpnet.c ../putty.h ../network.h ../mac/mac.h \
+               ../puttyps.h ../misc.h ../charset/charset.h ../tree234.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+nocproxy.o: ../nocproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+notiming.o: ../notiming.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+osxctrls.o: ../macosx/osxctrls.m ../putty.h ../dialog.h ../macosx/osxclass.h \
+               ../tree234.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) -x objective-c $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+osxdlg.o: ../macosx/osxdlg.m ../putty.h ../storage.h ../dialog.h \
+               ../macosx/osxclass.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) -x objective-c $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+osxmain.o: ../macosx/osxmain.m ../putty.h ../macosx/osxclass.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) -x objective-c $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+osxsel.o: ../macosx/osxsel.m ../putty.h ../macosx/osxclass.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) -x objective-c $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+osxwin.o: ../macosx/osxwin.m ../putty.h ../terminal.h ../macosx/osxclass.h \
+               ../puttyps.h ../network.h ../misc.h ../tree234.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) -x objective-c $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+otnet.o: ../mac/otnet.c ../putty.h ../network.h ../mac/mac.h ../puttyps.h \
+               ../misc.h ../charset/charset.h ../tree234.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+pinger.o: ../pinger.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+portfwd.o: ../portfwd.c ../putty.h ../ssh.h ../puttyps.h ../network.h \
+               ../misc.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+pproxy.o: ../pproxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+proxy.o: ../proxy.c ../putty.h ../network.h ../proxy.h ../puttyps.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+pscp.o: ../pscp.c ../putty.h ../psftp.h ../ssh.h ../sftp.h ../storage.h \
+               ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+psftp.o: ../psftp.c ../putty.h ../psftp.h ../storage.h ../ssh.h ../sftp.h \
+               ../int64.h ../puttyps.h ../network.h ../misc.h ../puttymem.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+raw.o: ../raw.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+rlogin.o: ../rlogin.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sbcs.o: ../charset/sbcs.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sbcsdat.o: ../charset/sbcsdat.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+settings.o: ../settings.c ../putty.h ../storage.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sftp.o: ../sftp.c ../misc.h ../int64.h ../tree234.h ../sftp.h ../puttymem.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sizetip.o: ../windows/sizetip.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+slookup.o: ../charset/slookup.c ../charset/charset.h ../charset/internal.h \
+               ../charset/enum.c ../charset/sbcsdat.c ../charset/utf8.c
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+ssh.o: ../ssh.c ../putty.h ../tree234.h ../ssh.h ../puttyps.h ../network.h \
+               ../misc.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshaes.o: ../sshaes.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshblowf.o: ../sshblowf.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshbn.o: ../sshbn.c ../misc.h ../ssh.h ../puttymem.h ../network.h ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshcrc.o: ../sshcrc.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshcrcda.o: ../sshcrcda.c ../misc.h ../ssh.h ../puttymem.h ../network.h \
+               ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshdes.o: ../sshdes.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshdh.o: ../sshdh.c ../ssh.h ../puttymem.h ../network.h ../int64.h ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshdss.o: ../sshdss.c ../ssh.h ../misc.h ../puttymem.h ../network.h \
+               ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshdssg.o: ../sshdssg.c ../misc.h ../ssh.h ../puttymem.h ../network.h \
+               ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshmd5.o: ../sshmd5.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshprime.o: ../sshprime.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshpubk.o: ../sshpubk.c ../putty.h ../ssh.h ../misc.h ../puttyps.h \
+               ../network.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshrand.o: ../sshrand.c ../putty.h ../ssh.h ../puttyps.h ../network.h \
+               ../misc.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshrsa.o: ../sshrsa.c ../ssh.h ../misc.h ../puttymem.h ../network.h \
+               ../int64.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshrsag.o: ../sshrsag.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshsh512.o: ../sshsh512.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshsha.o: ../sshsha.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+sshzlib.o: ../sshzlib.c ../ssh.h ../puttymem.h ../network.h ../int64.h \
+               ../misc.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+stricmp.o: ../mac/stricmp.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+telnet.o: ../telnet.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+terminal.o: ../terminal.c ../putty.h ../terminal.h ../puttyps.h ../network.h \
+               ../misc.h ../tree234.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+testback.o: ../testback.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+time.o: ../time.c
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+timing.o: ../timing.c ../putty.h ../tree234.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+toucs.o: ../charset/toucs.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+tree234.o: ../tree234.c ../puttymem.h ../tree234.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+utf8.o: ../charset/utf8.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+ux_x11.o: ../unix/ux_x11.c ../putty.h ../ssh.h ../puttyps.h ../network.h \
+               ../misc.h ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxagentc.o: ../unix/uxagentc.c ../putty.h ../misc.h ../tree234.h \
+               ../puttymem.h ../puttyps.h ../network.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxcfg.o: ../unix/uxcfg.c ../putty.h ../dialog.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxcons.o: ../unix/uxcons.c ../putty.h ../storage.h ../ssh.h ../puttyps.h \
+               ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxgen.o: ../unix/uxgen.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxmisc.o: ../unix/uxmisc.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxnet.o: ../unix/uxnet.c ../putty.h ../network.h ../tree234.h ../puttyps.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxnoise.o: ../unix/uxnoise.c ../putty.h ../ssh.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxplink.o: ../unix/uxplink.c ../putty.h ../storage.h ../tree234.h \
+               ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxprint.o: ../unix/uxprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxproxy.o: ../unix/uxproxy.c ../tree234.h ../putty.h ../network.h ../proxy.h \
+               ../puttyps.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxpterm.o: ../unix/uxpterm.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxpty.o: ../unix/uxpty.c ../putty.h ../tree234.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxputty.o: ../unix/uxputty.c ../putty.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxsel.o: ../unix/uxsel.c ../putty.h ../tree234.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxsftp.o: ../unix/uxsftp.c ../putty.h ../psftp.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxsignal.o: ../unix/uxsignal.c
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxstore.o: ../unix/uxstore.c ../putty.h ../storage.h ../tree234.h \
+               ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+uxucs.o: ../unix/uxucs.c ../putty.h ../charset/charset.h ../terminal.h \
+               ../misc.h ../puttyps.h ../network.h ../tree234.h \
+               ../puttymem.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../windows/winhelp.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+version.o: ../version.c
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+vsnprint.o: ../mac/vsnprint.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+wcwidth.o: ../wcwidth.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+wildcard.o: ../wildcard.c ../putty.h ../puttyps.h ../network.h ../misc.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+wincfg.o: ../windows/wincfg.c ../putty.h ../dialog.h ../storage.h \
+               ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+wincons.o: ../windows/wincons.c ../putty.h ../storage.h ../ssh.h \
+               ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winctrls.o: ../windows/winctrls.c ../putty.h ../misc.h ../dialog.h \
+               ../puttyps.h ../network.h ../puttymem.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+windefs.o: ../windows/windefs.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+windlg.o: ../windows/windlg.c ../putty.h ../ssh.h ../windows/win_res.h \
+               ../storage.h ../dialog.h ../puttyps.h ../network.h ../misc.h \
+               ../puttymem.h ../int64.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../tree234.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+window.o: ../windows/window.c ../putty.h ../terminal.h ../storage.h \
+               ../windows/win_res.h ../puttyps.h ../network.h ../misc.h \
+               ../tree234.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winmisc.o: ../windows/winmisc.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winnet.o: ../windows/winnet.c ../putty.h ../network.h ../tree234.h \
+               ../puttyps.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winnoise.o: ../windows/winnoise.c ../putty.h ../ssh.h ../storage.h \
+               ../puttyps.h ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winpgen.o: ../windows/winpgen.c ../putty.h ../ssh.h ../puttyps.h \
+               ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winpgnt.o: ../windows/winpgnt.c ../putty.h ../ssh.h ../misc.h ../tree234.h \
+               ../puttyps.h ../network.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winpgntc.o: ../windows/winpgntc.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winplink.o: ../windows/winplink.c ../putty.h ../storage.h ../tree234.h \
+               ../puttyps.h ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winprint.o: ../windows/winprint.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winsftp.o: ../windows/winsftp.c ../putty.h ../psftp.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winstore.o: ../windows/winstore.c ../putty.h ../storage.h ../puttyps.h \
+               ../network.h ../misc.h ../windows/winstuff.h \
+               ../mac/macstuff.h ../macosx/osx.h ../unix/unix.h \
+               ../puttymem.h ../tree234.h ../windows/winhelp.h \
+               ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+wintime.o: ../windows/wintime.c ../putty.h ../puttyps.h ../network.h \
+               ../misc.h ../windows/winstuff.h ../mac/macstuff.h \
+               ../macosx/osx.h ../unix/unix.h ../puttymem.h ../tree234.h \
+               ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winucs.o: ../windows/winucs.c ../putty.h ../terminal.h ../misc.h \
+               ../puttyps.h ../network.h ../tree234.h ../puttymem.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+winutils.o: ../windows/winutils.c ../misc.h ../puttymem.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+x11fwd.o: ../x11fwd.c ../putty.h ../ssh.h ../tree234.h ../puttyps.h \
+               ../network.h ../misc.h ../puttymem.h ../int64.h \
+               ../windows/winstuff.h ../mac/macstuff.h ../macosx/osx.h \
+               ../unix/unix.h ../windows/winhelp.h ../charset/charset.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+xenc.o: ../charset/xenc.c ../charset/charset.h ../charset/internal.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+xkeysym.o: ../unix/xkeysym.c ../misc.h ../puttymem.h
+       $(CC) $(COMPAT) $(FWHACK) $(XFLAGS) $(CFLAGS) -c $<
+
+clean:
+       rm -f *.o *.dmg
+       rm -rf *.app
diff --git a/macosx/README.OSX b/macosx/README.OSX
new file mode 100644 (file)
index 0000000..9dded05
--- /dev/null
@@ -0,0 +1,78 @@
+This directory contains a Mac OS X port of PuTTY/pterm, running as a
+native Aqua GUI application.
+
+THIS PORT IS CURRENTLY UNFINISHED AND EXPERIMENTAL. You are welcome
+to use it, but don't be surprised at unexpected behaviour. I'm not
+kidding.
+
+In particular, I have not yet decided where OS X PuTTY should store
+its configuration data. Options include storing it in ~/.putty to be
+compatible with Unix PuTTY, storing it wherever is compatible with
+Mac Classic PuTTY, storing it in a natively OS X location, or
+sorting out the `config-locations' wishlist item and doing all
+three. Therefore, if you start using this port and create a whole
+load of saved sessions, you should not be surprised if a future
+version of the port decides to look somewhere completely different
+for the data and therefore loses them all. If that happens, don't
+say you weren't warned!
+
+Even more importantly, the alert box that confirms host keys is not
+yet implemented, and the application will bomb out and exit if it
+should be needed. This means you cannot make an SSH connection to a
+new host using the GUI PuTTY in this port: you must first run
+`plink' (which should be exactly identical to the version in the
+Unix port) and tell it to confirm the host key.
+
+Other ways in which the port is currently unfinished include:
+
+ - terminal display is horribly slow
+
+ - fatal errors are currently output via printf, which is obviously
+   wrong for a GUI application
+
+ - fonts aren't configurable
+
+ - several features are unimplemented in the terminal display:
+   underlining, non-solid-block cursors, double-width and
+   double-height line attributes, bold as font rather than as
+   colour, wide (CJK) characters, combining characters.
+
+ - there's no scrollbar
+
+ - terminal window resizing isn't implemented yet
+
+ - proper window placement (cascading down and right from the
+   starting position, plus remembering previous window positions per
+   the Apple HIG) is not implemented
+
+ - close-on-exit isn't implemented
+
+ - warn-on-close isn't implemented
+
+ - SessionWindow's dealloc method does nothing yet, so leaks memory
+
+ - use of Alt+numberpad to enter arbitrary numeric character codes
+   is not yet supported
+
+ - cut and paste isn't supported
+
+ - there's no Meta key yet. (I think it will have to be Command
+   rather than Option since the latter is necessary to send some
+   characters, including the rather important # on Apple UK
+   keyboards; but trapping Command-<key> and sending it to the
+   window rather than the application menu requires me to make a
+   positive effort of some sort and I haven't got round to it yet.)
+
+ - there's no specials menu
+
+ - currently no support for server-side window management requests
+   (i.e. escape sequences to minimise or maximise the window,
+   request or change its position and size, change its title etc)
+
+ - window title is currently fixed
+
+ - no Event Log
+
+ - no mid-session Change Settings
+
+ - no icon (surprisingly important in an OS X app!)
diff --git a/macosx/osx.h b/macosx/osx.h
new file mode 100644 (file)
index 0000000..165539f
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef PUTTY_OSX_H
+#define PUTTY_OSX_H
+
+/*
+ * Cocoa defines `FontSpec' itself, so we must change its name.
+ * (Arrgh.)
+ */
+#define FontSpec FontSpec_OSX_Proof
+
+/*
+ * Define the various compatibility symbols to make uxpty.c compile
+ * correctly on OS X.
+ */
+#define BSD_PTYS
+#define OMIT_UTMP
+#define HAVE_NO_SETRESUID
+#define NOT_X_WINDOWS
+
+/*
+ * OS X is largely just Unix, so we can include most of this
+ * unchanged.
+ */
+#include "unix.h"
+
+/*
+ * Functions exported by osxsel.m. (Both of these functions are
+ * expected to be called in the _main_ thread: the select subthread
+ * is an implementation detail of osxsel.m and ideally should not
+ * be visible at all outside it.)
+ */
+void osxsel_init(void);                       /* call this to kick things off */
+void osxsel_process_results(void);     /* call this on receipt of a netevent */
+
+#endif
diff --git a/macosx/osxclass.h b/macosx/osxclass.h
new file mode 100644 (file)
index 0000000..5f009a9
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Header file for the Objective-C parts of Mac OS X PuTTY. This
+ * file contains the class definitions, which would cause compile
+ * failures in the pure C modules if they appeared in osx.h.
+ */
+
+#ifndef PUTTY_OSXCLASS_H
+#define PUTTY_OSXCLASS_H
+
+#include "putty.h"
+
+/*
+ * The application controller class, defined in osxmain.m.
+ */
+@interface AppController : NSObject
+{
+    NSTimer *timer;
+}
+- (void)newSessionConfig:(id)sender;
+- (void)newTerminal:(id)sender;
+- (void)newSessionWithConfig:(id)cfg;
+- (void)setTimer:(long)next;
+@end
+extern AppController *controller;
+
+/*
+ * The SessionWindow class, defined in osxwin.m.
+ */
+
+@class SessionWindow;
+@class TerminalView;
+
+@interface SessionWindow : NSWindow
+{
+    Terminal *term;
+    TerminalView *termview;
+    struct unicode_data ucsdata;
+    void *logctx;
+    Config cfg;
+    void *ldisc;
+    Backend *back;
+    void *backhandle;
+}
+- (id)initWithConfig:(Config)cfg;
+- (void)drawStartFinish:(BOOL)start;
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
+- (Config *)cfg;
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+    attr:(unsigned long)attr lattr:(int)lattr;
+- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr;
+@end
+
+/*
+ * The ConfigWindow class, defined in osxdlg.m.
+ */
+
+@class ConfigWindow;
+
+@interface ConfigWindow : NSWindow
+{
+    NSOutlineView *treeview;
+    struct controlbox *ctrlbox;
+    struct sesslist sl;
+    void *dv;
+    Config cfg;
+}
+- (id)initWithConfig:(Config)cfg;
+@end
+
+/*
+ * Functions exported by osxctrls.m. (They have to go in this
+ * header file and not osx.h, because some of them have Cocoa class
+ * types in their prototypes.)
+ */
+#define HSPACING 12                   /* needed in osxdlg.m and osxctrls.m */
+#define VSPACING 8
+
+void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action);
+void fe_dlg_free(void *dv);
+void create_ctrls(void *dv, NSView *parent, struct controlset *s,
+                 int *minw, int *minh);
+int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
+               int width);            /* returns height used */
+void select_panel(void *dv, struct controlbox *b, const char *name);
+
+#endif /* PUTTY_OSXCLASS_H */
diff --git a/macosx/osxctrls.m b/macosx/osxctrls.m
new file mode 100644 (file)
index 0000000..e3780ff
--- /dev/null
@@ -0,0 +1,1804 @@
+/*
+ * osxctrls.m: OS X implementation of the dialog.h interface.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "dialog.h"
+#include "osxclass.h"
+#include "tree234.h"
+
+/*
+ * Still to be implemented:
+ * 
+ *  - file selectors (NSOpenPanel / NSSavePanel)
+ * 
+ *  - font selectors
+ *  - colour selectors
+ *     * both of these have a conceptual oddity in Cocoa that
+ *      you're only supposed to have one per application. But I
+ *      currently expect to be able to have multiple PuTTY config
+ *      boxes on screen at once; what happens if you trigger the
+ *      font selector in each one at the same time?
+ *     * if it comes to that, the _font_ selector can probably be
+ *      managed by other means: nobody is forcing me to implement
+ *      a font selector using a `Change...' button. The portable
+ *      dialog interface gives me the flexibility to do this how I
+ *      want.
+ *     * The colour selector interface, in its present form, is
+ *      more interesting and _if_ a radical change of plan is
+ *      required then it may stretch across the interface into the
+ *      portable side.
+ *     * Before I do anything rash I should start by looking at the
+ *      Mac Classic port and see how it's done there, on the basis
+ *      that Apple seem reasonably unlikely to have invented this
+ *      crazy restriction specifically for OS X.
+ * 
+ *  - focus management
+ *     * I tried using makeFirstResponder to give keyboard focus,
+ *      but it appeared not to work. Try again, and work out how
+ *      it should be done.
+ *     * also look into tab order. Currently pressing Tab suggests
+ *      that only edit boxes and list boxes can get the keyboard
+ *      focus, and that buttons (in all their forms) are unable to
+ *      be driven by the keyboard. Find out for sure.
+ * 
+ *  - dlg_error_msg
+ *     * this may run into the usual aggro with modal dialog boxes.
+ */
+
+/*
+ * For Cocoa control layout, I need a two-stage process. In stage
+ * one, I allocate all the controls and measure their natural
+ * sizes, which allows me to compute the _minimum_ width and height
+ * of a given section of dialog. Then, in stage two, I lay out the
+ * dialog box as a whole, decide how much each section of the box
+ * needs to receive, and assign it its final size.
+ */
+
+/*
+ * As yet unsolved issues [FIXME]:
+ * 
+ *  - Sometimes the height returned from create_ctrls and the
+ *    height returned from place_ctrls differ. Find out why. It may
+ *    be harmless (e.g. results of NSTextView being odd), but I
+ *    want to know.
+ * 
+ *  - NSTextViews are indented a bit. It'd be nice to put their
+ *    left margin at the same place as everything else's.
+ * 
+ *  - I don't yet know whether we even _can_ support tab order or
+ *    keyboard shortcuts. If we can't, then fair enough, we can't.
+ *    But if we can, we should.
+ * 
+ *  - I would _really_ like to know of a better way to correct
+ *    NSButton's stupid size estimates than by subclassing it and
+ *    overriding sizeToFit with hard-wired sensible values!
+ * 
+ *  - Speaking of stupid size estimates, the amount by which I'm
+ *    adjusting a titled NSBox (currently equal to the point size
+ *    of its title font) looks as if it isn't _quite_ enough.
+ *    Figure out what the real amount should be and use it.
+ * 
+ *  - I don't understand why there's always a scrollbar displayed
+ *    in each list box. I thought I told it to autohide scrollers?
+ * 
+ *  - Why do I have to fudge list box heights by adding one? (Might
+ *    it be to do with the missing header view?)
+ */
+
+/*
+ * Subclass of NSButton which corrects the fact that the normal
+ * one's sizeToFit method persistently returns 32 as its height,
+ * which is simply a lie. I have yet to work out a better
+ * alternative than hard-coding the real heights.
+ */
+@interface MyButton : NSButton
+{
+    int minht;
+}
+@end
+@implementation MyButton
+- (id)initWithFrame:(NSRect)r
+{
+    self = [super initWithFrame:r];
+    minht = 25;
+    return self;
+}
+- (void)setButtonType:(NSButtonType)t
+{
+    if (t == NSRadioButton || t == NSSwitchButton)
+       minht = 18;
+    else
+       minht = 25;
+    [super setButtonType:t];
+}
+- (void)sizeToFit
+{
+    NSRect r;
+    [super sizeToFit];
+    r = [self frame];
+    r.size.height = minht;
+    [self setFrame:r];
+}
+@end
+
+/*
+ * Class used as the data source for NSTableViews.
+ */
+@interface MyTableSource : NSObject
+{
+    tree234 *tree;
+}
+- (id)init;
+- (void)add:(const char *)str withId:(int)id;
+- (int)getid:(int)index;
+- (void)swap:(int)index1 with:(int)index2;
+- (void)removestr:(int)index;
+- (void)clear;
+@end
+@implementation MyTableSource
+- (id)init
+{
+    self = [super init];
+    tree = newtree234(NULL);
+    return self;
+}
+- (void)dealloc
+{
+    char *p;
+    while ((p = delpos234(tree, 0)) != NULL)
+       sfree(p);
+    freetree234(tree);
+    [super dealloc];
+}
+- (void)add:(const char *)str withId:(int)id
+{
+    addpos234(tree, dupprintf("%d\t%s", id, str), count234(tree));
+}
+- (int)getid:(int)index
+{
+    char *p = index234(tree, index);
+    return atoi(p);
+}
+- (void)removestr:(int)index
+{
+    char *p = delpos234(tree, index);
+    sfree(p);
+}
+- (void)swap:(int)index1 with:(int)index2
+{
+    char *p1, *p2;
+
+    if (index1 > index2) {
+       int t = index1; index1 = index2; index2 = t;
+    }
+
+    /* delete later one first so it doesn't affect index of earlier one */
+    p2 = delpos234(tree, index2);
+    p1 = delpos234(tree, index1);
+
+    /* now insert earlier one before later one for the inverse reason */
+    addpos234(tree, p2, index1);
+    addpos234(tree, p1, index2);
+}
+- (void)clear
+{
+    char *p;
+    while ((p = delpos234(tree, 0)) != NULL)
+       sfree(p);
+}
+- (int)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+    return count234(tree);
+}
+- (id)tableView:(NSTableView *)aTableView
+    objectValueForTableColumn:(NSTableColumn *)aTableColumn
+    row:(int)rowIndex
+{
+    int j = [[aTableColumn identifier] intValue];
+    char *p = index234(tree, rowIndex);
+
+    while (j >= 0) {
+       p += strcspn(p, "\t");
+       if (*p) p++;
+       j--;
+    }
+
+    return [NSString stringWithCString:p length:strcspn(p, "\t")];
+}
+@end
+
+/*
+ * Object to receive messages from various control classes.
+ */
+@class Receiver;
+
+struct fe_dlg {
+    NSWindow *window;
+    NSObject *target;
+    SEL action;
+    tree234 *byctrl;
+    tree234 *bywidget;
+    tree234 *boxes;
+    void *data;                               /* passed to portable side */
+    Receiver *rec;
+};
+
+@interface Receiver : NSObject
+{
+    struct fe_dlg *d;
+}
+- (id)initWithStruct:(struct fe_dlg *)aStruct;
+@end
+
+struct fe_ctrl {
+    union control *ctrl;
+    NSButton *button, *button2;
+    NSTextField *label, *editbox;
+    NSComboBox *combobox;
+    NSButton **radiobuttons;
+    NSTextView *textview;
+    NSPopUpButton *popupbutton;
+    NSTableView *tableview;
+    NSScrollView *scrollview;
+    int nradiobuttons;
+    void *privdata;
+    int privdata_needs_free;
+};
+
+static int fe_ctrl_cmp_by_ctrl(void *av, void *bv)
+{
+    struct fe_ctrl *a = (struct fe_ctrl *)av;
+    struct fe_ctrl *b = (struct fe_ctrl *)bv;
+
+    if (a->ctrl < b->ctrl)
+       return -1;
+    if (a->ctrl > b->ctrl)
+       return +1;
+    return 0;
+}
+
+static int fe_ctrl_find_by_ctrl(void *av, void *bv)
+{
+    union control *a = (union control *)av;
+    struct fe_ctrl *b = (struct fe_ctrl *)bv;
+
+    if (a < b->ctrl)
+       return -1;
+    if (a > b->ctrl)
+       return +1;
+    return 0;
+}
+
+struct fe_box {
+    struct controlset *s;
+    id box;
+};
+
+static int fe_boxcmp(void *av, void *bv)
+{
+    struct fe_box *a = (struct fe_box *)av;
+    struct fe_box *b = (struct fe_box *)bv;
+
+    if (a->s < b->s)
+       return -1;
+    if (a->s > b->s)
+       return +1;
+    return 0;
+}
+
+static int fe_boxfind(void *av, void *bv)
+{
+    struct controlset *a = (struct controlset *)av;
+    struct fe_box *b = (struct fe_box *)bv;
+
+    if (a < b->s)
+       return -1;
+    if (a > b->s)
+       return +1;
+    return 0;
+}
+
+struct fe_backwards {                 /* map Cocoa widgets back to fe_ctrls */
+    id widget;
+    struct fe_ctrl *c;
+};
+
+static int fe_backwards_cmp_by_widget(void *av, void *bv)
+{
+    struct fe_backwards *a = (struct fe_backwards *)av;
+    struct fe_backwards *b = (struct fe_backwards *)bv;
+
+    if (a->widget < b->widget)
+       return -1;
+    if (a->widget > b->widget)
+       return +1;
+    return 0;
+}
+
+static int fe_backwards_find_by_widget(void *av, void *bv)
+{
+    id a = (id)av;
+    struct fe_backwards *b = (struct fe_backwards *)bv;
+
+    if (a < b->widget)
+       return -1;
+    if (a > b->widget)
+       return +1;
+    return 0;
+}
+
+static struct fe_ctrl *fe_ctrl_new(union control *ctrl)
+{
+    struct fe_ctrl *c;
+
+    c = snew(struct fe_ctrl);
+    c->ctrl = ctrl;
+
+    c->button = c->button2 = nil;
+    c->label = nil;
+    c->editbox = nil;
+    c->combobox = nil;
+    c->textview = nil;
+    c->popupbutton = nil;
+    c->tableview = nil;
+    c->scrollview = nil;
+    c->radiobuttons = NULL;
+    c->nradiobuttons = 0;
+    c->privdata = NULL;
+    c->privdata_needs_free = FALSE;
+
+    return c;
+}
+
+static void fe_ctrl_free(struct fe_ctrl *c)
+{
+    if (c->privdata_needs_free)
+       sfree(c->privdata);
+    sfree(c->radiobuttons);
+    sfree(c);
+}
+
+static struct fe_ctrl *fe_ctrl_byctrl(struct fe_dlg *d, union control *ctrl)
+{
+    return find234(d->byctrl, ctrl, fe_ctrl_find_by_ctrl);
+}
+
+static void add_box(struct fe_dlg *d, struct controlset *s, id box)
+{
+    struct fe_box *b = snew(struct fe_box);
+    b->box = box;
+    b->s = s;
+    add234(d->boxes, b);
+}
+
+static id find_box(struct fe_dlg *d, struct controlset *s)
+{
+    struct fe_box *b = find234(d->boxes, s, fe_boxfind);
+    return b ? b->box : NULL;
+}
+
+static void add_widget(struct fe_dlg *d, struct fe_ctrl *c, id widget)
+{
+    struct fe_backwards *b = snew(struct fe_backwards);
+    b->widget = widget;
+    b->c = c;
+    add234(d->bywidget, b);
+}
+
+static struct fe_ctrl *find_widget(struct fe_dlg *d, id widget)
+{
+    struct fe_backwards *b = find234(d->bywidget, widget,
+                                    fe_backwards_find_by_widget);
+    return b ? b->c : NULL;
+}
+
+void *fe_dlg_init(void *data, NSWindow *window, NSObject *target, SEL action)
+{
+    struct fe_dlg *d;
+
+    d = snew(struct fe_dlg);
+    d->window = window;
+    d->target = target;
+    d->action = action;
+    d->byctrl = newtree234(fe_ctrl_cmp_by_ctrl);
+    d->bywidget = newtree234(fe_backwards_cmp_by_widget);
+    d->boxes = newtree234(fe_boxcmp);
+    d->data = data;
+    d->rec = [[Receiver alloc] initWithStruct:d];
+
+    return d;
+}
+
+void fe_dlg_free(void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c;
+    struct fe_box *b;
+
+    while ( (c = delpos234(d->byctrl, 0)) != NULL )
+       fe_ctrl_free(c);
+    freetree234(d->byctrl);
+
+    while ( (c = delpos234(d->bywidget, 0)) != NULL )
+       sfree(c);
+    freetree234(d->bywidget);
+
+    while ( (b = delpos234(d->boxes, 0)) != NULL )
+       sfree(b);
+    freetree234(d->boxes);
+
+    [d->rec release];
+
+    sfree(d);
+}
+
+@implementation Receiver
+- (id)initWithStruct:(struct fe_dlg *)aStruct
+{
+    self = [super init];
+    d = aStruct;
+    return self;
+}
+- (void)buttonPushed:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+
+    assert(c && c->ctrl->generic.type == CTRL_BUTTON);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
+}
+- (void)checkboxChanged:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+
+    assert(c && c->ctrl->generic.type == CTRL_CHECKBOX);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)radioChanged:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+    int j;
+
+    assert(c && c->radiobuttons);
+    for (j = 0; j < c->nradiobuttons; j++)
+       if (sender != c->radiobuttons[j])
+           [c->radiobuttons[j] setState:NSOffState];
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)popupMenuSelected:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)controlTextDidChange:(NSNotification *)notification
+{
+    id widget = [notification object];
+    struct fe_ctrl *c = find_widget(d, widget);
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+- (void)controlTextDidEndEditing:(NSNotification *)notification
+{
+    id widget = [notification object];
+    struct fe_ctrl *c = find_widget(d, widget);
+    assert(c && c->ctrl->generic.type == CTRL_EDITBOX);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_REFRESH);
+}
+- (void)tableViewSelectionDidChange:(NSNotification *)notification
+{
+    id widget = [notification object];
+    struct fe_ctrl *c = find_widget(d, widget);
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_SELCHANGE);
+}
+- (BOOL)tableView:(NSTableView *)aTableView
+    shouldEditTableColumn:(NSTableColumn *)aTableColumn
+    row:(int)rowIndex
+{
+    return NO;                        /* no editing permitted */
+}
+- (void)listDoubleClicked:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX);
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_ACTION);
+}
+- (void)dragListButton:(id)sender
+{
+    struct fe_ctrl *c = find_widget(d, sender);
+    int direction, row, nrows;
+    assert(c && c->ctrl->generic.type == CTRL_LISTBOX &&
+          c->ctrl->listbox.draglist);
+
+    if (sender == c->button)
+       direction = -1;                /* up */
+    else
+       direction = +1;                /* down */
+
+    row = [c->tableview selectedRow];
+    nrows = [c->tableview numberOfRows];
+
+    if (row + direction < 0 || row + direction >= nrows) {
+       NSBeep();
+       return;
+    }
+
+    [[c->tableview dataSource] swap:row with:row+direction];
+    [c->tableview reloadData];
+    [c->tableview selectRow:row+direction byExtendingSelection:NO];
+
+    c->ctrl->generic.handler(c->ctrl, d, d->data, EVENT_VALCHANGE);
+}
+@end
+
+void create_ctrls(void *dv, NSView *parent, struct controlset *s,
+                 int *minw, int *minh)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    int ccw[100];                     /* cumulative column widths */
+    int cypos[100];
+    int ncols;
+    int wmin = 0, hmin = 0;
+    int i, j, cw, ch;
+    NSRect rect;
+    NSFont *textviewfont = nil;
+    int boxh = 0, boxw = 0;
+
+    if (!s->boxname && s->boxtitle) {
+        /* This controlset is a panel title. */
+
+       NSTextField *tf;
+
+       tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+       [tf setEditable:NO];
+       [tf setSelectable:NO];
+       [tf setBordered:NO];
+       [tf setDrawsBackground:NO];
+       [tf setStringValue:[NSString stringWithCString:s->boxtitle]];
+       [tf sizeToFit];
+       rect = [tf frame];
+       [parent addSubview:tf];
+
+       /*
+        * I'm going to store this NSTextField in the boxes tree,
+        * because I really can't face having a special tree234
+        * mapping controlsets to panel titles.
+        */
+       add_box(d, s, tf);
+
+       *minw = rect.size.width;
+       *minh = rect.size.height;
+
+       return;
+    }
+
+    if (*s->boxname) {
+       /*
+        * Create an NSBox to contain this subset of controls.
+        */
+       NSBox *box;
+       NSRect tmprect;
+
+       box = [[NSBox alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+       if (s->boxtitle)
+           [box setTitle:[NSString stringWithCString:s->boxtitle]];
+       else
+           [box setTitlePosition:NSNoTitle];
+       add_box(d, s, box);
+       tmprect = [box frame];
+       [box setContentViewMargins:NSMakeSize(20,20)];
+       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
+       rect = [box frame];
+       [box setFrame:tmprect];
+       boxh = (int)(rect.size.height - 100);
+       boxw = (int)(rect.size.width - 100);
+       [parent addSubview:box];
+
+       if (s->boxtitle)
+           boxh += [[box titleFont] pointSize];
+
+       /*
+        * All subsequent controls will be placed within this box.
+        */
+       parent = box;
+    }
+
+    ncols = 1;
+    ccw[0] = 0;
+    ccw[1] = 100;
+    cypos[0] = 0;
+
+    /*
+     * Now iterate through the controls themselves, create them,
+     * and add their width and height to the overall width/height
+     * calculation.
+     */
+    for (i = 0; i < s->ncontrols; i++) {
+       union control *ctrl = s->ctrls[i];
+       struct fe_ctrl *c;
+       int colstart = COLUMN_START(ctrl->generic.column);
+       int colspan = COLUMN_SPAN(ctrl->generic.column);
+       int colend = colstart + colspan;
+       int ytop, wthis;
+
+        switch (ctrl->generic.type) {
+          case CTRL_COLUMNS:
+           for (j = 1; j < ncols; j++)
+               if (cypos[0] < cypos[j])
+                   cypos[0] = cypos[j];
+
+           assert(ctrl->columns.ncols < lenof(ccw));
+
+           ccw[0] = 0;
+           for (j = 0; j < ctrl->columns.ncols; j++) {
+               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
+                                    ctrl->columns.percentages[j] : 100);
+               cypos[j] = cypos[0];
+           }
+
+           ncols = ctrl->columns.ncols;
+
+            continue;                  /* no actual control created */
+          case CTRL_TABDELAY:
+           /*
+            * I'm currently uncertain that we can implement tab
+            * order in OS X.
+            */
+            continue;                  /* no actual control created */
+       }
+
+       c = fe_ctrl_new(ctrl);
+       add234(d->byctrl, c);
+
+       cw = ch = 0;
+
+        switch (ctrl->generic.type) {
+          case CTRL_BUTTON:
+          case CTRL_CHECKBOX:
+           {
+               NSButton *b;
+
+               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
+               [b setBezelStyle:NSRoundedBezelStyle];
+               if (ctrl->generic.type == CTRL_CHECKBOX)
+                   [b setButtonType:NSSwitchButton];
+               [b setTitle:[NSString stringWithCString:ctrl->generic.label]];
+               if (ctrl->button.isdefault)
+                   [b setKeyEquivalent:@"\r"];
+               else if (ctrl->button.iscancel)
+                   [b setKeyEquivalent:@"\033"];
+               [b sizeToFit];
+               rect = [b frame];
+
+               [parent addSubview:b];
+
+               [b setTarget:d->rec];
+               if (ctrl->generic.type == CTRL_CHECKBOX)
+                   [b setAction:@selector(checkboxChanged:)];
+               else
+                   [b setAction:@selector(buttonPushed:)];
+               add_widget(d, c, b);
+
+               c->button = b;
+
+               cw = rect.size.width;
+               ch = rect.size.height;
+           }
+           break;
+         case CTRL_EDITBOX:
+           {
+               int editp = ctrl->editbox.percentwidth;
+               int labelp = editp == 100 ? 100 : 100 - editp;
+               NSTextField *tf;
+               NSComboBox *cb;
+
+               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+               [tf setEditable:NO];
+               [tf setSelectable:NO];
+               [tf setBordered:NO];
+               [tf setDrawsBackground:NO];
+               [tf setStringValue:[NSString
+                                   stringWithCString:ctrl->generic.label]];
+               [tf sizeToFit];
+               rect = [tf frame];
+               [parent addSubview:tf];
+               c->label = tf;
+
+               cw = rect.size.width * 100 / labelp;
+               ch = rect.size.height;
+
+               if (ctrl->editbox.has_list) {
+                   cb = [[NSComboBox alloc]
+                         initWithFrame:NSMakeRect(0,0,1,1)];
+                   [cb setStringValue:@"x"];
+                   [cb sizeToFit];
+                   rect = [cb frame];
+                   [parent addSubview:cb];
+                   c->combobox = cb;
+               } else {
+                   if (ctrl->editbox.password)
+                       tf = [NSSecureTextField alloc];
+                   else
+                       tf = [NSTextField alloc];
+
+                   tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
+                   [tf setEditable:YES];
+                   [tf setSelectable:YES];
+                   [tf setBordered:YES];
+                   [tf setStringValue:@"x"];
+                   [tf sizeToFit];
+                   rect = [tf frame];
+                   [parent addSubview:tf];
+                   c->editbox = tf;
+
+                   [tf setDelegate:d->rec];
+                   add_widget(d, c, tf);
+               }
+
+               if (editp == 100) {
+                   /* the edit box and its label are vertically separated */
+                   ch += VSPACING + rect.size.height;
+               } else {
+                   /* the edit box and its label are horizontally separated */
+                   if (ch < rect.size.height)
+                       ch = rect.size.height;
+               }
+
+               if (cw < rect.size.width * 100 / editp)
+                   cw = rect.size.width * 100 / editp;
+           }
+           break;
+         case CTRL_TEXT:
+           {
+               NSTextView *tv;
+               int testwid;
+
+               if (!textviewfont) {
+                   NSTextField *tf;
+                   tf = [[NSTextField alloc] init];
+                   textviewfont = [tf font];
+                   [tf release];
+               }
+
+               testwid = (ccw[colend] - ccw[colstart]) * 3;
+
+               tv = [[NSTextView alloc]
+                     initWithFrame:NSMakeRect(0,0,testwid,1)];
+               [tv setEditable:NO];
+               [tv setSelectable:NO];
+               //[tv setBordered:NO];
+               [tv setDrawsBackground:NO];
+               [tv setFont:textviewfont];
+               [tv setString:
+                [NSString stringWithCString:ctrl->generic.label]];
+               rect = [tv frame];
+               [tv sizeToFit];
+               [parent addSubview:tv];
+               c->textview = tv;
+
+               cw = rect.size.width;
+               ch = rect.size.height;
+           }
+           break;
+         case CTRL_RADIO:
+           {
+               NSTextField *tf;
+               int j;
+
+               if (ctrl->generic.label) {
+                   tf = [[NSTextField alloc]
+                         initWithFrame:NSMakeRect(0,0,1,1)];
+                   [tf setEditable:NO];
+                   [tf setSelectable:NO];
+                   [tf setBordered:NO];
+                   [tf setDrawsBackground:NO];
+                   [tf setStringValue:
+                    [NSString stringWithCString:ctrl->generic.label]];
+                   [tf sizeToFit];
+                   rect = [tf frame];
+                   [parent addSubview:tf];
+                   c->label = tf;
+
+                   cw = rect.size.width;
+                   ch = rect.size.height;
+               } else {
+                   cw = 0;
+                   ch = -VSPACING;    /* compensate for next advance */
+               }
+
+               c->nradiobuttons = ctrl->radio.nbuttons;
+               c->radiobuttons = snewn(ctrl->radio.nbuttons, NSButton *);
+
+               for (j = 0; j < ctrl->radio.nbuttons; j++) {
+                   NSButton *b;
+                   int ncols;
+
+                   b = [[MyButton alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+                   [b setBezelStyle:NSRoundedBezelStyle];
+                   [b setButtonType:NSRadioButton];
+                   [b setTitle:[NSString
+                                stringWithCString:ctrl->radio.buttons[j]]];
+                   [b sizeToFit];
+                   rect = [b frame];
+                   [parent addSubview:b];
+
+                   c->radiobuttons[j] = b;
+
+                   [b setTarget:d->rec];
+                   [b setAction:@selector(radioChanged:)];
+                   add_widget(d, c, b);
+
+                   /*
+                    * Add to the height every time we place a
+                    * button in column 0.
+                    */
+                   if (j % ctrl->radio.ncolumns == 0) {
+                       ch += rect.size.height + VSPACING;
+                   }
+
+                   /*
+                    * Add to the width by working out how many
+                    * columns this button spans.
+                    */
+                   if (j == ctrl->radio.nbuttons - 1)
+                       ncols = (ctrl->radio.ncolumns -
+                                (j % ctrl->radio.ncolumns));
+                   else
+                       ncols = 1;
+
+                   if (cw < rect.size.width * ctrl->radio.ncolumns / ncols)
+                       cw = rect.size.width * ctrl->radio.ncolumns / ncols;
+               }
+           }
+           break;
+         case CTRL_FILESELECT:
+         case CTRL_FONTSELECT:
+           {
+               NSTextField *tf;
+               NSButton *b;
+               int kh;
+
+               tf = [[NSTextField alloc] initWithFrame:NSMakeRect(0,0,1,1)];
+               [tf setEditable:NO];
+               [tf setSelectable:NO];
+               [tf setBordered:NO];
+               [tf setDrawsBackground:NO];
+               [tf setStringValue:[NSString
+                                   stringWithCString:ctrl->generic.label]];
+               [tf sizeToFit];
+               rect = [tf frame];
+               [parent addSubview:tf];
+               c->label = tf;
+
+               cw = rect.size.width;
+               ch = rect.size.height;
+
+               tf = [NSTextField alloc];
+               tf = [tf initWithFrame:NSMakeRect(0,0,1,1)];
+               if (ctrl->generic.type == CTRL_FILESELECT) {
+                   [tf setEditable:YES];
+                   [tf setSelectable:YES];
+                   [tf setBordered:YES];
+               } else {
+                   [tf setEditable:NO];
+                   [tf setSelectable:NO];
+                   [tf setBordered:NO];
+                   [tf setDrawsBackground:NO];
+               }
+               [tf setStringValue:@"x"];
+               [tf sizeToFit];
+               rect = [tf frame];
+               [parent addSubview:tf];
+               c->editbox = tf;
+
+               kh = rect.size.height;
+               if (cw < rect.size.width * 4 / 3)
+                   cw = rect.size.width * 4 / 3;
+
+               b = [[MyButton alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
+               [b setBezelStyle:NSRoundedBezelStyle];
+               if (ctrl->generic.type == CTRL_FILESELECT)
+                   [b setTitle:@"Browse..."];
+               else
+                   [b setTitle:@"Change..."];
+               // [b setKeyEquivalent:somethingorother];
+               // [b setTarget:somethingorother];
+               // [b setAction:somethingorother];
+               [b sizeToFit];
+               rect = [b frame];
+               [parent addSubview:b];
+
+               c->button = b;
+
+               if (kh < rect.size.height)
+                   kh = rect.size.height;
+               ch += VSPACING + kh;
+               if (cw < rect.size.width * 4)
+                   cw = rect.size.width * 4;
+           }
+           break;
+         case CTRL_LISTBOX:
+           {
+               int listp = ctrl->listbox.percentwidth;
+               int labelp = listp == 100 ? 100 : 100 - listp;
+               NSTextField *tf;
+               NSPopUpButton *pb;
+               NSTableView *tv;
+               NSScrollView *sv;
+
+               if (ctrl->generic.label) {
+                   tf = [[NSTextField alloc]
+                         initWithFrame:NSMakeRect(0,0,1,1)];
+                   [tf setEditable:NO];
+                   [tf setSelectable:NO];
+                   [tf setBordered:NO];
+                   [tf setDrawsBackground:NO];
+                   [tf setStringValue:
+                    [NSString stringWithCString:ctrl->generic.label]];
+                   [tf sizeToFit];
+                   rect = [tf frame];
+                   [parent addSubview:tf];
+                   c->label = tf;
+
+                   cw = rect.size.width;
+                   ch = rect.size.height;
+               } else {
+                   cw = 0;
+                   ch = -VSPACING;    /* compensate for next advance */
+               }
+
+               if (ctrl->listbox.height == 0) {
+                   pb = [[NSPopUpButton alloc]
+                         initWithFrame:NSMakeRect(0,0,1,1)];
+                   [pb sizeToFit];
+                   rect = [pb frame];
+                   [parent addSubview:pb];
+                   c->popupbutton = pb;
+
+                   [pb setTarget:d->rec];
+                   [pb setAction:@selector(popupMenuSelected:)];
+                   add_widget(d, c, pb);
+               } else {
+                   assert(listp == 100);
+                   if (ctrl->listbox.draglist) {
+                       int bi;
+
+                       listp = 75;
+
+                       for (bi = 0; bi < 2; bi++) {
+                           NSButton *b;
+                           b = [[MyButton alloc]
+                                initWithFrame:NSMakeRect(0, 0, 1, 1)];
+                           [b setBezelStyle:NSRoundedBezelStyle];
+                           if (bi == 0)
+                               [b setTitle:@"Up"];
+                           else
+                               [b setTitle:@"Down"];
+                           [b sizeToFit];
+                           rect = [b frame];
+                           [parent addSubview:b];
+
+                           if (bi == 0)
+                               c->button = b;
+                           else
+                               c->button2 = b;
+
+                           [b setTarget:d->rec];
+                           [b setAction:@selector(dragListButton:)];
+                           add_widget(d, c, b);
+
+                           if (cw < rect.size.width * 4)
+                               cw = rect.size.width * 4;
+                       }
+                   }
+
+                   sv = [[NSScrollView alloc] initWithFrame:
+                         NSMakeRect(20,20,10,10)];
+                   [sv setBorderType:NSLineBorder];
+                   tv = [[NSTableView alloc] initWithFrame:[sv frame]];
+                   [[tv headerView] setFrame:NSMakeRect(0,0,0,0)];
+                   [sv setDocumentView:tv];
+                   [parent addSubview:sv];
+                   [sv setHasVerticalScroller:YES];
+                   [sv setAutohidesScrollers:YES];
+                   [tv setAllowsColumnReordering:NO];
+                   [tv setAllowsColumnResizing:NO];
+                   [tv setAllowsMultipleSelection:ctrl->listbox.multisel];
+                   [tv setAllowsEmptySelection:YES];
+                   [tv setAllowsColumnSelection:YES];
+                   [tv setDataSource:[[MyTableSource alloc] init]];
+                   rect = [tv frame];
+                   /*
+                    * For some reason this consistently comes out
+                    * one short. Add one.
+                    */
+                   rect.size.height = (ctrl->listbox.height+1)*[tv rowHeight];
+                   [sv setFrame:rect];
+                   c->tableview = tv;
+                   c->scrollview = sv;
+
+                   [tv setDelegate:d->rec];
+                   [tv setTarget:d->rec];
+                   [tv setDoubleAction:@selector(listDoubleClicked:)];
+                   add_widget(d, c, tv);
+               }
+
+               if (c->tableview) {
+                   int ncols, *percentages;
+                   int hundred = 100;
+
+                   if (ctrl->listbox.ncols) {
+                       ncols = ctrl->listbox.ncols;
+                       percentages = ctrl->listbox.percentages;
+                   } else {
+                       ncols = 1;
+                       percentages = &hundred;
+                   }
+
+                   for (j = 0; j < ncols; j++) {
+                       NSTableColumn *col;
+
+                       col = [[NSTableColumn alloc] initWithIdentifier:
+                              [NSNumber numberWithInt:j]];
+                       [c->tableview addTableColumn:col];
+                   }
+               }
+
+               if (labelp == 100) {
+                   /* the list and its label are vertically separated */
+                   ch += VSPACING + rect.size.height;
+               } else {
+                   /* the list and its label are horizontally separated */
+                   if (ch < rect.size.height)
+                       ch = rect.size.height;
+               }
+
+               if (cw < rect.size.width * 100 / listp)
+                   cw = rect.size.width * 100 / listp;
+           }
+           break;
+       }
+
+       /*
+        * Update the width and height data for the control we've
+        * just created.
+        */
+       ytop = 0;
+
+       for (j = colstart; j < colend; j++) {
+           if (ytop < cypos[j])
+               ytop = cypos[j];
+       }
+
+       for (j = colstart; j < colend; j++)
+           cypos[j] = ytop + ch + VSPACING;
+
+       if (hmin < ytop + ch)
+           hmin = ytop + ch;
+
+       wthis = (cw + HSPACING) * 100 / (ccw[colend] - ccw[colstart]);
+       wthis -= HSPACING;
+
+       if (wmin < wthis)
+           wmin = wthis;
+    }
+
+    if (*s->boxname) {
+       /*
+        * Add a bit to the width and height for the box.
+        */
+       wmin += boxw;
+       hmin += boxh;
+    }
+
+    //printf("For controlset %s/%s, returning w=%d h=%d\n",
+    //       s->pathname, s->boxname, wmin, hmin);
+    *minw = wmin;
+    *minh = hmin;
+}
+
+int place_ctrls(void *dv, struct controlset *s, int leftx, int topy,
+               int width)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    int ccw[100];                     /* cumulative column widths */
+    int cypos[100];
+    int ncols;
+    int i, j, ret;
+    int boxh = 0, boxw = 0;
+
+    if (!s->boxname && s->boxtitle) {
+        /* Size and place the panel title. */
+
+       NSTextField *tf = find_box(d, s);
+       NSRect rect;
+
+       rect = [tf frame];
+       [tf setFrame:NSMakeRect(leftx, topy-rect.size.height,
+                               width, rect.size.height)];
+       return rect.size.height;
+    }
+
+    if (*s->boxname) {
+       NSRect rect, tmprect;
+       NSBox *box = find_box(d, s);
+
+       assert(box != NULL);
+       tmprect = [box frame];
+       [box setFrameFromContentFrame:NSMakeRect(100,100,100,100)];
+       rect = [box frame];
+       [box setFrame:tmprect];
+       boxw = rect.size.width - 100;
+       boxh = rect.size.height - 100;
+       if (s->boxtitle)
+           boxh += [[box titleFont] pointSize];
+       topy -= boxh;
+       width -= boxw;
+    }
+
+    ncols = 1;
+    ccw[0] = 0;
+    ccw[1] = 100;
+    cypos[0] = topy;
+    ret = 0;
+
+    /*
+     * Now iterate through the controls themselves, placing them
+     * appropriately.
+     */
+    for (i = 0; i < s->ncontrols; i++) {
+       union control *ctrl = s->ctrls[i];
+       struct fe_ctrl *c;
+       int colstart = COLUMN_START(ctrl->generic.column);
+       int colspan = COLUMN_SPAN(ctrl->generic.column);
+       int colend = colstart + colspan;
+       int xthis, ythis, wthis, ch;
+       NSRect rect;
+
+        switch (ctrl->generic.type) {
+          case CTRL_COLUMNS:
+           for (j = 1; j < ncols; j++)
+               if (cypos[0] > cypos[j])
+                   cypos[0] = cypos[j];
+
+           assert(ctrl->columns.ncols < lenof(ccw));
+
+           ccw[0] = 0;
+           for (j = 0; j < ctrl->columns.ncols; j++) {
+               ccw[j+1] = ccw[j] + (ctrl->columns.percentages ?
+                                    ctrl->columns.percentages[j] : 100);
+               cypos[j] = cypos[0];
+           }
+
+           ncols = ctrl->columns.ncols;
+
+            continue;                  /* no actual control created */
+          case CTRL_TABDELAY:
+            continue;                  /* nothing to do here, move along */
+       }
+
+       c = fe_ctrl_byctrl(d, ctrl);
+
+       ch = 0;
+       ythis = topy;
+
+       for (j = colstart; j < colend; j++) {
+           if (ythis > cypos[j])
+               ythis = cypos[j];
+       }
+
+       xthis = (width + HSPACING) * ccw[colstart] / 100;
+       wthis = (width + HSPACING) * ccw[colend] / 100 - HSPACING - xthis;
+       xthis += leftx;
+
+        switch (ctrl->generic.type) {
+          case CTRL_BUTTON:
+         case CTRL_CHECKBOX:
+           rect = [c->button frame];
+           [c->button setFrame:NSMakeRect(xthis,ythis-rect.size.height,wthis,
+                                          rect.size.height)];
+           ch = rect.size.height;
+           break;
+         case CTRL_EDITBOX:
+           {
+               int editp = ctrl->editbox.percentwidth;
+               int labelp = editp == 100 ? 100 : 100 - editp;
+               int lheight, theight, rheight, ynext, editw;
+               NSControl *edit = (c->editbox ? c->editbox : c->combobox);
+
+               rect = [c->label frame];
+               lheight = rect.size.height;
+               rect = [edit frame];
+               theight = rect.size.height;
+
+               if (editp == 100)
+                   rheight = lheight;
+               else
+                   rheight = (lheight < theight ? theight : lheight);
+
+               [c->label setFrame:
+                NSMakeRect(xthis, ythis-(rheight+lheight)/2,
+                           (wthis + HSPACING) * labelp / 100 - HSPACING,
+                           lheight)];
+               if (editp == 100) {
+                   ynext = ythis - rheight - VSPACING;
+                   rheight = theight;
+               } else {
+                   ynext = ythis;
+               }
+
+               editw = (wthis + HSPACING) * editp / 100 - HSPACING;
+
+               [edit setFrame:
+                NSMakeRect(xthis+wthis-editw, ynext-(rheight+theight)/2,
+                           editw, theight)];
+
+               ch = (ythis - ynext) + theight;
+           }
+           break;
+          case CTRL_TEXT:
+           [c->textview setFrame:NSMakeRect(xthis, 0, wthis, 1)];
+           [c->textview sizeToFit];
+           rect = [c->textview frame];
+           [c->textview setFrame:NSMakeRect(xthis, ythis-rect.size.height,
+                                            wthis, rect.size.height)];
+           ch = rect.size.height;
+           break;
+         case CTRL_RADIO:
+           {
+               int j, ynext;
+
+               if (c->label) {
+                   rect = [c->label frame];
+                   [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
+                                                 wthis,rect.size.height)];
+                   ynext = ythis - rect.size.height - VSPACING;
+               } else
+                   ynext = ythis;
+
+               for (j = 0; j < ctrl->radio.nbuttons; j++) {
+                   int col = j % ctrl->radio.ncolumns;
+                   int ncols;
+                   int lx,rx;
+
+                   if (j == ctrl->radio.nbuttons - 1)
+                       ncols = ctrl->radio.ncolumns - col;
+                   else
+                       ncols = 1;
+
+                   lx = (wthis + HSPACING) * col / ctrl->radio.ncolumns;
+                   rx = ((wthis + HSPACING) *
+                         (col+ncols) / ctrl->radio.ncolumns) - HSPACING;
+
+                   /*
+                    * Set the frame size.
+                    */
+                   rect = [c->radiobuttons[j] frame];
+                   [c->radiobuttons[j] setFrame:
+                    NSMakeRect(lx+xthis, ynext-rect.size.height,
+                               rx-lx, rect.size.height)];
+
+                   /*
+                    * Advance to next line if we're in the last
+                    * column.
+                    */
+                   if (col + ncols == ctrl->radio.ncolumns)
+                       ynext -= rect.size.height + VSPACING;
+               }
+               ch = (ythis - ynext) - VSPACING;
+           }
+           break;
+         case CTRL_FILESELECT:
+         case CTRL_FONTSELECT:
+           {
+               int ynext, eh, bh, th, mx;
+
+               rect = [c->label frame];
+               [c->label setFrame:NSMakeRect(xthis,ythis-rect.size.height,
+                                             wthis,rect.size.height)];
+               ynext = ythis - rect.size.height - VSPACING;
+
+               rect = [c->editbox frame];
+               eh = rect.size.height;
+               rect = [c->button frame];
+               bh = rect.size.height;
+               th = (eh > bh ? eh : bh);
+
+               mx = (wthis + HSPACING) * 3 / 4 - HSPACING;
+
+               [c->editbox setFrame:
+                NSMakeRect(xthis, ynext-(th+eh)/2, mx, eh)];
+               [c->button setFrame:
+                NSMakeRect(xthis+mx+HSPACING, ynext-(th+bh)/2,
+                           wthis-mx-HSPACING, bh)];
+
+               ch = (ythis - ynext) + th + VSPACING;
+           }
+           break;
+         case CTRL_LISTBOX:
+           {
+               int listp = ctrl->listbox.percentwidth;
+               int labelp = listp == 100 ? 100 : 100 - listp;
+               int lheight, theight, rheight, ynext, listw, xlist;
+               NSControl *list = (c->scrollview ? (id)c->scrollview :
+                                  (id)c->popupbutton);
+
+               if (ctrl->listbox.draglist) {
+                   assert(listp == 100);
+                   listp = 75;
+               }
+
+               rect = [list frame];
+               theight = rect.size.height;
+
+               if (c->label) {
+                   rect = [c->label frame];
+                   lheight = rect.size.height;
+
+                   if (labelp == 100)
+                       rheight = lheight;
+                   else
+                       rheight = (lheight < theight ? theight : lheight);
+
+                   [c->label setFrame:
+                    NSMakeRect(xthis, ythis-(rheight+lheight)/2,
+                               (wthis + HSPACING) * labelp / 100 - HSPACING,
+                               lheight)];
+                   if (labelp == 100) {
+                       ynext = ythis - rheight - VSPACING;
+                       rheight = theight;
+                   } else {
+                       ynext = ythis;
+                   }
+               } else {
+                   ynext = ythis;
+                   rheight = theight;
+               }
+
+               listw = (wthis + HSPACING) * listp / 100 - HSPACING;
+
+               if (labelp == 100)
+                   xlist = xthis;
+               else
+                   xlist = xthis+wthis-listw;
+
+               [list setFrame: NSMakeRect(xlist, ynext-(rheight+theight)/2,
+                                          listw, theight)];
+
+               /*
+                * Size the columns for the table view.
+                */
+               if (c->tableview) {
+                   int ncols, *percentages;
+                   int hundred = 100;
+                   int cpercent = 0, cpixels = 0;
+                   NSArray *cols;
+
+                   if (ctrl->listbox.ncols) {
+                       ncols = ctrl->listbox.ncols;
+                       percentages = ctrl->listbox.percentages;
+                   } else {
+                       ncols = 1;
+                       percentages = &hundred;
+                   }
+
+                   cols = [c->tableview tableColumns];
+
+                   for (j = 0; j < ncols; j++) {
+                       NSTableColumn *col = [cols objectAtIndex:j];
+                       int newcpixels;
+
+                       cpercent += percentages[j];
+                       newcpixels = listw * cpercent / 100;
+                       [col setWidth:newcpixels-cpixels];
+                       cpixels = newcpixels;
+                   }
+               }
+
+               ch = (ythis - ynext) + theight;
+
+               if (c->button) {
+                   int b2height, centre;
+                   int bx, bw;
+
+                   /*
+                    * Place the Up and Down buttons for a drag list.
+                    */
+                   assert(c->button2);
+
+                   rect = [c->button frame];
+                   b2height = VSPACING + 2 * rect.size.height;
+
+                   centre = ynext - rheight/2;
+
+                   bx = (wthis + HSPACING) * 3 / 4;
+                   bw = wthis - bx;
+                   bx += leftx;
+
+                   [c->button setFrame:
+                    NSMakeRect(bx, centre+b2height/2-rect.size.height,
+                               bw, rect.size.height)];
+                   [c->button2 setFrame:
+                    NSMakeRect(bx, centre-b2height/2,
+                               bw, rect.size.height)];
+               }
+           }
+           break;
+       }
+
+       for (j = colstart; j < colend; j++)
+           cypos[j] = ythis - ch - VSPACING;
+       if (ret < topy - (ythis - ch))
+           ret = topy - (ythis - ch);
+    }
+
+    if (*s->boxname) {
+       NSBox *box = find_box(d, s);
+       assert(box != NULL);
+       [box sizeToFit];
+
+       if (s->boxtitle) {
+           NSRect rect = [box frame];
+           rect.size.height += [[box titleFont] pointSize];
+           [box setFrame:rect];
+       }
+
+       ret += boxh;
+    }
+
+    //printf("For controlset %s/%s, returning ret=%d\n",
+    //       s->pathname, s->boxname, ret);
+    return ret;
+}
+
+void select_panel(void *dv, struct controlbox *b, const char *name)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    int i, j, hidden;
+    struct controlset *s;
+    union control *ctrl;
+    struct fe_ctrl *c;
+    NSBox *box;
+
+    for (i = 0; i < b->nctrlsets; i++) {
+       s = b->ctrlsets[i];
+
+       if (*s->pathname) {
+           hidden = !strcmp(s->pathname, name) ? NO : YES;
+
+           if ((box = find_box(d, s)) != NULL) {
+               [box setHidden:hidden];
+           } else {
+               for (j = 0; j < s->ncontrols; j++) {
+                   ctrl = s->ctrls[j];
+                   c = fe_ctrl_byctrl(d, ctrl);
+
+                   if (!c)
+                       continue;
+
+                   if (c->label)
+                       [c->label setHidden:hidden];
+                   if (c->button)
+                       [c->button setHidden:hidden];
+                   if (c->button2)
+                       [c->button2 setHidden:hidden];
+                   if (c->editbox)
+                       [c->editbox setHidden:hidden];
+                   if (c->combobox)
+                       [c->combobox setHidden:hidden];
+                   if (c->textview)
+                       [c->textview setHidden:hidden];
+                   if (c->tableview)
+                       [c->tableview setHidden:hidden];
+                   if (c->scrollview)
+                       [c->scrollview setHidden:hidden];
+                   if (c->popupbutton)
+                       [c->popupbutton setHidden:hidden];
+                   if (c->radiobuttons) {
+                       int j;
+                       for (j = 0; j < c->nradiobuttons; j++)
+                           [c->radiobuttons[j] setHidden:hidden];
+                   }
+                   break;
+               }
+           }
+       }
+    }
+}
+
+void dlg_radiobutton_set(union control *ctrl, void *dv, int whichbutton)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    int j;
+
+    assert(c->radiobuttons);
+    for (j = 0; j < c->nradiobuttons; j++)
+       [c->radiobuttons[j] setState:
+        (j == whichbutton ? NSOnState : NSOffState)];
+}
+
+int dlg_radiobutton_get(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    int j;
+
+    assert(c->radiobuttons);
+    for (j = 0; j < c->nradiobuttons; j++)
+       if ([c->radiobuttons[j] state] == NSOnState)
+           return j;
+
+    return 0;                         /* should never reach here */
+}
+
+void dlg_checkbox_set(union control *ctrl, void *dv, int checked)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    assert(c->button);
+    [c->button setState:(checked ? NSOnState : NSOffState)];
+}
+
+int dlg_checkbox_get(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    assert(c->button);
+    return ([c->button state] == NSOnState);
+}
+
+void dlg_editbox_set(union control *ctrl, void *dv, char const *text)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->editbox) {
+       [c->editbox setStringValue:[NSString stringWithCString:text]];
+    } else {
+       assert(c->combobox);
+       [c->combobox setStringValue:[NSString stringWithCString:text]];
+    }
+}
+
+void dlg_editbox_get(union control *ctrl, void *dv, char *buffer, int length)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    NSString *str;
+
+    if (c->editbox) {
+       str = [c->editbox stringValue];
+    } else {
+       assert(c->combobox);
+       str = [c->combobox stringValue];
+    }
+    if (!str)
+       str = @"";
+
+    /* The length parameter to this method doesn't include a trailing NUL */
+    [str getCString:buffer maxLength:length-1];
+}
+
+void dlg_listbox_clear(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       [[c->tableview dataSource] clear];
+       [c->tableview reloadData];
+    } else {
+       [c->popupbutton removeAllItems];
+    }
+}
+
+void dlg_listbox_del(union control *ctrl, void *dv, int index)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       [[c->tableview dataSource] removestr:index];
+       [c->tableview reloadData];
+    } else {
+       [c->popupbutton removeItemAtIndex:index];
+    }
+}
+
+void dlg_listbox_addwithid(union control *ctrl, void *dv,
+                          char const *text, int id)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       [[c->tableview dataSource] add:text withId:id];
+       [c->tableview reloadData];
+    } else {
+       [c->popupbutton addItemWithTitle:[NSString stringWithCString:text]];
+       [[c->popupbutton lastItem] setTag:id];
+    }
+}
+
+void dlg_listbox_add(union control *ctrl, void *dv, char const *text)
+{
+    dlg_listbox_addwithid(ctrl, dv, text, -1);
+}
+
+int dlg_listbox_getid(union control *ctrl, void *dv, int index)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       return [[c->tableview dataSource] getid:index];
+    } else {
+       return [[c->popupbutton itemAtIndex:index] tag];
+    }
+}
+
+int dlg_listbox_index(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       return [c->tableview selectedRow];
+    } else {
+       return [c->popupbutton indexOfSelectedItem];
+    }
+}
+
+int dlg_listbox_issel(union control *ctrl, void *dv, int index)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       return [c->tableview isRowSelected:index];
+    } else {
+       return [c->popupbutton indexOfSelectedItem] == index;
+    }
+}
+
+void dlg_listbox_select(union control *ctrl, void *dv, int index)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    if (c->tableview) {
+       [c->tableview selectRow:index byExtendingSelection:NO];
+    } else {
+       [c->popupbutton selectItemAtIndex:index];
+    }
+}
+
+void dlg_text_set(union control *ctrl, void *dv, char const *text)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+
+    assert(c->textview);
+    [c->textview setString:[NSString stringWithCString:text]];
+}
+
+void dlg_filesel_set(union control *ctrl, void *dv, Filename fn)
+{
+    /* FIXME */
+}
+
+void dlg_filesel_get(union control *ctrl, void *dv, Filename *fn)
+{
+    /* FIXME */
+}
+
+void dlg_fontsel_set(union control *ctrl, void *dv, FontSpec fn)
+{
+    /* FIXME */
+}
+
+void dlg_fontsel_get(union control *ctrl, void *dv, FontSpec *fn)
+{
+    /* FIXME */
+}
+
+void dlg_update_start(union control *ctrl, void *dv)
+{
+    /* FIXME */
+}
+
+void dlg_update_done(union control *ctrl, void *dv)
+{
+    /* FIXME */
+}
+
+void dlg_set_focus(union control *ctrl, void *dv)
+{
+    /* FIXME */
+}
+
+union control *dlg_last_focused(union control *ctrl, void *dv)
+{
+    return NULL; /* FIXME */
+}
+
+void dlg_beep(void *dv)
+{
+    NSBeep();
+}
+
+void dlg_error_msg(void *dv, char *msg)
+{
+    /* FIXME */
+}
+
+void dlg_end(void *dv, int value)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    [d->target performSelector:d->action
+     withObject:[NSNumber numberWithInt:value]];
+}
+
+void dlg_coloursel_start(union control *ctrl, void *dv,
+                        int r, int g, int b)
+{
+    /* FIXME */
+}
+
+int dlg_coloursel_results(union control *ctrl, void *dv,
+                         int *r, int *g, int *b)
+{
+    return 0; /* FIXME */
+}
+
+void dlg_refresh(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c;
+
+    if (ctrl) {
+       if (ctrl->generic.handler != NULL)
+           ctrl->generic.handler(ctrl, d, d->data, EVENT_REFRESH);
+    } else {
+       int i;
+
+       for (i = 0; (c = index234(d->byctrl, i)) != NULL; i++) {
+           assert(c->ctrl != NULL);
+           if (c->ctrl->generic.handler != NULL)
+               c->ctrl->generic.handler(c->ctrl, d,
+                                        d->data, EVENT_REFRESH);
+       }
+    }
+}
+
+void *dlg_get_privdata(union control *ctrl, void *dv)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    return c->privdata;
+}
+
+void dlg_set_privdata(union control *ctrl, void *dv, void *ptr)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    c->privdata = ptr;
+    c->privdata_needs_free = FALSE;
+}
+
+void *dlg_alloc_privdata(union control *ctrl, void *dv, size_t size)
+{
+    struct fe_dlg *d = (struct fe_dlg *)dv;
+    struct fe_ctrl *c = fe_ctrl_byctrl(d, ctrl);
+    /*
+     * This is an internal allocation routine, so it's allowed to
+     * use smalloc directly.
+     */
+    c->privdata = smalloc(size);
+    c->privdata_needs_free = TRUE;
+    return c->privdata;
+}
diff --git a/macosx/osxdlg.m b/macosx/osxdlg.m
new file mode 100644 (file)
index 0000000..5bc13a4
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+ * osxdlg.m: various PuTTY dialog boxes for OS X.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "storage.h"
+#include "dialog.h"
+#include "osxclass.h"
+
+/*
+ * The `ConfigWindow' class is used to start up a new PuTTY
+ * session.
+ */
+
+@class ConfigTree;
+@interface ConfigTree : NSObject
+{
+    NSString **paths;
+    int *levels;
+    int nitems, itemsize;
+}
+- (void)addPath:(char *)path;
+@end
+
+@implementation ConfigTree
+- (id)init
+{
+    self = [super init];
+    paths = NULL;
+    levels = NULL;
+    nitems = itemsize = 0;
+    return self;
+}
+- (void)addPath:(char *)path
+{
+    if (nitems >= itemsize) {
+       itemsize += 32;
+       paths = sresize(paths, itemsize, NSString *);
+       levels = sresize(levels, itemsize, int);
+    }
+    paths[nitems] = [[NSString stringWithCString:path] retain];
+    levels[nitems] = ctrl_path_elements(path) - 1;
+    nitems++;
+}
+- (void)dealloc
+{
+    int i;
+
+    for (i = 0; i < nitems; i++)
+       [paths[i] release];
+
+    sfree(paths);
+    sfree(levels);
+
+    [super dealloc];
+}
+- (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
+{
+    int i, plevel;
+
+    if (item) {
+       for (i = 0; i < nitems; i++)
+           if (paths[i] == item)
+               break;
+       assert(i < nitems);
+       plevel = levels[i];
+       i++;
+    } else {
+       i = 0;
+       plevel = -1;
+    }
+
+    if (count)
+       *count = 0;
+
+    while (index > 0) {
+       if (i >= nitems || levels[i] != plevel+1)
+           return nil;
+       if (count)
+           (*count)++;
+       do {
+           i++;
+       } while (i < nitems && levels[i] > plevel+1);
+       index--;
+    }
+
+    return paths[i];
+}
+- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
+{
+    return [self iterateChildren:index ofItem:item count:NULL];
+}
+- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
+{
+    int count = 0;
+    /* pass nitems+1 to ensure we run off the end */
+    [self iterateChildren:nitems+1 ofItem:item count:&count];
+    return count;
+}
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
+{
+    return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
+}
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
+{
+    /*
+     * Trim off all path elements except the last one.
+     */
+    NSArray *components = [item componentsSeparatedByString:@"/"];
+    return [components objectAtIndex:[components count]-1];
+}
+@end
+
+@implementation ConfigWindow
+- (id)initWithConfig:(Config)aCfg
+{
+    NSScrollView *scrollview;
+    NSTableColumn *col;
+    ConfigTree *treedata;
+    int by = 0, mby = 0;
+    int wmin = 0;
+    int hmin = 0;
+    int panelht = 0;
+
+    get_sesslist(&sl, TRUE);
+
+    ctrlbox = ctrl_new_box();
+    setup_config_box(ctrlbox, &sl, FALSE /*midsession*/, aCfg.protocol,
+                    0 /* protcfginfo */);
+    unix_setup_config_box(ctrlbox, FALSE /*midsession*/);
+
+    cfg = aCfg;                               /* structure copy */
+
+    self = [super initWithContentRect:NSMakeRect(0,0,300,300)
+           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+                      NSClosableWindowMask)
+           backing:NSBackingStoreBuffered
+           defer:YES];
+    [self setTitle:@"PuTTY Configuration"];
+
+    [self setIgnoresMouseEvents:NO];
+
+    dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
+
+    scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
+    treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
+    [scrollview setBorderType:NSLineBorder];
+    [scrollview setDocumentView:treeview];
+    [[self contentView] addSubview:scrollview];
+    [scrollview setHasVerticalScroller:YES];
+    [scrollview setAutohidesScrollers:YES];
+    /* FIXME: the below is untested. Test it then remove this notice. */
+    [treeview setAllowsColumnReordering:NO];
+    [treeview setAllowsColumnResizing:NO];
+    [treeview setAllowsMultipleSelection:NO];
+    [treeview setAllowsEmptySelection:NO];
+    [treeview setAllowsColumnSelection:YES];
+
+    treedata = [[[ConfigTree alloc] init] retain];
+
+    col = [[NSTableColumn alloc] initWithIdentifier:nil];
+    [treeview addTableColumn:col];
+    [treeview setOutlineTableColumn:col];
+
+    [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
+
+    /*
+     * Create the controls.
+     */
+    {
+       int i;
+       char *path = NULL;
+
+       for (i = 0; i < ctrlbox->nctrlsets; i++) {
+           struct controlset *s = ctrlbox->ctrlsets[i];
+           int mw, mh;
+
+           if (!*s->pathname) {
+
+               create_ctrls(dv, [self contentView], s, &mw, &mh);
+
+               by += 20 + mh;
+
+               if (wmin < mw + 40)
+                   wmin = mw + 40;
+           } else {
+               int j = path ? ctrl_path_compare(s->pathname, path) : 0;
+
+               if (j != INT_MAX) {    /* add to treeview, start new panel */
+                   char *c;
+
+                   /*
+                    * We expect never to find an implicit path
+                    * component. For example, we expect never to
+                    * see A/B/C followed by A/D/E, because that
+                    * would _implicitly_ create A/D. All our path
+                    * prefixes are expected to contain actual
+                    * controls and be selectable in the treeview;
+                    * so we would expect to see A/D _explicitly_
+                    * before encountering A/D/E.
+                    */
+                   assert(j == ctrl_path_elements(s->pathname) - 1);
+
+                   c = strrchr(s->pathname, '/');
+                   if (!c)
+                       c = s->pathname;
+                   else
+                       c++;
+
+                   [treedata addPath:s->pathname];
+                   path = s->pathname;
+
+                   panelht = 0;
+               }
+
+               create_ctrls(dv, [self contentView], s, &mw, &mh);
+               if (wmin < mw + 3*20+150)
+                   wmin = mw + 3*20+150;
+               panelht += mh + 20;
+               if (hmin < panelht - 20)
+                   hmin = panelht - 20;
+           }
+       }
+    }
+
+    {
+       int i;
+       NSRect r;
+
+       [treeview setDataSource:treedata];
+       for (i = [treeview numberOfRows]; i-- ;)
+           [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
+
+       [treeview sizeToFit];
+       r = [treeview frame];
+       if (hmin < r.size.height)
+           hmin = r.size.height;
+    }
+
+    [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
+    [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
+    [treeview setDelegate:self];
+    mby = by;
+
+    /*
+     * Now place the controls.
+     */
+    {
+       int i;
+       char *path = NULL;
+       panelht = 0;
+
+       for (i = 0; i < ctrlbox->nctrlsets; i++) {
+           struct controlset *s = ctrlbox->ctrlsets[i];
+
+           if (!*s->pathname) {
+               by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
+           } else {
+               if (!path || strcmp(s->pathname, path))
+                   panelht = 0;
+
+               panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
+                                                 40+mby+hmin-panelht,
+                                                 wmin - (3*20+150));
+
+               path = s->pathname;
+           }
+       }
+    }
+
+    select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
+
+    [treeview reloadData];
+
+    dlg_refresh(NULL, dv);
+
+    [self center];                    /* :-) */
+
+    return self;
+}
+- (void)configBoxFinished:(id)object
+{
+    int ret = [object intValue];       /* it'll be an NSNumber */
+    if (ret) {
+       [controller performSelectorOnMainThread:
+        @selector(newSessionWithConfig:)
+        withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
+        waitUntilDone:NO];
+    }
+    [self close];
+}
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification
+{
+    const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
+    select_panel(dv, ctrlbox, path);
+}
+- (BOOL)outlineView:(NSOutlineView *)outlineView
+    shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
+{
+    return NO;                        /* no editing! */
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Various special-purpose dialog boxes.
+ */
+
+int askappend(void *frontend, Filename filename)
+{
+    return 0;                         /* FIXME */
+}
+
+void askalg(void *frontend, const char *algtype, const char *algname)
+{
+    fatalbox("Cipher algorithm dialog box not supported yet");
+    return;                           /* FIXME */
+}
+
+void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
+                        char *keystr, char *fingerprint)
+{
+    int ret;
+
+    /*
+     * Verify the key.
+     */
+    ret = verify_host_key(host, port, keytype, keystr);
+
+    if (ret == 0)
+       return;
+
+    /*
+     * FIXME FIXME FIXME. I currently lack any sensible means of
+     * asking the user for a verification non-application-modally,
+     * _or_ any means of closing just this connection if the answer
+     * is no (the Unix and Windows ports just exit() in this
+     * situation since they're one-connection-per-process).
+     * 
+     * What I need to do is to make this function optionally-
+     * asynchronous, much like the interface to agent_query(). It
+     * can either run modally and return a result directly, _or_ it
+     * can kick off a non-modal dialog, return a `please wait'
+     * status, and the dialog can call the backend back when the
+     * result comes in. Also, in either case, the aye/nay result
+     * wants to be passed to the backend so that it can tear down
+     * the connection if the answer was nay.
+     * 
+     * For the moment, I simply bomb out if we have an unrecognised
+     * host key. This makes this port safe but not very useful: you
+     * can only use it at all if you already have a host key cache
+     * set up by running the Unix port.
+     */
+    fatalbox("Host key dialog box not supported yet");
+}
+
+void old_keyfile_warning(void)
+{
+    /*
+     * This should never happen on OS X. We hope.
+     */
+}
+
+void about_box(void *window)
+{
+    /* FIXME */
+}
diff --git a/macosx/osxmain.m b/macosx/osxmain.m
new file mode 100644 (file)
index 0000000..bd57985
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * osxmain.m: main-program file of Mac OS X PuTTY.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+
+#include "putty.h"
+#include "osxclass.h"
+
+/* ----------------------------------------------------------------------
+ * Global variables.
+ */
+
+AppController *controller;
+
+/* ----------------------------------------------------------------------
+ * Miscellaneous elements of the interface to the cross-platform
+ * and Unix PuTTY code.
+ */
+
+const char platform_x11_best_transport[] = "unix";
+
+char *platform_get_x_display(void) {
+    return NULL;
+}
+
+FontSpec platform_default_fontspec(const char *name)
+{
+    FontSpec ret;
+    /* FIXME */
+    return ret;
+}
+
+Filename platform_default_filename(const char *name)
+{
+    Filename ret;
+    if (!strcmp(name, "LogFileName"))
+       strcpy(ret.path, "putty.log");
+    else
+       *ret.path = '\0';
+    return ret;
+}
+
+char *platform_default_s(const char *name)
+{
+    return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+    if (!strcmp(name, "CloseOnExit"))
+       return 2;  /* maps to FORCE_ON after painful rearrangement :-( */
+    return def;
+}
+
+char *x_get_default(const char *key)
+{
+    return NULL;                      /* this is a stub */
+}
+
+void modalfatalbox(char *p, ...)
+{
+    /* FIXME: proper OS X GUI stuff */
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+void fatalbox(char *p, ...)
+{
+    /* FIXME: proper OS X GUI stuff */
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+void cmdline_error(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "%s: ", appname);
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+    /*
+     * Clean up.
+     */
+    sk_cleanup();
+    random_save_seed();
+    exit(code);
+}
+
+/* ----------------------------------------------------------------------
+ * Tiny extension to NSMenuItem which carries a payload of a `void
+ * *', allowing several menu items to invoke the same message but
+ * pass different data through it.
+ */
+@interface DataMenuItem : NSMenuItem
+{
+    void *payload;
+}
+- (void)setPayload:(void *)d;
+- (void *)getPayload;
+@end
+@implementation DataMenuItem
+- (void)setPayload:(void *)d
+{
+    payload = d;
+}
+- (void *)getPayload
+{
+    return payload;
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Utility routines for constructing OS X menus.
+ */
+
+NSMenu *newmenu(const char *title)
+{
+    return [[[NSMenu allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithCString:title]]
+           autorelease];
+}
+
+NSMenu *newsubmenu(NSMenu *parent, const char *title)
+{
+    NSMenuItem *item;
+    NSMenu *child;
+
+    item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithCString:title]
+            action:NULL
+            keyEquivalent:@""]
+           autorelease];
+    child = newmenu(title);
+    [item setEnabled:YES];
+    [item setSubmenu:child];
+    [parent addItem:item];
+    return child;
+}
+
+id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title,
+              const char *key, id target, SEL action)
+{
+    unsigned mask = NSCommandKeyMask;
+
+    if (key[strcspn(key, "-")]) {
+       while (*key && *key != '-') {
+           int c = tolower((unsigned char)*key);
+           if (c == 's') {
+               mask |= NSShiftKeyMask;
+           } else if (c == 'o' || c == 'a') {
+               mask |= NSAlternateKeyMask;
+           }
+           key++;
+       }
+       if (*key)
+           key++;
+    }
+
+    item = [[item initWithTitle:[NSString stringWithCString:title]
+            action:NULL
+            keyEquivalent:[NSString stringWithCString:key]]
+           autorelease];
+
+    if (*key)
+       [item setKeyEquivalentModifierMask: mask];
+
+    [item setEnabled:YES];
+    [item setTarget:target];
+    [item setAction:action];
+
+    [parent addItem:item];
+
+    return item;
+}
+
+NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
+                   id target, SEL action)
+{
+    return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]],
+                      parent, title, key, target, action);
+}
+
+/* ----------------------------------------------------------------------
+ * AppController: the object which receives the messages from all
+ * menu selections that aren't standard OS X functions.
+ */
+@implementation AppController
+
+- (id)init
+{
+    self = [super init];
+    timer = NULL;
+    return self;
+}
+
+- (void)newTerminal:(id)sender
+{
+    id win;
+    Config cfg;
+
+    do_defaults(NULL, &cfg);
+
+    cfg.protocol = -1;                /* PROT_TERMINAL */
+
+    win = [[SessionWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionConfig:(id)sender
+{
+    id win;
+    Config cfg;
+
+    do_defaults(NULL, &cfg);
+
+    win = [[ConfigWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionWithConfig:(id)vdata
+{
+    id win;
+    Config cfg;
+    NSData *data = (NSData *)vdata;
+
+    assert([data length] == sizeof(cfg));
+    [data getBytes:&cfg];
+
+    win = [[SessionWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender
+{
+    NSMenu *menu = newmenu("Dock Menu");
+    /*
+     * FIXME: Add some useful things to this, probably including
+     * the saved session list.
+     */
+    return menu;
+}
+
+- (void)timerFired:(id)sender
+{
+    long now, next;
+
+    assert(sender == timer);
+
+    /* `sender' is the timer itself, so its userInfo is an NSNumber. */
+    now = [(NSNumber *)[sender userInfo] longValue];
+
+    [sender invalidate];
+
+    timer = NULL;
+
+    if (run_timers(now, &next))
+       [self setTimer:next];
+}
+
+- (void)setTimer:(long)next
+{
+    long interval = next - GETTICKCOUNT();
+    float finterval;
+
+    if (interval <= 0)
+       interval = 1;                  /* just in case */
+
+    finterval = interval / (float)TICKSPERSEC;
+
+    if (timer) {
+       [timer invalidate];
+    }
+
+    timer = [NSTimer scheduledTimerWithTimeInterval:finterval
+            target:self selector:@selector(timerFired:)
+            userInfo:[NSNumber numberWithLong:next] repeats:NO];
+}
+
+@end
+
+void timer_change_notify(long next)
+{
+    [controller setTimer:next];
+}
+
+/* ----------------------------------------------------------------------
+ * Annoyingly, it looks as if I have to actually subclass
+ * NSApplication if I want to catch NSApplicationDefined events. So
+ * here goes.
+ */
+@interface MyApplication : NSApplication
+{
+}
+@end
+@implementation MyApplication
+- (void)sendEvent:(NSEvent *)ev
+{
+    if ([ev type] == NSApplicationDefined)
+       osxsel_process_results();
+
+    [super sendEvent:ev];
+}    
+@end
+
+/* ----------------------------------------------------------------------
+ * Main program. Constructs the menus and runs the application.
+ */
+int main(int argc, char **argv)
+{
+    NSAutoreleasePool *pool;
+    NSMenu *menu;
+    NSMenuItem *item;
+    NSImage *icon;
+
+    pool = [[NSAutoreleasePool alloc] init];
+
+    icon = [NSImage imageNamed:@"NSApplicationIcon"];
+    [MyApplication sharedApplication];
+    [NSApp setApplicationIconImage:icon];
+
+    controller = [[[AppController alloc] init] autorelease];
+    [NSApp setDelegate:controller];
+
+    [NSApp setMainMenu: newmenu("Main Menu")];
+
+    menu = newsubmenu([NSApp mainMenu], "Apple Menu");
+    [NSApp setServicesMenu:newsubmenu(menu, "Services")];
+    [menu addItem:[NSMenuItem separatorItem]];
+    item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:));
+    item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:));
+    item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:));
+    [NSApp setAppleMenu: menu];
+
+    menu = newsubmenu([NSApp mainMenu], "File");
+    item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:));
+    item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:));
+    item = newitem(menu, "Close", "w", NULL, @selector(performClose:));
+
+    menu = newsubmenu([NSApp mainMenu], "Window");
+    [NSApp setWindowsMenu: menu];
+    item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:));
+
+//    menu = newsubmenu([NSApp mainMenu], "Help");
+//    item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:));
+
+    /*
+     * Start up the sub-thread doing select().
+     */
+    osxsel_init();
+
+    /*
+     * Start up networking.
+     */
+    sk_init();
+
+    /*
+     * FIXME: To make initial debugging more convenient I'm going
+     * to start by opening a session window unconditionally. This
+     * will probably change later on.
+     */
+    [controller newSessionConfig:nil];
+
+    [NSApp run];
+    [pool release];
+
+    return 0;
+}
diff --git a/macosx/osxsel.m b/macosx/osxsel.m
new file mode 100644 (file)
index 0000000..eac6028
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * osxsel.m: OS X implementation of the front end interface to uxsel.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <unistd.h>
+#include "putty.h"
+#include "osxclass.h"
+
+/*
+ * The unofficial Cocoa FAQ at
+ *
+ *   http://www.alastairs-place.net/cocoa/faq.txt
+ * 
+ * says that Cocoa has the native ability to be given an fd and
+ * tell you when it becomes readable, but cannot tell you when it
+ * becomes _writable_. This is unacceptable to PuTTY, which depends
+ * for correct functioning on being told both. Therefore, I can't
+ * use the Cocoa native mechanism.
+ * 
+ * Instead, I'm going to resort to threads. I start a second thread
+ * whose job is to do selects. At the termination of every select,
+ * it posts a Cocoa event into the main thread's event queue, so
+ * that the main thread gets select results interleaved with other
+ * GUI operations. Communication from the main thread _to_ the
+ * select thread is performed by writing to a pipe whose other end
+ * is one of the file descriptors being selected on. (This is the
+ * only sensible way, because we have to be able to interrupt a
+ * select in order to provide a new fd list.)
+ */
+
+/*
+ * In more detail, the select thread must:
+ * 
+ *  - start off by listening to _just_ the pipe, waiting to be told
+ *    to begin a select.
+ * 
+ *  - when it receives the `start' command, it should read the
+ *    shared uxsel data (which is protected by a mutex), set up its
+ *    select, and begin it.
+ * 
+ *  - when the select terminates, it should write the results
+ *    (perhaps minus the inter-thread pipe if it's there) into
+ *    shared memory and dispatch a GUI event to let the main thread
+ *    know.
+ * 
+ *  - the main thread will then think about it, do some processing,
+ *    and _then_ send a command saying `now restart select'. Before
+ *    sending that command it might easily have tinkered with the
+ *    uxsel structures, which is why it waited before sending it.
+ * 
+ *  - EOF on the inter-thread pipe, of course, means the process
+ *    has finished completely, so the select thread terminates.
+ * 
+ *  - The main thread may wish to adjust the uxsel settings in the
+ *    middle of a select. In this situation it first writes the new
+ *    data to the shared memory area, then notifies the select
+ *    thread by writing to the inter-thread pipe.
+ * 
+ * So the upshot is that the sequence of operations performed in
+ * the select thread must be:
+ * 
+ *  - read a byte from the pipe (which may block)
+ * 
+ *  - read the shared uxsel data and perform a select
+ * 
+ *  - notify the main thread of interesting select results (if any)
+ * 
+ *  - loop round again from the top.
+ * 
+ * This is sufficient. Notifying the select thread asynchronously
+ * by writing to the pipe will cause its select to terminate and
+ * another to begin immediately without blocking. If the select
+ * thread's select terminates due to network data, its subsequent
+ * pipe read will block until the main thread is ready to let it
+ * loose again.
+ */
+
+static int osxsel_pipe[2];
+
+static NSLock *osxsel_inlock;
+static fd_set osxsel_rfds_in;
+static fd_set osxsel_wfds_in;
+static fd_set osxsel_xfds_in;
+static int osxsel_inmax;
+
+static NSLock *osxsel_outlock;
+static fd_set osxsel_rfds_out;
+static fd_set osxsel_wfds_out;
+static fd_set osxsel_xfds_out;
+static int osxsel_outmax;
+
+static int inhibit_start_select;
+
+/*
+ * NSThread requires an object method as its thread procedure, so
+ * here I define a trivial holding class.
+ */
+@class OSXSel;
+@interface OSXSel : NSObject
+{
+}
+- (void)runThread:(id)arg;
+@end
+@implementation OSXSel
+- (void)runThread:(id)arg
+{
+    char c;
+    fd_set r, w, x;
+    int n, ret;
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+
+    while (1) {
+       /*
+        * Read one byte from the pipe.
+        */
+       ret = read(osxsel_pipe[0], &c, 1);
+
+       if (ret <= 0)
+           return;                    /* terminate the thread */
+
+       /*
+        * Now set up the select data.
+        */
+       [osxsel_inlock lock];
+       memcpy(&r, &osxsel_rfds_in, sizeof(fd_set));
+       memcpy(&w, &osxsel_wfds_in, sizeof(fd_set));
+       memcpy(&x, &osxsel_xfds_in, sizeof(fd_set));
+       n = osxsel_inmax;
+       [osxsel_inlock unlock];
+       FD_SET(osxsel_pipe[0], &r);
+       if (n < osxsel_pipe[0]+1)
+           n = osxsel_pipe[0]+1;
+
+       /*
+        * Perform the select.
+        */
+       ret = select(n, &r, &w, &x, NULL);
+
+       /*
+        * Detect the one special case in which the only
+        * interesting fd was the inter-thread pipe. In that
+        * situation only we are interested - the main thread will
+        * not be!
+        */
+       if (ret == 1 && FD_ISSET(osxsel_pipe[0], &r))
+           continue;                  /* just loop round again */
+
+       /*
+        * Write the select results to shared data.
+        * 
+        * I _think_ we don't need this data to be lock-protected:
+        * it won't be read by the main thread until after we send
+        * a message indicating that we've finished writing it, and
+        * we won't start another select (hence potentially writing
+        * it again) until the main thread notifies us in return.
+        * 
+        * However, I'm scared of multithreading and not totally
+        * convinced of my reasoning, so I'm going to lock it
+        * anyway.
+        */
+       [osxsel_outlock lock];
+       memcpy(&osxsel_rfds_out, &r, sizeof(fd_set));
+       memcpy(&osxsel_wfds_out, &w, sizeof(fd_set));
+       memcpy(&osxsel_xfds_out, &x, sizeof(fd_set));
+       osxsel_outmax = n;
+       [osxsel_outlock unlock];
+
+       /*
+        * Post a message to the main thread's message queue
+        * telling it that select data is available.
+        */
+       [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined
+                         location:NSMakePoint(0,0)
+                         modifierFlags:0
+                         timestamp:0
+                         windowNumber:0
+                         context:nil
+                         subtype:0
+                         data1:0
+                         data2:0]
+        atStart:NO];
+    }
+
+    [pool release];
+}
+@end
+
+void osxsel_init(void)
+{
+    uxsel_init();
+
+    if (pipe(osxsel_pipe) < 0) {
+       fatalbox("Unable to set up inter-thread pipe for select");
+    }
+    [NSThread detachNewThreadSelector:@selector(runThread:)
+       toTarget:[[[OSXSel alloc] init] retain] withObject:nil];
+    /*
+     * Also initialise (i.e. clear) the input fd_sets. Need not
+     * start a select just yet - the select thread will block until
+     * we have at least one fd for it!
+     */
+    FD_ZERO(&osxsel_rfds_in);
+    FD_ZERO(&osxsel_wfds_in);
+    FD_ZERO(&osxsel_xfds_in);
+    osxsel_inmax = 0;
+    /*
+     * Initialise the mutex locks used to protect the data passed
+     * between threads.
+     */
+    osxsel_inlock = [[[NSLock alloc] init] retain];
+    osxsel_outlock = [[[NSLock alloc] init] retain];
+}
+
+static void osxsel_start_select(void)
+{
+    char c = 'g';                     /* for `Go!' :-) but it's never used */
+
+    if (!inhibit_start_select)
+       write(osxsel_pipe[1], &c, 1);
+}
+
+int uxsel_input_add(int fd, int rwx)
+{
+    /*
+     * Add the new fd to the appropriate input fd_sets, then write
+     * to the inter-thread pipe.
+     */
+    [osxsel_inlock lock];
+    if (rwx & 1)
+       FD_SET(fd, &osxsel_rfds_in);
+    else
+       FD_CLR(fd, &osxsel_rfds_in);
+    if (rwx & 2)
+       FD_SET(fd, &osxsel_wfds_in);
+    else
+       FD_CLR(fd, &osxsel_wfds_in);
+    if (rwx & 4)
+       FD_SET(fd, &osxsel_xfds_in);
+    else
+       FD_CLR(fd, &osxsel_xfds_in);
+    if (osxsel_inmax < fd+1)
+       osxsel_inmax = fd+1;
+    [osxsel_inlock unlock];
+    osxsel_start_select();
+
+    /*
+     * We must return an `id' which will be passed back to us at
+     * the time of uxsel_input_remove. Since we have no need to
+     * store ids in that sense, we might as well go with the fd
+     * itself.
+     */
+    return fd;
+}
+
+void uxsel_input_remove(int id)
+{
+    /*
+     * Remove the fd from all the input fd_sets. In this
+     * implementation, the simplest way to do that is to call
+     * uxsel_input_add with rwx==0!
+     */
+    uxsel_input_add(id, 0);
+}
+
+/*
+ * Function called in the main thread to process results. It will
+ * have to read the output fd_sets, go through them, call back to
+ * uxsel with the results, and then write to the inter-thread pipe.
+ * 
+ * This function will have to be called from an event handler in
+ * osxmain.m, which will therefore necessarily contain a small part
+ * of this mechanism (along with calling osxsel_init).
+ */
+void osxsel_process_results(void)
+{
+    int i;
+
+    /*
+     * We must write to the pipe to start a fresh select _even if_
+     * there were no changes. So for efficiency, we set a flag here
+     * which inhibits uxsel_input_{add,remove} from writing to the
+     * pipe; then once we finish processing, we clear the flag
+     * again and write a single byte ourselves. It's cleaner,
+     * because it wakes up the select thread fewer times.
+     */
+    inhibit_start_select = TRUE;
+
+    [osxsel_outlock lock];
+
+    for (i = 0; i < osxsel_outmax; i++) {
+       if (FD_ISSET(i, &osxsel_xfds_out))
+           select_result(i, 4);
+    }
+    for (i = 0; i < osxsel_outmax; i++) {
+       if (FD_ISSET(i, &osxsel_rfds_out))
+           select_result(i, 1);
+    }
+    for (i = 0; i < osxsel_outmax; i++) {
+       if (FD_ISSET(i, &osxsel_wfds_out))
+           select_result(i, 2);
+    }
+
+    [osxsel_outlock unlock];
+
+    inhibit_start_select = FALSE;
+    osxsel_start_select();
+}
diff --git a/macosx/osxwin.m b/macosx/osxwin.m
new file mode 100644 (file)
index 0000000..a54f771
--- /dev/null
@@ -0,0 +1,1122 @@
+/*
+ * osxwin.m: code to manage a session window in Mac OS X PuTTY.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include "putty.h"
+#include "terminal.h"
+#include "osxclass.h"
+
+/* Colours come in two flavours: configurable, and xterm-extended. */
+#define NCFGCOLOURS (lenof(((Config *)0)->colours))
+#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */
+#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
+
+/*
+ * The key component of the per-session data is the SessionWindow
+ * class. A pointer to this is used as the frontend handle, to be
+ * passed to all the platform-independent subsystems that require
+ * one.
+ */
+
+@interface TerminalView : NSImageView
+{
+    NSFont *font;
+    NSImage *image;
+    Terminal *term;
+    Config cfg;
+    NSColor *colours[NALLCOLOURS];
+    float fw, fasc, fdesc, fh;
+}
+- (void)drawStartFinish:(BOOL)start;
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+    attr:(unsigned long)attr lattr:(int)lattr;
+@end
+
+@implementation TerminalView
+- (BOOL)isFlipped
+{
+    return YES;
+}
+- (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg
+{
+    float w, h;
+
+    self = [self initWithFrame:NSMakeRect(0,0,100,100)];
+
+    term = aTerm;
+    cfg = aCfg;
+
+    /*
+     * Initialise the fonts we're going to use.
+     * 
+     * FIXME: for the moment I'm sticking with exactly one default font.
+     */
+    font = [NSFont userFixedPitchFontOfSize:0];
+
+    /*
+     * Now determine the size of the primary font.
+     * 
+     * FIXME: If we have multiple fonts, we may need to set fasc
+     * and fdesc to the _maximum_ asc and desc out of all the
+     * fonts, _before_ adding them together to get fh.
+     */
+    fw = [font widthOfString:@"A"];
+    fasc = [font ascender];
+    fdesc = -[font descender];
+    fh = fasc + fdesc;
+    fh = (int)fh + (fh > (int)fh);     /* round up, ickily */
+
+    /*
+     * Use this to figure out the size of the terminal view.
+     */
+    w = fw * term->cols;
+    h = fh * term->rows;
+
+    /*
+     * And set our size and subimage.
+     */
+    image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
+    [image setFlipped:YES];
+    [self setImage:image];
+    [self setFrame:NSMakeRect(0,0,w,h)];
+
+    term_invalidate(term);
+
+    return self;
+}
+- (void)drawStartFinish:(BOOL)start
+{
+    if (start)
+       [image lockFocus];
+    else
+       [image unlockFocus];
+}
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+    attr:(unsigned long)attr lattr:(int)lattr
+{
+    int nfg, nbg, rlen, widefactor;
+    float ox, oy, tw, th;
+    NSDictionary *attrdict;
+
+    /* FIXME: TATTR_COMBINING */
+
+    nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
+    nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
+    if (attr & ATTR_REVERSE) {
+       int t = nfg;
+       nfg = nbg;
+       nbg = t;
+    }
+    if (cfg.bold_colour && (attr & ATTR_BOLD)) {
+       if (nfg < 16) nfg |= 8;
+       else if (nfg >= 256) nfg |= 1;
+    }
+    if (cfg.bold_colour && (attr & ATTR_BLINK)) {
+       if (nbg < 16) nbg |= 8;
+       else if (nbg >= 256) nbg |= 1;
+    }
+    if (attr & TATTR_ACTCURS) {
+       nfg = 260;
+       nbg = 261;
+    }
+
+    if (attr & ATTR_WIDE) {
+       widefactor = 2;
+       /* FIXME: what do we actually have to do about wide characters? */
+    } else {
+       widefactor = 1;
+    }
+
+    /* FIXME: ATTR_BOLD without cfg.bold_colour */
+
+    if ((lattr & LATTR_MODE) != LATTR_NORM) {
+       x *= 2;
+       if (x >= term->cols)
+           return;
+       if (x + len*2*widefactor > term->cols)
+           len = (term->cols-x)/2/widefactor;/* trim to LH half */
+       rlen = len * 2;
+    } else
+       rlen = len;
+
+    /* FIXME: how do we actually implement double-{width,height} lattrs? */
+
+    ox = x * fw;
+    oy = y * fh;
+    tw = rlen * widefactor * fw;
+    th = fh;
+
+    /*
+     * Set the clipping rectangle.
+     */
+    [[NSGraphicsContext currentContext] saveGraphicsState];
+    [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)];
+
+    attrdict = [NSDictionary dictionaryWithObjectsAndKeys:
+               colours[nfg], NSForegroundColorAttributeName,
+               colours[nbg], NSBackgroundColorAttributeName,
+               font, NSFontAttributeName, nil];
+
+    /*
+     * Create an NSString and draw it.
+     * 
+     * Annoyingly, although our input is wchar_t which is four
+     * bytes wide on OS X and terminal.c supports 32-bit Unicode,
+     * we must convert into the two-byte type `unichar' to store in
+     * NSString, so we lose display capability for extra-BMP stuff
+     * at this point.
+     */
+    {
+       NSString *string;
+       unichar *utext;
+       int i;
+
+       utext = snewn(len, unichar);
+       for (i = 0; i < len; i++)
+           utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]);
+
+       string = [NSString stringWithCharacters:utext length:len];
+       [string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict];
+
+       sfree(utext);
+    }
+
+    /*
+     * Restore the graphics state from before the clipRect: call.
+     */
+    [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+    /*
+     * And flag this area as needing display.
+     */
+    [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)];
+}
+
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
+{
+    assert(n >= 0 && n < lenof(colours));
+    colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0]
+                 retain];
+}
+@end
+
+@implementation SessionWindow
+- (id)initWithConfig:(Config)aCfg
+{
+    NSRect rect = { {0,0}, {0,0} };
+
+    cfg = aCfg;                               /* structure copy */
+
+    init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override,
+            CS_UTF8, cfg.vtmode);
+    term = term_init(&cfg, &ucsdata, self);
+    logctx = log_init(self, &cfg);
+    term_provide_logctx(term, logctx);
+    term_size(term, cfg.height, cfg.width, cfg.savelines);
+
+    termview = [[[TerminalView alloc] initWithTerminal:term config:cfg]
+               autorelease];
+
+    /*
+     * Now work out the size of the window.
+     */
+    rect = [termview frame];
+    rect.origin = NSMakePoint(0,0);
+    rect.size.width += 2 * cfg.window_border;
+    rect.size.height += 2 * cfg.window_border;
+
+    /*
+     * Set up a backend.
+     */
+    {
+       int i;
+       back = &pty_backend;
+       for (i = 0; backends[i].backend != NULL; i++)
+           if (backends[i].protocol == cfg.protocol) {
+               back = backends[i].backend;
+               break;
+           }
+    }
+
+    {
+       const char *error;
+       char *realhost = NULL;
+       error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
+                          &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
+       if (error) {
+           fatalbox("%s\n", error);   /* FIXME: connection_fatal at worst */
+       }
+
+       if (realhost)
+           sfree(realhost);           /* FIXME: do something with this */
+    }
+
+    /*
+     * Create a line discipline. (This must be done after creating
+     * the terminal _and_ the backend, since it needs to be passed
+     * pointers to both.)
+     */
+    ldisc = ldisc_create(&cfg, term, back, backhandle, self);
+
+    /*
+     * FIXME: Set up a scrollbar.
+     */
+
+    self = [super initWithContentRect:rect
+           styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
+                      NSClosableWindowMask)
+           backing:NSBackingStoreBuffered
+           defer:YES];
+    [self setTitle:@"PuTTY"];
+
+    [self setIgnoresMouseEvents:NO];
+
+    /*
+     * Put the terminal view in the window.
+     */
+    rect = [termview frame];
+    rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
+    [termview setFrame:rect];
+    [[self contentView] addSubview:termview];
+
+    /*
+     * Set up the colour palette.
+     */
+    palette_reset(self);
+
+    /*
+     * FIXME: Only the _first_ document window should be centred.
+     * The subsequent ones should appear down and to the right of
+     * it, probably using the cascade function provided by Cocoa.
+     * Also we're apparently required by the HIG to remember and
+     * reuse previous positions of windows, although I'm not sure
+     * how that works if the user opens more than one of the same
+     * session type.
+     */
+    [self center];                    /* :-) */
+
+    return self;
+}
+
+- (void)dealloc
+{
+    /*
+     * FIXME: Here we must deallocate all sorts of stuff: the
+     * terminal, the backend, the ldisc, the logctx, you name it.
+     * Do so.
+     */
+    [super dealloc];
+}
+
+- (void)drawStartFinish:(BOOL)start
+{
+    [termview drawStartFinish:start];
+}
+
+- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
+{
+    [termview setColour:n r:r g:g b:b];
+}
+
+- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
+    attr:(unsigned long)attr lattr:(int)lattr
+{
+    /* Pass this straight on to the TerminalView. */
+    [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
+}
+
+- (Config *)cfg
+{
+    return &cfg;
+}
+
+- (void)keyDown:(NSEvent *)ev
+{
+    NSString *s = [ev characters];
+    int i;
+    int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
+    int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
+    wchar_t output[32];
+    char coutput[32];
+    int use_coutput = FALSE, special = FALSE, start, end;
+
+printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);
+
+    /*
+     * FIXME: Alt+numberpad codes.
+     */
+
+    /*
+     * Shift and Ctrl with PageUp/PageDown for scrollback.
+     */
+    if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
+       term_scroll(term, 0, -term->rows/2);
+       return;
+    }
+    if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
+       term_scroll(term, 0, -1);
+       return;
+    }
+    if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
+       term_scroll(term, 0, +term->rows/2);
+       return;
+    }
+    if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
+       term_scroll(term, 0, +1);
+       return;
+    }
+
+    /*
+     * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
+     */
+
+    /*
+     * FIXME: Alt (Option? Command?) prefix in general.
+     * 
+     * (Note that Alt-Shift-thing will work just by looking at
+     * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
+     * processing properly, and Alt-as-in-Option won't happen at
+     * all. Hmmm.)
+     * 
+     * (Note also that we need to be able to override menu key
+     * equivalents before this is particularly useful.)
+     */
+    start = 1;
+    end = start;
+
+    /*
+     * Ctrl-` is the same as Ctrl-\, unless we already have a
+     * better idea.
+     */
+    if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
+       output[1] = '\x1c';
+       end = 2;
+    }
+
+    /* We handle Return ourselves, because it needs to be flagged as
+     * special to ldisc. */
+    if (n == 1 && c == '\015') {
+       coutput[1] = '\015';
+       use_coutput = TRUE;
+       end = 2;
+       special = TRUE;
+    }
+
+    /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
+    if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
+       cm == ' ') {
+       output[1] = '\240';
+       end = 2;
+    }
+
+    /* Control-2, Control-Space and Control-@ are all NUL. */
+    if ((m & NSControlKeyMask) && n == 1 &&
+       (cm == '2' || cm == '@' || cm == ' ') && c == cm) {
+       output[1] = '\0';
+       end = 2;
+    }
+
+    /* We don't let MacOS tell us what Backspace is! We know better. */
+    if (cm == 0x7F && !(m & NSShiftKeyMask)) {
+       coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
+       end = 2;
+       use_coutput = special = TRUE;
+    }
+    /* For Shift Backspace, do opposite of what is configured. */
+    if (cm == 0x7F && (m & NSShiftKeyMask)) {
+       coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
+       end = 2;
+       use_coutput = special = TRUE;
+    }
+
+    /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
+     * default on MacOS! */
+    if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
+       end = 1;
+       output[end++] = '\033';
+       output[end++] = '[';
+       output[end++] = 'Z';
+    }
+
+    /*
+     * NetHack keypad mode.
+     */
+    if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
+       wchar_t *keys = NULL;
+       switch (cm) {
+         case '1': keys = L"bB"; break;
+         case '2': keys = L"jJ"; break;
+         case '3': keys = L"nN"; break;
+         case '4': keys = L"hH"; break;
+         case '5': keys = L".."; break;
+         case '6': keys = L"lL"; break;
+         case '7': keys = L"yY"; break;
+         case '8': keys = L"kK"; break;
+         case '9': keys = L"uU"; break;
+       }
+       if (keys) {
+           end = 2;
+           if (m & NSShiftKeyMask)
+               output[1] = keys[1];
+           else
+               output[1] = keys[0];
+           goto done;
+       }
+    }
+
+    /*
+     * Application keypad mode.
+     */
+    if (term->app_keypad_keys && !cfg.no_applic_k &&
+       (m & NSNumericPadKeyMask)) {
+       int xkey = 0;
+       switch (cm) {
+         case NSClearLineFunctionKey: xkey = 'P'; break;
+         case '=': xkey = 'Q'; break;
+         case '/': xkey = 'R'; break;
+         case '*': xkey = 'S'; break;
+           /*
+            * FIXME: keypad - and + need to be mapped to ESC O l
+            * and ESC O k, or ESC O l and ESC O m, depending on
+            * xterm function key mode, and I can't remember which
+            * goes where.
+            */
+         case '\003': xkey = 'M'; break;
+         case '0': xkey = 'p'; break;
+         case '1': xkey = 'q'; break;
+         case '2': xkey = 'r'; break;
+         case '3': xkey = 's'; break;
+         case '4': xkey = 't'; break;
+         case '5': xkey = 'u'; break;
+         case '6': xkey = 'v'; break;
+         case '7': xkey = 'w'; break;
+         case '8': xkey = 'x'; break;
+         case '9': xkey = 'y'; break;
+         case '.': xkey = 'n'; break;
+       }
+       if (xkey) {
+           if (term->vt52_mode) {
+               if (xkey >= 'P' && xkey <= 'S') {
+                   output[end++] = '\033';
+                   output[end++] = xkey;
+               } else {
+                   output[end++] = '\033';
+                   output[end++] = '?';
+                   output[end++] = xkey;
+               }
+           } else {
+               output[end++] = '\033';
+               output[end++] = 'O';
+               output[end++] = xkey;
+           }
+           goto done;
+       }
+    }
+
+    /*
+     * Next, all the keys that do tilde codes. (ESC '[' nn '~',
+     * for integer decimal nn.)
+     *
+     * We also deal with the weird ones here. Linux VCs replace F1
+     * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
+     * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
+     * respectively.
+     */
+    {
+       int code = 0;
+       switch (cm) {
+         case NSF1FunctionKey:
+           code = (m & NSShiftKeyMask ? 23 : 11);
+           break;
+         case NSF2FunctionKey:
+           code = (m & NSShiftKeyMask ? 24 : 12);
+           break;
+         case NSF3FunctionKey:
+           code = (m & NSShiftKeyMask ? 25 : 13);
+           break;
+         case NSF4FunctionKey:
+           code = (m & NSShiftKeyMask ? 26 : 14);
+           break;
+         case NSF5FunctionKey:
+           code = (m & NSShiftKeyMask ? 28 : 15);
+           break;
+         case NSF6FunctionKey:
+           code = (m & NSShiftKeyMask ? 29 : 17);
+           break;
+         case NSF7FunctionKey:
+           code = (m & NSShiftKeyMask ? 31 : 18);
+           break;
+         case NSF8FunctionKey:
+           code = (m & NSShiftKeyMask ? 32 : 19);
+           break;
+         case NSF9FunctionKey:
+           code = (m & NSShiftKeyMask ? 33 : 20);
+           break;
+         case NSF10FunctionKey:
+           code = (m & NSShiftKeyMask ? 34 : 21);
+           break;
+         case NSF11FunctionKey:
+           code = 23;
+           break;
+         case NSF12FunctionKey:
+           code = 24;
+           break;
+         case NSF13FunctionKey:
+           code = 25;
+           break;
+         case NSF14FunctionKey:
+           code = 26;
+           break;
+         case NSF15FunctionKey:
+           code = 28;
+           break;
+         case NSF16FunctionKey:
+           code = 29;
+           break;
+         case NSF17FunctionKey:
+           code = 31;
+           break;
+         case NSF18FunctionKey:
+           code = 32;
+           break;
+         case NSF19FunctionKey:
+           code = 33;
+           break;
+         case NSF20FunctionKey:
+           code = 34;
+           break;
+       }
+       if (!(m & NSControlKeyMask)) switch (cm) {
+         case NSHomeFunctionKey:
+           code = 1;
+           break;
+#ifdef FIXME
+         case GDK_Insert: case GDK_KP_Insert:
+           code = 2;
+           break;
+#endif
+         case NSDeleteFunctionKey:
+           code = 3;
+           break;
+         case NSEndFunctionKey:
+           code = 4;
+           break;
+         case NSPageUpFunctionKey:
+           code = 5;
+           break;
+         case NSPageDownFunctionKey:
+           code = 6;
+           break;
+       }
+       /* Reorder edit keys to physical order */
+       if (cfg.funky_type == FUNKY_VT400 && code <= 6)
+           code = "\0\2\1\4\5\3\6"[code];
+
+       if (term->vt52_mode && code > 0 && code <= 6) {
+           output[end++] = '\033';
+           output[end++] = " HLMEIG"[code];
+           goto done;
+       }
+
+       if (cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
+           code >= 11 && code <= 34) {
+           char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
+           int index = 0;
+           switch (cm) {
+             case NSF1FunctionKey: index = 0; break;
+             case NSF2FunctionKey: index = 1; break;
+             case NSF3FunctionKey: index = 2; break;
+             case NSF4FunctionKey: index = 3; break;
+             case NSF5FunctionKey: index = 4; break;
+             case NSF6FunctionKey: index = 5; break;
+             case NSF7FunctionKey: index = 6; break;
+             case NSF8FunctionKey: index = 7; break;
+             case NSF9FunctionKey: index = 8; break;
+             case NSF10FunctionKey: index = 9; break;
+             case NSF11FunctionKey: index = 10; break;
+             case NSF12FunctionKey: index = 11; break;
+           }
+           if (m & NSShiftKeyMask) index += 12;
+           if (m & NSControlKeyMask) index += 24;
+           output[end++] = '\033';
+           output[end++] = '[';
+           output[end++] = codes[index];
+           goto done;
+       }
+       if (cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
+           code >= 1 && code <= 6) {
+           char codes[] = "HL.FIG";
+           if (code == 3) {
+               output[1] = '\x7F';
+               end = 2;
+           } else {
+               output[end++] = '\033';
+               output[end++] = '[';
+               output[end++] = codes[code-1];
+           }
+           goto done;
+       }
+       if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
+           code >= 11 && code <= 24) {
+           int offt = 0;
+           if (code > 15)
+               offt++;
+           if (code > 21)
+               offt++;
+           if (term->vt52_mode) {
+               output[end++] = '\033';
+               output[end++] = code + 'P' - 11 - offt;
+           } else {
+               output[end++] = '\033';
+               output[end++] = 'O';
+               output[end++] = code + 'P' - 11 - offt;
+           }
+           goto done;
+       }
+       if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
+           output[end++] = '\033';
+           output[end++] = '[';
+           output[end++] = '[';        
+           output[end++] = code + 'A' - 11;
+           goto done;
+       }
+       if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
+           if (term->vt52_mode) {
+               output[end++] = '\033';
+               output[end++] = code + 'P' - 11;
+           } else {
+               output[end++] = '\033';
+               output[end++] = 'O';
+               output[end++] = code + 'P' - 11;
+           }
+           goto done;
+       }
+       if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
+           if (code == 1) {
+               output[end++] = '\033';
+               output[end++] = '[';
+               output[end++] = 'H';
+           } else {
+               output[end++] = '\033';
+               output[end++] = 'O';
+               output[end++] = 'w';
+           }
+           goto done;
+       }
+       if (code) {
+           char buf[20];
+           sprintf(buf, "\x1B[%d~", code);
+           for (i = 0; buf[i]; i++)
+               output[end++] = buf[i];
+           goto done;
+       }
+    }
+
+    /*
+     * Cursor keys. (This includes the numberpad cursor keys,
+     * if we haven't already done them due to app keypad mode.)
+     */
+    {
+       int xkey = 0;
+       switch (cm) {
+         case NSUpArrowFunctionKey: xkey = 'A'; break;
+         case NSDownArrowFunctionKey: xkey = 'B'; break;
+         case NSRightArrowFunctionKey: xkey = 'C'; break;
+         case NSLeftArrowFunctionKey: xkey = 'D'; break;
+       }
+       if (xkey) {
+           /*
+            * The arrow keys normally do ESC [ A and so on. In
+            * app cursor keys mode they do ESC O A instead.
+            * Ctrl toggles the two modes.
+            */
+           if (term->vt52_mode) {
+               output[end++] = '\033';
+               output[end++] = xkey;
+           } else if (!term->app_cursor_keys ^ !(m & NSControlKeyMask)) {
+               output[end++] = '\033';
+               output[end++] = 'O';
+               output[end++] = xkey;
+           } else {
+               output[end++] = '\033';
+               output[end++] = '[';
+               output[end++] = xkey;
+           }
+           goto done;
+       }
+    }
+
+    done:
+
+    /*
+     * Failing everything else, send the exact Unicode we got from
+     * OS X.
+     */
+    if (end == start) {
+       if (n > lenof(output)-start)
+           n = lenof(output)-start;   /* _shouldn't_ happen! */
+       for (i = 0; i < n; i++) {
+           output[i+start] = [s characterAtIndex:i];
+       }
+       end = n+start;
+    }
+
+    if (use_coutput) {
+       assert(special);
+       assert(end < lenof(coutput));
+       coutput[end] = '\0';
+       ldisc_send(ldisc, coutput+start, -2, TRUE);
+    } else {
+       luni_send(ldisc, output+start, end-start, TRUE);
+    }
+}
+
+- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
+{
+    return term_data(term, is_stderr, data, len);
+}
+
+@end
+
+int from_backend(void *frontend, int is_stderr, const char *data, int len)
+{
+    SessionWindow *win = (SessionWindow *)frontend;
+    return [win fromBackend:data len:len isStderr:is_stderr];
+}
+
+void frontend_keypress(void *handle)
+{
+    /* FIXME */
+}
+
+void connection_fatal(void *frontend, char *p, ...)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME: proper OS X GUI stuff */
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+void notify_remote_exit(void *frontend)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void ldisc_update(void *frontend, int echo, int edit)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /*
+     * In a GUI front end, this need do nothing.
+     */
+}
+
+void update_specials_menu(void *frontend)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * This is still called when mode==BELL_VISUAL, even though the
+ * visual bell is handled entirely within terminal.c, because we
+ * may want to perform additional actions on any kind of bell (for
+ * example, taskbar flashing in Windows).
+ */
+void beep(void *frontend, int mode)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    if (mode != BELL_VISUAL)
+       NSBeep();
+}
+
+int char_width(Context ctx, int uc)
+{
+    /*
+     * Under X, any fixed-width font really _is_ fixed-width.
+     * Double-width characters will be dealt with using a separate
+     * font. For the moment we can simply return 1.
+     */
+    return 1;
+}
+
+void palette_set(void *frontend, int n, int r, int g, int b)
+{
+    SessionWindow *win = (SessionWindow *)frontend;
+
+    if (n >= 16)
+       n += 256 - 16;
+    if (n > NALLCOLOURS)
+       return;
+    [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0];
+
+    /*
+     * FIXME: do we need an OS X equivalent of set_window_background?
+     */
+}
+
+void palette_reset(void *frontend)
+{
+    SessionWindow *win = (SessionWindow *)frontend;
+    Config *cfg = [win cfg];
+
+    /* This maps colour indices in cfg to those used in colours[]. */
+    static const int ww[] = {
+       256, 257, 258, 259, 260, 261,
+       0, 8, 1, 9, 2, 10, 3, 11,
+       4, 12, 5, 13, 6, 14, 7, 15
+    };
+
+    int i;
+
+    for (i = 0; i < NCFGCOLOURS; i++) {
+       [win setColour:ww[i] r:cfg->colours[i][0]/255.0
+        g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
+    }
+
+    for (i = 0; i < NEXTCOLOURS; i++) {
+       if (i < 216) {
+           int r = i / 36, g = (i / 6) % 6, b = i % 6;
+           [win setColour:i+16 r:r/5.0 g:g/5.0 b:b/5.0];
+       } else {
+           int shade = i - 216;
+           float fshade = (shade + 1) / (float)(NEXTCOLOURS - 216 + 1);
+           [win setColour:i+16 r:fshade g:fshade b:fshade];
+       }
+    }
+
+    /*
+     * FIXME: do we need an OS X equivalent of set_window_background?
+     */
+}
+
+Context get_ctx(void *frontend)
+{
+    SessionWindow *win = (SessionWindow *)frontend;
+
+    /*
+     * Lock the drawing focus on the image inside the TerminalView.
+     */
+    [win drawStartFinish:YES];
+
+    [[NSGraphicsContext currentContext] setShouldAntialias:YES];
+
+    /*
+     * Cocoa drawing functions don't take a graphics context: that
+     * parameter is implicit. Therefore, we'll use the frontend
+     * handle itself as the context, on the grounds that it's as
+     * good a thing to use as any.
+     */
+    return frontend;
+}
+
+void free_ctx(Context ctx)
+{
+    SessionWindow *win = (SessionWindow *)ctx;
+
+    [win drawStartFinish:NO];
+}
+
+void do_text(Context ctx, int x, int y, wchar_t *text, int len,
+            unsigned long attr, int lattr)
+{
+    SessionWindow *win = (SessionWindow *)ctx;
+
+    [win doText:text len:len x:x y:y attr:attr lattr:lattr];
+}
+
+void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
+              unsigned long attr, int lattr)
+{
+    SessionWindow *win = (SessionWindow *)ctx;
+    Config *cfg = [win cfg];
+    int active, passive;
+
+    if (attr & TATTR_PASCURS) {
+       attr &= ~TATTR_PASCURS;
+       passive = 1;
+    } else
+       passive = 0;
+    if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
+       attr &= ~TATTR_ACTCURS;
+        active = 1;
+    } else
+        active = 0;
+
+    [win doText:text len:len x:x y:y attr:attr lattr:lattr];
+
+    /*
+     * FIXME: now draw the various cursor types (both passive and
+     * active underlines and vertical lines, plus passive blocks).
+     */
+}
+
+/*
+ * Minimise or restore the window in response to a server-side
+ * request.
+ */
+void set_iconic(void *frontend, int iconic)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Move the window in response to a server-side request.
+ */
+void move_window(void *frontend, int x, int y)
+{
+    //SessionWindow *win = (SessionWindow *)frontend; 
+    /* FIXME */
+}
+
+/*
+ * Move the window to the top or bottom of the z-order in response
+ * to a server-side request.
+ */
+void set_zorder(void *frontend, int top)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Refresh the window in response to a server-side request.
+ */
+void refresh_window(void *frontend)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Maximise or restore the window in response to a server-side
+ * request.
+ */
+void set_zoomed(void *frontend, int zoomed)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Report whether the window is iconic, for terminal reports.
+ */
+int is_iconic(void *frontend)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    return NO;                                /* FIXME */
+}
+
+/*
+ * Report the window's position, for terminal reports.
+ */
+void get_window_pos(void *frontend, int *x, int *y)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Report the window's pixel size, for terminal reports.
+ */
+void get_window_pixels(void *frontend, int *x, int *y)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+/*
+ * Return the window or icon title.
+ */
+char *get_window_title(void *frontend, int icon)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    return NULL; /* FIXME */
+}
+
+void set_title(void *frontend, char *title)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void set_icon(void *frontend, char *title)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void set_sbar(void *frontend, int total, int start, int page)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void get_clip(void *frontend, wchar_t ** p, int *len)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void request_paste(void *frontend)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void set_raw_mouse_mode(void *frontend, int activate)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void request_resize(void *frontend, int w, int h)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+}
+
+void sys_cursor(void *frontend, int x, int y)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /*
+     * This is probably meaningless under OS X. FIXME: find out for
+     * sure.
+     */
+}
+
+void logevent(void *frontend, const char *string)
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    /* FIXME */
+printf("logevent: %s\n", string);
+}
+
+int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
+{
+    //SessionWindow *win = (SessionWindow *)frontend;
+    return 1; /* FIXME */
+}
+
+void set_busy_status(void *frontend, int status)
+{
+    /*
+     * We need do nothing here: the OS X `application is busy'
+     * beachball pointer appears _automatically_ when the
+     * application isn't responding to GUI messages.
+     */
+}
index 1860b5f..17a63a6 100755 (executable)
@@ -92,8 +92,8 @@ while (<IN>) {
     if ($groups{$i}) {
       foreach $j (@{$groups{$i}}) { unshift @objs, $j; }
     } elsif (($i eq "[G]" or $i eq "[C]" or $i eq "[M]" or
-              $i eq "[X]" or $i eq "[U]") and defined $prog) {
-      $type = substr($i,1,1);
+              $i eq "[X]" or $i eq "[U]" or $i eq "[MX]") and defined $prog) {
+      $type = substr($i,1,(length $i)-2);
     } else {
       push @$listref, $i;
     }
@@ -122,7 +122,8 @@ foreach $i (@prognames) {
           sort @{$programs{$i}};
   $programs{$i} = [@list];
   foreach $j (@list) {
-    # Dependencies for "x" start with "x.c".
+    # Dependencies for "x" start with "x.c" or "x.m" (depending on
+    # which one exists).
     # Dependencies for "x.res" start with "x.rc".
     # Dependencies for "x.rsrc" start with "x.r".
     # Both types of file are pushed on the list of files to scan.
@@ -135,10 +136,9 @@ foreach $i (@prognames) {
       $file = "$1.r";
       $depends{$j} = [$file];
       push @scanlist, $file;
-    } elsif ($j =~ /\.lib$/) {
-      # libraries don't have dependencies
-    } else {
+    } elsif ($j !~ /\./) {
       $file = "$j.c";
+      $file = "$j.m" unless &findfile($file);
       $depends{$j} = [$file];
       push @scanlist, $file;
     }
@@ -209,7 +209,7 @@ sub mfval($) {
     # Returns true if the argument is a known makefile type. Otherwise,
     # prints a warning and returns false;
     if (grep { $type eq $_ }
-       ("vc","vcproj","cygwin","borland","lcc","gtk","mpw")) {
+       ("vc","vcproj","cygwin","borland","lcc","gtk","mpw","osx")) {
            return 1;
        }
     warn "$.:unknown makefile type '$type'\n";
@@ -237,7 +237,9 @@ sub dirpfx {
 
 sub findfile {
   my ($name) = @_;
-  my $dir, $i, $outdir = "";
+  my $dir;
+  my $i;
+  my $outdir = undef;
   unless (defined $findfilecache{$name}) {
     $i = 0;
     foreach $dir (@srcdirs) {
@@ -245,7 +247,7 @@ sub findfile {
       $outdir=~s/^\.\///;
     }
     die "multiple instances of source file $name\n" if $i > 1;
-    $findfilecache{$name} = $outdir . $name;
+    $findfilecache{$name} = (defined $outdir ? $outdir . $name : undef);
   }
   return $findfilecache{$name};
 }
@@ -263,7 +265,7 @@ sub objects {
     } elsif ($i =~ /^(.*)\.lib/) {
       $y = $1;
       ($x = $ltmpl) =~ s/X/$y/;
-    } else {
+    } elsif ($i !~ /\./) {
       ($x = $otmpl) =~ s/X/$i/;
     }
     push @ret, $x if $x ne "";
@@ -271,6 +273,19 @@ sub objects {
   return join " ", @ret;
 }
 
+sub special {
+  my ($prog, $suffix) = @_;
+  my @ret;
+  my ($i, $x, $y);
+  @ret = ();
+  foreach $i (@{$programs{$prog}}) {
+    if (substr($i, (length $i) - (length $suffix)) eq $suffix) {
+      push @ret, $i;
+    }
+  }
+  return (scalar @ret) ? (join " ", @ret) : undef;
+}
+
 sub splitline {
   my ($line, $width, $splitchar) = @_;
   my ($result, $len);
@@ -318,7 +333,7 @@ sub prognames {
   @ret = ();
   foreach $n (@prognames) {
     ($prog, $type) = split ",", $n;
-    push @ret, $n if index($types, $type) >= 0;
+    push @ret, $n if index(":$types:", ":$type:") >= 0;
   }
   return @ret;
 }
@@ -330,7 +345,7 @@ sub progrealnames {
   @ret = ();
   foreach $n (@prognames) {
     ($prog, $type) = split ",", $n;
-    push @ret, $prog if index($types, $type) >= 0;
+    push @ret, $prog if index(":$types:", ":$type:") >= 0;
   }
   return @ret;
 }
@@ -339,7 +354,7 @@ sub manpages {
   my ($types,$suffix) = @_;
 
   # assume that all UNIX programs have a man page
-  if($suffix eq "1" && $types =~ /X/) {
+  if($suffix eq "1" && $types =~ /:X:/) {
     return map("$_.1", &progrealnames($types));
   }
   return ();
@@ -382,9 +397,9 @@ if (defined $makefiles{'cygwin'}) {
     "\n".
     ".SUFFIXES:\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", "X.res.o", undef);
       print &splitline($prog . ".exe: " . $objstr), "\n";
@@ -459,16 +474,16 @@ if (defined $makefiles{'borland'}) {
     &splitline("\tbrcc32 \$(RCFL) -i \$(BCB)\\include -r".
       " -DNO_WINRESRC_H -DWIN32 -D_WIN32 -DWINVER=0x0401 \$*.rc",69)."\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.obj", "X.res", undef);
       print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
       my $ap = ($type eq "G") ? "-aa" : "-ap";
       print "\tilink32 $ap -Gn -L\$(BCB)\\lib \@$prog.rsp\n\n";
     }
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       print $prog, ".rsp: \$(MAKEFILE)\n";
       $objstr = &objects($p, "X.obj", undef, undef);
@@ -539,15 +554,15 @@ if (defined $makefiles{'vc'}) {
       "LFLAGS = /incremental:no /fixed\n".
       "\n".
       "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
        ($prog, $type) = split ",", $p;
        $objstr = &objects($p, "X.obj", "X.res", undef);
        print &splitline("$prog.exe: " . $objstr . " $prog.rsp"), "\n";
        print "\tlink \$(LFLAGS) -out:$prog.exe -map:$prog.map \@$prog.rsp\n\n";
     }
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
        ($prog, $type) = split ",", $p;
        print $prog, ".rsp: \$(MAKEFILE)\n";
        $objstr = &objects($p, "X.obj", "X.res", "X.lib");
@@ -620,7 +635,7 @@ if (defined $makefiles{'vcproj'}) {
     %all_object_deps = map {$_->{obj} => $_->{deps}} @deps;
     # Create the project files
     # Get names of all Windows projects (GUI and console)
-    my @prognames = &prognames("GC");
+    my @prognames = &prognames("G:C");
     foreach $progname (@prognames) {
        create_project(\%all_object_deps, $progname);
     }
@@ -897,9 +912,9 @@ if (defined $makefiles{'gtk'}) {
     ".SUFFIXES:\n".
     "\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_" } &progrealnames("XU"));
+    print &splitline("all:" . join "", map { " $_" } &progrealnames("X:U"));
     print "\n\n";
-    foreach $p (&prognames("XU")) {
+    foreach $p (&prognames("X:U")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.o", undef, undef);
       print &splitline($prog . ": " . $objstr), "\n";
@@ -915,7 +930,7 @@ if (defined $makefiles{'gtk'}) {
     print "\n";
     print $makefile_extra{'gtk'};
     print "\nclean:\n".
-    "\trm -f *.o". (join "", map { " $_" } &progrealnames("XU")) . "\n";
+    "\trm -f *.o". (join "", map { " $_" } &progrealnames("X:U")) . "\n";
     select STDOUT; close OUT;
 }
 
@@ -1084,9 +1099,9 @@ if (defined $makefiles{'lcc'}) {
     "\n".
     "# Get include directory for resource compiler\n".
     "\n";
-    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("GC"));
+    print &splitline("all:" . join "", map { " $_.exe" } &progrealnames("G:C"));
     print "\n\n";
-    foreach $p (&prognames("GC")) {
+    foreach $p (&prognames("G:C")) {
       ($prog, $type) = split ",", $p;
       $objstr = &objects($p, "X.obj", "X.res", undef);
       print &splitline("$prog.exe: " . $objstr ), "\n";
@@ -1116,3 +1131,76 @@ if (defined $makefiles{'lcc'}) {
 
     select STDOUT; close OUT;
 }
+
+if (defined $makefiles{'osx'}) {
+    $dirpfx = &dirpfx($makefiles{'osx'}, "/");
+
+    ##-- Mac OS X makefile
+    open OUT, ">$makefiles{'osx'}"; select OUT;
+    print
+    "# Makefile for $project_name under Mac OS X.\n".
+    "#\n# This file was created by `mkfiles.pl' from the `Recipe' file.\n".
+    "# DO NOT EDIT THIS FILE DIRECTLY; edit Recipe or mkfiles.pl instead.\n";
+    # gcc command line option is -D not /D
+    ($_ = $help) =~ s/=\/D/=-D/gs;
+    print $_;
+    print
+    "CC = \$(TOOLPATH)gcc\n".
+    "\n".
+    &splitline("CFLAGS = -O2 -Wall -Werror -g " .
+              (join " ", map {"-I$dirpfx$_"} @srcdirs))."\n".
+    "MLDFLAGS = -framework Cocoa\n".
+    "ULDFLAGS =\n".
+    &splitline("all:" . join "", map { " $_" } &progrealnames("MX:U")) .
+    "\n" .
+    $makefile_extra{'osx'} .
+    "\n";
+    foreach $p (&prognames("MX")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      $icon = &special($p, ".icns");
+      $infoplist = &special($p, "info.plist");
+      print "${prog}.app:\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents: ${prog}.app\n\tmkdir -p \$\@\n";
+      print "${prog}.app/Contents/MacOS: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+      $targets = "${prog}.app/Contents/MacOS/$prog";
+      if (defined $icon) {
+       print "${prog}.app/Contents/Resources: ${prog}.app/Contents\n\tmkdir -p \$\@\n";
+       print "${prog}.app/Contents/Resources/${prog}.icns: ${prog}.app/Contents/Resources $icon\n\tcp $icon \$\@\n";
+       $targets .= " ${prog}.app/Contents/Resources/${prog}.icns";
+      }
+      if (defined $infoplist) {
+       print "${prog}.app/Contents/Info.plist: ${prog}.app/Contents/Resources $infoplist\n\tcp $infoplist \$\@\n";
+       $targets .= " ${prog}.app/Contents/Info.plist";
+      }
+      $targets .= " \$(${prog}_extra)";
+      print &splitline("${prog}: $targets", 69) . "\n\n";
+      print &splitline("${prog}.app/Contents/MacOS/$prog: ".
+                      "${prog}.app/Contents/MacOS " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC)" . $mw . " \$(MLDFLAGS) -o \$@ " .
+                       $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $p (&prognames("U")) {
+      ($prog, $type) = split ",", $p;
+      $objstr = &objects($p, "X.o", undef, undef);
+      print &splitline($prog . ": " . $objstr), "\n";
+      $libstr = &objects($p, undef, undef, "-lX");
+      print &splitline("\t\$(CC)" . $mw . " \$(ULDFLAGS) -o \$@ " .
+                       $objstr . " $libstr", 69), "\n\n";
+    }
+    foreach $d (&deps("X.o", undef, $dirpfx, "/")) {
+      print &splitline(sprintf("%s: %s", $d->{obj}, join " ", @{$d->{deps}})),
+          "\n";
+      $firstdep = $d->{deps}->[0];
+      if ($firstdep =~ /\.c$/) {
+         print "\t\$(CC) \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) -c \$<\n";
+      } elsif ($firstdep =~ /\.m$/) {
+         print "\t\$(CC) -x objective-c \$(COMPAT) \$(FWHACK) \$(XFLAGS) \$(CFLAGS) -c \$<\n";
+      }
+    }
+    print "\nclean:\n".
+    "\trm -f *.o *.dmg\n".
+    "\trm -rf *.app\n";
+    select STDOUT; close OUT;
+}
index 976f598..1370bfd 100644 (file)
--- a/puttyps.h
+++ b/puttyps.h
@@ -9,6 +9,10 @@
 
 #include "macstuff.h"
 
+#elif defined(MACOSX)
+
+#include "osx.h"
+
 #else
 
 #include "unix.h"