コード例 #1
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 private void PerformSplit(int lines)
 {
     if (lines > 0)
     {
         if (upperWin.IsNull)
         {
             upperWin = Glk.glk_window_open(lowerWin, WinMethod.Above | WinMethod.Fixed,
                                            (uint)lines, WinType.TextGrid, 0);
         }
         else
         {
             Glk.glk_window_set_arrangement(Glk.glk_window_get_parent(upperWin),
                                            WinMethod.Above | WinMethod.Fixed, (uint)lines, winid_t.Null);
         }
     }
     else
     {
         if (!upperWin.IsNull)
         {
             stream_result_t dummy;
             Glk.glk_window_close(upperWin, out dummy);
             upperWin   = winid_t.Null;
             currentWin = lowerWin;
         }
     }
 }
コード例 #2
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        Stream IZMachineIO.OpenRestoreFile()
        {
            frefid_t file = Glk.glk_fileref_create_by_prompt(
                FileUsage.SavedGame | FileUsage.BinaryMode, FileMode.Read, 0);

            return(OpenStream(file, FileMode.Read));
        }
コード例 #3
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        Stream IZMachineIO.OpenAuxiliaryFile(string name, int size, bool writing)
        {
            frefid_t file = Glk.glk_fileref_create_by_name(
                FileUsage.Data | FileUsage.BinaryMode, name, 0);

            return(OpenStream(file, writing ? FileMode.Write : FileMode.Read));
        }
コード例 #4
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        private void RefreshTextStyle()
        {
            Style glkStyle = lastStyle;

            if (forceFixed)
            {
                switch (glkStyle)
                {
                case Style.Normal:
                    glkStyle = Style.Preformatted;     // fixed roman
                    break;

                case Style.Subheader:
                    glkStyle = Style.Header;     // fixed bold
                    break;

                case Style.Emphasized:
                    glkStyle = Style.Note;     // fixed italic
                    break;

                case Style.Alert:
                    glkStyle = Style.User1;     // fixed bold+italic
                    break;
                }
            }

            Glk.glk_set_style(glkStyle);
        }
コード例 #5
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        Stream IZMachineIO.OpenSaveFile(int size)
        {
            frefid_t file = Glk.glk_fileref_create_by_prompt(
                FileUsage.SavedGame | FileUsage.BinaryMode, FileMode.Write, 0);

            return(OpenStream(file, FileMode.Write));
        }
コード例 #6
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 void IZMachineIO.PutTranscriptString(string str)
 {
     if (!transcriptStream.IsNull)
     {
         Glk.glk_put_string_stream(transcriptStream, str);
     }
 }
コード例 #7
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        Stream IZMachineIO.OpenCommandFile(bool writing)
        {
            FileMode mode = writing ? FileMode.Write : FileMode.Read;
            frefid_t file = Glk.glk_fileref_create_by_prompt(
                FileUsage.InputRecord | FileUsage.TextMode, mode, 0);

            return(OpenStream(file, mode));
        }
コード例 #8
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 protected override void Dispose(bool disposing)
 {
     if (!gstr.IsNull)
     {
         stream_result_t dummy;
         Glk.glk_stream_close(gstr, out dummy);
         gstr = strid_t.Null;
     }
 }
コード例 #9
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.PutTranscriptChar(char ch)
        {
            if (ch > 255)
            {
                ch = '?';
            }

            if (!transcriptStream.IsNull)
            {
                Glk.glk_put_char_stream(transcriptStream, (byte)ch);
            }
        }
コード例 #10
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.PutTextRectangle(string[] lines)
        {
            if (lineInputActive)
            {
                Glk.glk_cancel_line_event(currentWin, out canceledLineEvent);
                lineInputActive = false;
            }

            if (currentWin == lowerWin)
            {
                foreach (string str in lines)
                {
                    if (unicode)
                    {
                        Glk.glk_put_string_uni(str);
                    }
                    else
                    {
                        Glk.glk_put_string(str);
                    }
                    Glk.glk_put_char((byte)'\n');
                }
            }
            else
            {
                int oxpos = xpos;

                foreach (string str in lines)
                {
                    Glk.glk_window_move_cursor(upperWin, (uint)oxpos, (uint)ypos);
                    if (unicode)
                    {
                        Glk.glk_put_string_uni(str);
                    }
                    else
                    {
                        Glk.glk_put_string(str);
                    }

                    ypos++;
                    if (ypos >= screenHeight)
                    {
                        ypos = (int)screenHeight - 1;
                    }

                    xpos = oxpos + str.Length;
                    if (xpos >= screenWidth)
                    {
                        xpos = (int)screenWidth - 1;
                    }
                }
            }
        }
コード例 #11
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 public override void Write(byte[] buffer, int offset, int count)
 {
     if (offset == 0)
     {
         Glk.glk_put_buffer_stream(gstr, buffer, (uint)count);
     }
     else
     {
         byte[] temp = new byte[count];
         Array.Copy(buffer, offset, temp, 0, count);
         Glk.glk_put_buffer_stream(gstr, temp, (uint)count);
     }
 }
コード例 #12
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 void IZMachineIO.EraseLine()
 {
     if (currentWin == upperWin)
     {
         uint width, height;
         Glk.glk_window_get_size(upperWin, out width, out height);
         for (int i = xpos; i < width; i++)
         {
             Glk.glk_put_char((byte)' ');
         }
         Glk.glk_window_move_cursor(upperWin, (uint)xpos, (uint)ypos);
     }
 }
コード例 #13
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.PutChar(char ch)
        {
            if (lineInputActive)
            {
                Glk.glk_cancel_line_event(currentWin, out canceledLineEvent);

                lineInputActive = false;
                // glk_cancel_line_event prints a newline
                if (ch == '\n')
                {
                    return;
                }
            }

            if (unicode)
            {
                Glk.glk_put_char_uni((uint)ch);
            }
            else
            {
                byte b;
                encodingChar[0] = ch;
                int result = Encoding.GetEncoding(Glk.LATIN1).GetBytes(encodingChar, 0, 1, encodedBytes, 0);
                if (result != 1)
                {
                    b = (byte)'?';
                }
                else
                {
                    b = encodedBytes[0];
                }
                Glk.glk_put_char(b);
            }

            if (currentWin == upperWin)
            {
                xpos++;
                uint width, height;
                Glk.glk_window_get_size(upperWin, out width, out height);
                if (xpos >= width)
                {
                    xpos = 0;
                    ypos++;
                    if (ypos >= height)
                    {
                        ypos = (int)height - 1;
                    }
                }
            }
        }
コード例 #14
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 public override int Read(byte[] buffer, int offset, int count)
 {
     if (offset == 0)
     {
         return((int)Glk.glk_get_buffer_stream(gstr, buffer, (uint)count));
     }
     else
     {
         byte[] temp   = new byte[count];
         int    actual = (int)Glk.glk_get_buffer_stream(gstr, temp, (uint)count);
         Array.Copy(temp, 0, buffer, offset, actual);
         return(actual);
     }
 }
コード例 #15
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        UnicodeCaps IZMachineIO.CheckUnicode(char ch)
        {
            UnicodeCaps result = 0;

            if (Glk.glk_gestalt(Gestalt.CharOutput, ch) != 0)
            {
                result |= UnicodeCaps.CanPrint;
            }
            if (Glk.glk_gestalt(Gestalt.CharInput, ch) != 0)
            {
                result |= UnicodeCaps.CanInput;
            }
            return(result);
        }
コード例 #16
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        private void UpdateScreenSize()
        {
            if (!upperWin.IsNull)
            {
                uint width, height;
                Glk.glk_window_get_size(upperWin, out width, out height);
                screenWidth = width;

                if (SizeChanged != null)
                {
                    SizeChanged(this, EventArgs.Empty);
                }
            }
        }
コード例 #17
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        private Stream OpenStream(frefid_t fileref, FileMode mode)
        {
            if (fileref.IsNull)
            {
                return(null);
            }

            strid_t gstr = Glk.glk_stream_open_file(fileref, mode, 0);

            if (gstr.IsNull)
            {
                return(null);
            }

            return(new GlkStream(gstr));
        }
コード例 #18
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        public override long Seek(long offset, SeekOrigin origin)
        {
            SeekMode gseek;

            switch (origin)
            {
            case SeekOrigin.Begin: gseek = SeekMode.Start; break;

            case SeekOrigin.Current: gseek = SeekMode.Current; break;

            case SeekOrigin.End: gseek = SeekMode.End; break;

            default: throw new ArgumentOutOfRangeException("origin");
            }
            Glk.glk_stream_set_position(gstr, (int)offset, gseek);
            return(Glk.glk_stream_get_position(gstr));
        }
コード例 #19
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.SelectWindow(short num)
        {
            if (num == 0)
            {
                currentWin = lowerWin;
            }
            else if (num == 1)
            {
                /* work around a bug in some Inform games where the screen is erased,
                 * destroying the split, but the split isn't restored before drawing
                 * the status line. */
                if (upperWin.IsNull)
                {
                    ((IZMachineIO)this).SplitWindow(1);
                }

                currentWin = upperWin;
            }

            Glk.glk_set_window(currentWin);
        }
コード例 #20
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.MoveCursor(short x, short y)
        {
            // convert to Glk coordinates
            x--; y--;

            if (currentWin == upperWin)
            {
                uint width, height;
                Glk.glk_window_get_size(upperWin, out width, out height);

                if (x < 0)
                {
                    xpos = 0;
                }
                else if (x >= width)
                {
                    xpos = (int)width - 1;
                }
                else
                {
                    xpos = x;
                }

                if (y < 0)
                {
                    ypos = 0;
                }
                else if (y >= height)
                {
                    ypos = (int)height - 1;
                }
                else
                {
                    ypos = y;
                }

                Glk.glk_window_move_cursor(upperWin, (uint)xpos, (uint)ypos);
            }
        }
コード例 #21
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.EraseWindow(short num)
        {
            switch (num)
            {
            case 0:
                // lower only
                Glk.glk_window_clear(lowerWin);
                break;

            case 1:
                // upper only
                if (!upperWin.IsNull)
                {
                    Glk.glk_window_clear(upperWin);
                }
                break;

            case -1:
                // erase both and unsplit
                if (!upperWin.IsNull)
                {
                    stream_result_t dummy;
                    Glk.glk_window_close(upperWin, out dummy);
                    upperWin = winid_t.Null;
                }
                goto case -2;

            case -2:
                // erase both but keep split
                if (!upperWin.IsNull)
                {
                    Glk.glk_window_clear(upperWin);
                }
                Glk.glk_window_clear(lowerWin);
                currentWin = lowerWin;
                xpos       = 0; ypos = 0;
                break;
            }
        }
コード例 #22
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.SplitWindow(short lines)
        {
            uint curHeight;

            if (upperWin.IsNull)
            {
                curHeight = 0;
            }
            else
            {
                WinMethod method;
                winid_t   keywin;
                Glk.glk_window_get_arrangement(Glk.glk_window_get_parent(upperWin),
                                               out method, out curHeight, out keywin);
            }

            targetSplit = lines;
            if (lines > curHeight)
            {
                PerformSplit(lines);
            }
        }
コード例 #23
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.PlaySoundSample(ushort number, SoundAction action, byte volume, byte repeats,
                                         SoundFinishedCallback callback)
        {
            switch (action)
            {
            case SoundAction.Prepare:
                Glk.glk_sound_load_hint(number, true);
                break;

            case SoundAction.FinishWith:
                Glk.glk_sound_load_hint(number, false);
                break;

            case SoundAction.Start:
                if (soundChannel.IsNull)
                {
                    soundChannel = Glk.glk_schannel_create(0);
                }

                if (!soundChannel.IsNull)
                {
                    volume = Math.Min(volume, (byte)8);
                    Glk.glk_schannel_set_volume(soundChannel, (uint)(volume << 13));
                    soundCallback = callback;
                    Glk.glk_schannel_play_ext(soundChannel, number, repeats, 1);
                }
                break;

            case SoundAction.Stop:
                if (!soundChannel.IsNull)
                {
                    Glk.glk_schannel_stop(soundChannel);
                    soundChannel  = schanid_t.Null;
                    soundCallback = null;
                }
                break;
            }
        }
コード例 #24
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.PutString(string str)
        {
            if (lineInputActive)
            {
                Glk.glk_cancel_line_event(currentWin, out canceledLineEvent);
                lineInputActive = false;
                // glk_cancel_line_event prints a newline
                if (str.Length > 0 && str[0] == '\n')
                {
                    str = str.Substring(1);
                }
            }

            if (unicode)
            {
                Glk.glk_put_string_uni(str);
            }
            else
            {
                Glk.glk_put_string(str);
            }

            if (currentWin == upperWin)
            {
                xpos += str.Length;
                uint width, height;
                Glk.glk_window_get_size(upperWin, out width, out height);
                while (xpos >= width)
                {
                    xpos -= (int)width;
                    ypos++;
                    if (ypos >= height)
                    {
                        ypos = (int)height - 1;
                    }
                }
            }
        }
コード例 #25
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Glk.glk_exit();
            }

            if (argvStrings != null)
            {
                foreach (IntPtr str in argvStrings)
                {
                    Marshal.FreeHGlobal(str);
                }
                argvStrings = null;
            }

            if (argv != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(argv);
                argv = IntPtr.Zero;
            }

            GC.SuppressFinalize(this);
        }
コード例 #26
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        string IZMachineIO.ReadLine(string initial, int time, TimedInputCallback callback, byte[] terminatingKeys, out byte terminator)
        {
            const int BUFSIZE  = 256;
            IntPtr    buf      = Marshal.AllocHGlobal(unicode ? BUFSIZE * 4 : BUFSIZE);
            Encoding  encoding = unicode ? Encoding.UTF32 : Encoding.GetEncoding(Glk.LATIN1);

            try
            {
                uint initlen = 0;

                if (initial.Length > 0)
                {
                    if (unicode)
                    {
                        Glk.garglk_unput_string_uni(initial);
                    }
                    else
                    {
                        Glk.garglk_unput_string(initial);
                    }

                    byte[] initBytes = encoding.GetBytes(initial);
                    Marshal.Copy(initBytes, 0, buf, initBytes.Length);

                    initlen = (uint)initBytes.Length;
                    if (unicode)
                    {
                        initlen /= 4;
                    }
                }

                if (unicode)
                {
                    Glk.glk_request_line_event_uni(currentWin, buf, BUFSIZE, initlen);
                }
                else
                {
                    Glk.glk_request_line_event(currentWin, buf, BUFSIZE, initlen);
                }
                Glk.glk_request_timer_events((uint)(time * 100));

                KeyCode[] glkTerminators = null;
                if (terminatingKeys != null && terminatingKeys.Length > 0)
                {
                    glkTerminators = GlkKeysFromZSCII(terminatingKeys);
                    Glk.garglk_set_line_terminators(currentWin, glkTerminators, (uint)glkTerminators.Length);
                }

                terminator = 0;

                event_t ev;
                bool    done = false;
                do
                {
                    Glk.glk_select(out ev);

                    switch (ev.type)
                    {
                    case EvType.LineInput:
                        if (ev.win == currentWin)
                        {
                            done = true;
                            if (glkTerminators == null || ev.val2 == 0)
                            {
                                terminator = 13;
                            }
                            else
                            {
                                terminator = GlkKeyToZSCII((KeyCode)ev.val2);
                            }
                        }
                        break;

                    case EvType.Timer:
                        lineInputActive = true;
                        if (callback() == true)
                        {
                            done = true;
                        }
                        else if (!lineInputActive)
                        {
                            // the callback cancelled the line input request to print something...
                            if (unicode)
                            {
                                Glk.glk_request_line_event_uni(currentWin, buf, BUFSIZE, canceledLineEvent.val1);
                            }
                            else
                            {
                                Glk.glk_request_line_event(currentWin, buf, BUFSIZE, canceledLineEvent.val1);
                            }
                            if (glkTerminators != null)
                            {
                                Glk.garglk_set_line_terminators(currentWin, glkTerminators, (uint)glkTerminators.Length);
                            }
                        }
                        break;

                    case EvType.Arrange:
                        UpdateScreenSize();
                        break;

                    case EvType.SoundNotify:
                        SoundNotify();
                        break;
                    }
                }while (!done);

                Glk.glk_request_timer_events(0);
                PerformSplit(targetSplit);

                // convert the string from Latin-1 or UTF-32
                int length = (int)ev.val1;
                if (unicode)
                {
                    length *= 4;
                }
                byte[] bytes = new byte[length];
                Marshal.Copy(buf, bytes, 0, length);
                return(encoding.GetString(bytes));
            }
            finally
            {
                Marshal.FreeHGlobal(buf);
            }
        }
コード例 #27
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
 void IZMachineIO.PutCommand(string command)
 {
     Glk.glk_set_style(Style.Input);
     ((IZMachineIO)this).PutString(command);
     RefreshTextStyle();
 }
コード例 #28
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        void IZMachineIO.SetTextStyle(TextStyle style)
        {
            Style glkStyle;

            switch (style)
            {
            case TextStyle.Roman:
                glkStyle = Style.Normal;
                Glk.garglk_set_reversevideo(false);
                break;

            case TextStyle.Reverse:
                Glk.garglk_set_reversevideo(true);
                return;

            case TextStyle.Bold:
                switch (lastStyle)
                {
                case Style.Normal:
                    glkStyle = Style.Subheader;         // prop bold
                    break;

                case Style.Emphasized:
                    glkStyle = Style.Alert;         // prop bold+italic
                    break;

                case Style.Preformatted:
                    glkStyle = Style.Header;         // fixed bold
                    break;

                case Style.Note:
                    glkStyle = Style.User1;         // fixed bold+italic
                    break;

                default:
                    return;
                }
                break;

            case TextStyle.Italic:
                switch (lastStyle)
                {
                case Style.Normal:
                    glkStyle = Style.Emphasized;         // prop italic
                    break;

                case Style.Subheader:
                    glkStyle = Style.Alert;         // prop bold+italic
                    break;

                case Style.Preformatted:
                    glkStyle = Style.Normal;         // fixed italic
                    break;

                case Style.Header:
                    glkStyle = Style.User1;         // fixed bold+italic
                    break;

                default:
                    return;
                }
                break;

            case TextStyle.FixedPitch:
                switch (lastStyle)
                {
                case Style.Normal:
                    glkStyle = Style.Preformatted;         // fixed roman
                    break;

                case Style.Subheader:
                    glkStyle = Style.Header;         // fixed bold
                    break;

                case Style.Emphasized:
                    glkStyle = Style.Note;         // fixed italic
                    break;

                case Style.Alert:
                    glkStyle = Style.User1;         // fixed bold+italic
                    break;

                default:
                    return;
                }
                break;

            default:
                return;
            }

            lastStyle = glkStyle;
            RefreshTextStyle();
        }
コード例 #29
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        short IZMachineIO.ReadKey(int time, TimedInputCallback callback, CharTranslator translator)
        {
            PerformSplit(targetSplit);

            if (unicode)
            {
                Glk.glk_request_char_event_uni(currentWin);
            }
            else
            {
                Glk.glk_request_char_event(currentWin);
            }

            Glk.glk_request_timer_events((uint)(time * 100));

            event_t ev;
            bool    done   = false;
            short   result = 0;

            do
            {
                Glk.glk_select(out ev);

                switch (ev.type)
                {
                case EvType.CharInput:
                    if (ev.win == currentWin)
                    {
                        if (ev.val1 <= 255 || (unicode && ev.val1 <= 0x10000))
                        {
                            result = translator((char)ev.val1);
                        }
                        else
                        {
                            result = GlkKeyToZSCII((KeyCode)ev.val1);
                        }

                        if (result != 0)
                        {
                            done = true;
                        }
                        else if (unicode)
                        {
                            Glk.glk_request_char_event_uni(currentWin);
                        }
                        else
                        {
                            Glk.glk_request_char_event(currentWin);
                        }
                    }
                    break;

                case EvType.Timer:
                    if (callback() == true)
                    {
                        Glk.glk_cancel_char_event(currentWin);
                        done = true;
                    }
                    break;

                case EvType.Arrange:
                    UpdateScreenSize();
                    break;

                case EvType.SoundNotify:
                    SoundNotify();
                    break;
                }
            }while (!done);

            Glk.glk_request_timer_events(0);

            return(result);
        }
コード例 #30
0
ファイル: GlkIO.cs プロジェクト: hackerlank/zlr
        public GlkIO(string[] args, string storyName)
        {
            // initialize Glk
            // first, add the application's path to the beginning of the arg list
            string[] newArgs = new string[args.Length + 1];
            newArgs[0] = Application.ExecutablePath;
            Array.Copy(args, 0, newArgs, 1, args.Length);
            args = newArgs;

            // now, GarGlk keeps pointers into argv, so we have to copy the args into unmanaged memory
            argv        = Marshal.AllocHGlobal(4 * (args.Length + 1));
            argvStrings = new IntPtr[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                IntPtr str = Marshal.StringToHGlobalAnsi(args[i]);
                argvStrings[i] = str;
                Marshal.WriteIntPtr(argv, 4 * i, str);
            }
            Marshal.WriteIntPtr(argv, 4 * args.Length, IntPtr.Zero);
            Glk.gli_startup(args.Length, argv);

            Glk.garglk_set_program_name("Demona");
            Glk.garglk_set_program_info("Demona by Jesse McGrew\nA Glk interface for ZLR\nVersion " + ZMachine.ZLR_VERSION);
            Glk.garglk_set_story_name(storyName);

            // set style hints
            Glk.glk_stylehint_set(WinType.AllTypes, Style.User1, StyleHint.ReverseColor, 1);

            Glk.glk_stylehint_set(WinType.AllTypes, Style.User2, StyleHint.Weight, 1);
            Glk.glk_stylehint_set(WinType.AllTypes, Style.User2, StyleHint.Proportional, 0);

            // figure out how big the screen is
            winid_t tempWin = Glk.glk_window_open(winid_t.Null, 0, 0, WinType.TextGrid, 0);

            if (tempWin.IsNull)
            {
                screenWidth  = 80;
                screenHeight = 25;
            }
            else
            {
                Glk.glk_window_get_size(tempWin, out screenWidth, out screenHeight);
                stream_result_t dummy;
                Glk.glk_window_close(tempWin, out dummy);
            }

            // open the lower window
            lowerWin = Glk.glk_window_open(winid_t.Null, 0, 0, WinType.TextBuffer, 0);
            if (lowerWin.IsNull)
            {
                throw new Exception("glk_window_open failed");
            }

            Glk.glk_set_window(lowerWin);
            currentWin = lowerWin;

            xpos = 0;
            ypos = 0;

            unicode = (Glk.glk_gestalt(Gestalt.Unicode, 0) != 0);
        }