time
Video bugs:
- * Recode windows port
+ * AVI file works on Windows
CD features:
* standard access to the CDAUDIO interface
CD bugs:
+----------------
+| INSTALLATION |
+----------------
+
+You need to move the three files included in this directory:
+ - utilsunx.cpp => src/unix
+ - process.cpp => src/common
+ - process.h => include/wx
PROGRAM=mmboard
-OBJECTS=$(PROGRAM).o
+OBJECTS=mmboard.o mmbman.o
EXTRA_CPPFLAGS= -I$(top_srcdir)/utils/wxMMedia2/lib
-EXTRA_LIBS= ../lib/libwxmmedia2.a
+EXTRA_LIBS= ../lib/libwxmmedia2.a -lesd
# the comment at the end of the next line is needed because otherwise autoconf
# would remove this line completely - it contains a built-in hack to remove
# any VPATH assignment not containing ':'
-VPATH = @PATH_IFS@$(top_srcdir)/utils/wxMMedia2/sample # ':' for autoconf
+VPATH = @PATH_IFS@$(top_srcdir)/utils/wxMMedia2/board # ':' for autoconf
include ../../../src/make.env
.SUFFIXES: .o .cpp .c
.cpp.o:
- $(CC) $(CPPFLAGS) $(EXTRA_CPPFLAGS) -o $@ $<
+ $(CC) -c $(CPPFLAGS) $(EXTRA_CPPFLAGS) -o $@ $<
all: $(PROGRAM)
protected:
wxWindow *m_output_window;
- wxInputStream *m_input_stream;
wxVideoBaseDriver *m_video_driver;
};
{
if (m_video_driver)
delete m_video_driver;
-
- delete m_input_stream;
}
bool MMBoardVideoFile::NeedWindow()
{
m_output_window = window;
m_video_driver->AttachOutput(*window);
+
+ wxSize size;
+ m_video_driver->GetSize(size);
+ window->SetSize(size);
+ // BAD BAD
+ window->GetParent()->GetSizer()->Fit(window->GetParent());
}
void MMBoardVideoFile::Play()
MMBoardTime MMBoardVideoFile::GetLength()
{
MMBoardTime btime;
+ int frameTime;
- btime.seconds = 1;
- btime.minutes = btime.hours = 0;
+ frameTime = (int)( m_video_driver->GetNbFrames() / m_video_driver->GetFrameRate());
+
+ btime.seconds = frameTime % 60;
+ btime.minutes = (frameTime / 60) % 60;
+ btime.hours = frameTime / 3600;
return btime;
}
wxString MMBoardVideoFile::GetStringInformation()
{
- return wxString(wxT("No info"));
+ wxString info;
+
+ info = wxT("Video codec: ");
+ info += m_video_driver->GetMovieCodec() + "\n";
+ info += wxT("Audio codec: ");
+ info += m_video_driver->GetAudioCodec();
+ info += wxString::Format(" Sample rate: %d Channels: %d\n", m_video_driver->GetSampleRate(),
+ m_video_driver->GetBPS());
+ info += wxString::Format(" Frame rate: %.01f", m_video_driver->GetFrameRate());
+ return info;
}
// ----------------------------------------------------------------------------
// We test the OSS (Open Sound System) support.
+#if 0
dev = new wxSoundStreamOSS();
if (dev->GetError() == wxSOUND_NOERR)
caps |= MM_SOUND_OSS;
delete dev;
#endif
+#endif
#ifdef __WIN32__
// We test the Windows sound support.
LIBTARGET=libwxmmedia2
OBJECTS=sndbase.o sndcodec.o sndpcm.o sndcpcm.o sndulaw.o sndfile.o sndoss.o\
- sndaiff.o sndwav.o sndg72x.o \
+ sndesd.o sndaiff.o sndwav.o sndg72x.o \
g711.o g721.o g723_24.o g723_40.o g72x.o \
cdbase.o cdunix.o \
vidbase.o vidxanm.o
{
}
+wxString wxSoundAiff::GetCodecName() const
+{
+ return "wxSoundAiff codec";
+}
+
bool wxSoundAiff::CanRead()
{
wxUint32 signature1, signature2, len;
class wxSoundAiff: public wxSoundFileStream {
public:
- wxSoundAiff(wxInputStream& stream, wxSoundStream& io_sound);
- wxSoundAiff(wxOutputStream& stream, wxSoundStream& io_sound);
- ~wxSoundAiff();
-
- bool CanRead();
-
+ wxSoundAiff(wxInputStream& stream, wxSoundStream& io_sound);
+ wxSoundAiff(wxOutputStream& stream, wxSoundStream& io_sound);
+ ~wxSoundAiff();
+
+ bool CanRead();
+ wxString GetCodecName() const;
+
protected:
- bool PrepareToPlay();
- bool PrepareToRecord(unsigned long time);
- bool FinishRecording();
-
- wxUint32 GetData(void *buffer, wxUint32 len);
- wxUint32 PutData(const void *buffer, wxUint32 len);
+ bool PrepareToPlay();
+ bool PrepareToRecord(unsigned long time);
+ bool FinishRecording();
+
+ wxUint32 GetData(void *buffer, wxUint32 len);
+ wxUint32 PutData(const void *buffer, wxUint32 len);
};
#endif
m_prepared = TRUE;
}
+wxString wxSoundFileStream::GetCodecName() const
+{
+ return wxString(wxT("wxSoundFileStream base codec"));
+}
+
wxUint32 wxSoundFileStream::GetLength()
{
if (m_input && !m_prepared && GetError() == wxSOUND_NOERR)
// For this action, you must use wxSoundRouterStream applied to wxSoundFileStream.
bool SetSoundFormat(const wxSoundFormatBase& format);
+ // This function returns the Codec name. This is useful for those who want to build
+ // a player (But also in some other case).
+ virtual wxString GetCodecName() const;
+
// You should use this function to test whether this file codec can read the stream you passed
// to it.
virtual bool CanRead() { return FALSE; }
{
}
+wxString wxSoundWave::GetCodecName() const
+{
+ return wxString(wxT("wxSoundWave codec"));
+}
+
#define FAIL_WITH(condition, err) if (condition) { m_snderror = err; return FALSE; }
bool wxSoundWave::CanRead()
class wxSoundWave: public wxSoundFileStream {
public:
- wxSoundWave(wxInputStream& stream, wxSoundStream& io_sound);
- wxSoundWave(wxOutputStream& stream, wxSoundStream& io_sound);
- ~wxSoundWave();
-
- bool CanRead();
-
+ wxSoundWave(wxInputStream& stream, wxSoundStream& io_sound);
+ wxSoundWave(wxOutputStream& stream, wxSoundStream& io_sound);
+ ~wxSoundWave();
+
+ bool CanRead();
+ wxString GetCodecName() const;
+
protected:
- bool PrepareToPlay();
- bool PrepareToRecord(unsigned long time);
- bool FinishRecording();
-
- wxUint32 GetData(void *buffer, wxUint32 len);
- wxUint32 PutData(const void *buffer, wxUint32 len);
-
- bool HandleOutputPCM(wxDataInputStream& data, wxUint16 channels,
- wxUint32 sample_fq, wxUint32 byte_p_sec,
- wxUint16 byte_p_spl, wxUint16 bits_p_spl);
- bool HandleOutputG721(wxDataInputStream& data, wxUint16 channels,
- wxUint32 sample_fq, wxUint32 byte_p_sec,
- wxUint16 byte_p_spl, wxUint16 bits_p_spl);
- wxSoundFormatBase *HandleInputPCM(wxDataOutputStream& data);
- wxSoundFormatBase *HandleInputG72X(wxDataOutputStream& data);
+ bool PrepareToPlay();
+ bool PrepareToRecord(unsigned long time);
+ bool FinishRecording();
+
+ wxUint32 GetData(void *buffer, wxUint32 len);
+ wxUint32 PutData(const void *buffer, wxUint32 len);
+
+ bool HandleOutputPCM(wxDataInputStream& data, wxUint16 channels,
+ wxUint32 sample_fq, wxUint32 byte_p_sec,
+ wxUint16 byte_p_spl, wxUint16 bits_p_spl);
+ bool HandleOutputG721(wxDataInputStream& data, wxUint16 channels,
+ wxUint32 sample_fq, wxUint32 byte_p_sec,
+ wxUint16 byte_p_spl, wxUint16 bits_p_spl);
+ wxSoundFormatBase *HandleInputPCM(wxDataOutputStream& data);
+ wxSoundFormatBase *HandleInputG72X(wxDataOutputStream& data);
};
#endif
virtual bool Resume() = 0;
// Size management
- virtual bool Resize(wxUint16 w, wxUint16 h) = 0;
+ virtual bool SetSize(wxSize size) = 0;
virtual bool GetSize(wxSize& size) const = 0;
// Test the capability of the driver to handle the specified type
- virtual bool IsCapable(wxVideoType WXUNUSED(v_type)) { return FALSE; }
+ virtual bool IsCapable(wxVideoType WXUNUSED(v_type)) const { return FALSE; }
+
+ // Return the video codec name
+ virtual wxString GetMovieCodec() const = 0;
+ // Return the audio codec name
+ virtual wxString GetAudioCodec() const = 0;
+ // Return misc info about audio
+ virtual wxUint32 GetSampleRate() const = 0;
+ virtual wxUint8 GetChannels() const = 0;
+ virtual wxUint8 GetBPS() const = 0;
+ // Return frame rate
+ virtual double GetFrameRate() const = 0;
+ // Return number of frames
+ virtual wxUint32 GetNbFrames() const = 0;
// Called when the movie finished
virtual void OnFinished() {}
virtual void DetachOutput();
// They return the state of the movie.
- virtual bool IsPaused() = 0;
- virtual bool IsStopped() = 0;
+ virtual bool IsPaused() const = 0;
+ virtual bool IsStopped() const = 0;
};
WXDLLEXPORT wxFrame *wxVideoCreateFrame(wxVideoBaseDriver *vid_drv);
#include <wx/filefn.h>
#include <wx/wfstream.h>
+#include <wx/datstrm.h>
+#include <wx/tokenzr.h>
#define WXMMEDIA_INTERNAL
#include "vidbase.h"
wxVideoXANIM *m_vid_xanim;
};
+class wxVideoXANIMOutput: public wxProcess {
+public:
+ wxVideoXANIMOutput();
+
+ void OnTerminate(int pid, int status);
+
+ bool IsTerminated() const;
+protected:
+ bool m_terminated;
+};
+
// -------------------------------------------------------------------------
-// XAnim video driver (implementation)
+// XAnim video driver (process handling implementation)
wxVideoXANIMProcess::wxVideoXANIMProcess(wxVideoXANIM *xanim)
{
m_vid_xanim->OnFinished();
}
+wxVideoXANIMOutput::wxVideoXANIMOutput()
+ : wxProcess(NULL, TRUE, -1)
+{
+ m_terminated = FALSE;
+}
+
+bool wxVideoXANIMOutput::IsTerminated() const
+{
+ return m_terminated;
+}
+
+void wxVideoXANIMOutput::OnTerminate(int pid, int status)
+{
+ m_terminated = TRUE;
+}
+
+// -------------------------------------------------------------------------
+// XAnim video driver (implementation)
+
wxVideoXANIM::wxVideoXANIM()
: wxVideoBaseDriver()
{
m_xanim_detector = new wxVideoXANIMProcess(this);
m_xanim_started = FALSE;
m_paused = FALSE;
+ m_size[0] = 0;
+ m_size[1] = 0;
m_filename = wxGetTempFileName("vidxa");
m_remove_file = TRUE;
wxFileOutputStream fout(m_filename);
fout << str;
+
+ CollectInfo();
}
wxVideoXANIM::wxVideoXANIM(const wxString& filename)
m_filename = filename;
m_remove_file = FALSE;
+ m_size[0] = 0;
+ m_size[1] = 0;
+
+ CollectInfo();
}
wxVideoXANIM::~wxVideoXANIM()
wxRemoveFile(m_filename);
}
+// -------------------------------------------------------------------------
+// Movie controller
+
bool wxVideoXANIM::Play()
{
if (!m_paused && m_xanim_started)
return TRUE;
}
-bool wxVideoXANIM::Resize(wxUint16 w, wxUint16 h)
+// -------------------------------------------------------------------------
+// Movie size controller
+
+bool wxVideoXANIM::SetSize(wxSize size)
{
if (!m_video_output)
return FALSE;
- m_video_output->SetSize(w, h);
+ m_video_output->SetSize(size.GetWidth(), size.GetHeight());
return FALSE;
}
bool wxVideoXANIM::GetSize(wxSize& size) const
{
- return FALSE;
+ if (m_size[0] == 0)
+ return FALSE;
+ size.Set(m_size[0], m_size[1]);
+ return TRUE;
}
-bool wxVideoXANIM::IsCapable(wxVideoType v_type)
+// -------------------------------------------------------------------------
+// Capabilities of XAnim
+
+bool wxVideoXANIM::IsCapable(wxVideoType v_type) const
{
if (v_type == wxVIDEO_MSAVI || v_type == wxVIDEO_MPEG ||
v_type == wxVIDEO_QT || v_type == wxVIDEO_GIF || v_type == wxVIDEO_JMOV ||
return FALSE;
}
-bool wxVideoXANIM::IsPaused()
+// -------------------------------------------------------------------------
+// Movie state
+
+wxString wxVideoXANIM::GetMovieCodec() const
+{
+ if (m_size[0] == 0)
+ return wxT("");
+ return m_movieCodec;
+}
+
+wxString wxVideoXANIM::GetAudioCodec() const
+{
+ if (m_size[0] == 0)
+ return wxT("");
+ return m_audioCodec;
+}
+
+wxUint32 wxVideoXANIM::GetSampleRate() const
+{
+ if (m_size[0] == 0)
+ return 0;
+ return m_sampleRate;
+}
+
+wxUint8 wxVideoXANIM::GetChannels() const
+{
+ if (m_size[0] == 0)
+ return 0;
+ return m_channels;
+}
+
+wxUint8 wxVideoXANIM::GetBPS() const
+{
+ if (m_size[0] == 0)
+ return 0;
+ return m_bps;
+}
+
+double wxVideoXANIM::GetFrameRate() const
+{
+ if (m_size[0] == 0)
+ return 0.0;
+ return m_frameRate;
+}
+
+wxUint32 wxVideoXANIM::GetNbFrames() const
+{
+ if (m_size[0] == 0)
+ return 0;
+ return m_frames;
+}
+
+
+bool wxVideoXANIM::IsPaused() const
{
return m_paused;
}
-bool wxVideoXANIM::IsStopped()
+bool wxVideoXANIM::IsStopped() const
{
return !m_xanim_started;
}
+// -------------------------------------------------------------------------
+// Output management
+
bool wxVideoXANIM::AttachOutput(wxWindow& out)
{
if (!wxVideoBaseDriver::AttachOutput(out))
return FALSE;
-
+
return TRUE;
}
wxVideoBaseDriver::DetachOutput();
}
+// -------------------------------------------------------------------------
+// Lowlevel XAnim controller
+
bool wxVideoXANIM::SendCommand(const char *command, char **ret,
wxUint32 *size)
{
return TRUE;
}
+bool wxVideoXANIM::CollectInfo()
+{
+ wxVideoXANIMOutput *xanimProcess;
+ wxString xanim_command;
+ wxStringTokenizer tokenizer;
+
+ xanimProcess = new wxVideoXANIMOutput;
+ xanim_command = wxT("xanim +v +Zv -Ae ");
+ xanim_command += m_filename;
+ if (!wxExecute(xanim_command, FALSE, xanimProcess))
+ return FALSE;
+
+ wxInputStream *infoStream = xanimProcess->GetInputStream();
+ wxString totalOutput;
+
+ while (infoStream->LastError() == wxSTREAM_NOERROR) {
+ char line[100];
+
+ infoStream->Read(line, sizeof(line)-1);
+ if (infoStream->LastRead() == 0)
+ break;
+
+ line[infoStream->LastRead()] = 0;
+
+ totalOutput += line;
+ }
+
+ // This is good for everything ... :-)
+ int position = totalOutput.Find(wxT("Video Codec:"));
+
+ totalOutput.Remove(0, position+13);
+
+ position = totalOutput.Find(wxT("depth="));
+ m_movieCodec = totalOutput(0, position);
+
+ totalOutput.Remove(0, position);
+ tokenizer.SetString(totalOutput, "\n\r");
+
+ // the rest of the line
+ wxString token = tokenizer.GetNextToken();
+ unsigned long my_long;
+
+#define GETINT(i) \
+totalOutput.ToULong(&my_long); \
+i = my_long;
+
+ // 'Audio Codec:'
+ totalOutput = tokenizer.GetString();
+ totalOutput.Remove(0, totalOutput.Find(wxT(":"))+2);
+
+ position = totalOutput.Find(wxT("Rate"));
+ m_audioCodec = totalOutput(0, position-1);
+
+ // 'Rate='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ GETINT(m_sampleRate);
+ // 'Chans='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ GETINT(m_channels);
+ // 'Bps='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ GETINT(m_bps);
+ // 'Frame Stats:'
+ tokenizer.Reinit(totalOutput);
+ tokenizer.GetNextToken();
+ totalOutput = tokenizer.GetString();
+ totalOutput.Remove(0, totalOutput.Find(wxT(":"))+2);
+ // 'Size='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ GETINT(m_size[0]);
+ // 'x'
+ totalOutput.Remove(0,1);
+ GETINT(m_size[1]);
+ // 'Frames='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ GETINT(m_frames);
+ // 'avfps='
+ totalOutput.Remove(0, totalOutput.Find(wxT("="))+1);
+ totalOutput.ToDouble(&m_frameRate);
+
+ // We wait for the conclusion
+ while (!xanimProcess->IsTerminated())
+ wxYield();
+
+ delete xanimProcess;
+
+ return TRUE;
+}
+
bool wxVideoXANIM::RestartXANIM()
{
wxString xanim_command;
(xanim_chg_size) ? _T("") : _T(""),
WXSTRINGCAST m_filename);
- // Execute it
+ // Execute it
if (!wxExecute(xanim_command, FALSE, m_xanim_detector))
return FALSE;
wxYield();
}
- m_video_output->SetSize(m_video_output->GetSize());
- // Very useful ! Actually it sends a SETSIZE event to XAnim
+ wxSize vibrato_size;
+
+ vibrato_size = m_video_output->GetSize();
+
+ vibrato_size.SetWidth(vibrato_size.GetWidth()+1);
+ m_video_output->SetSize(vibrato_size);
+ vibrato_size.SetWidth(vibrato_size.GetWidth()-1);
+ m_video_output->SetSize(vibrato_size);
+ // Very useful ! Actually it sends a SETSIZE event to XAnim
m_paused = FALSE;
class WXDLLEXPORT wxVideoXANIM : public wxVideoBaseDriver {
DECLARE_DYNAMIC_CLASS(wxVideoXANIM)
-protected:
+ protected:
// Remember the state of the subprocess
bool m_xanim_started, m_paused;
// Pure X11 variables
wxProcess *m_xanim_detector;
// Remember to delete the temporary file when necessary
bool m_remove_file;
-public:
+ wxUint32 m_size[2];
+ wxUint32 m_sampleRate;
+ wxUint8 m_channels;
+ wxUint8 m_bps;
+ wxUint32 m_frames;
+ double m_frameRate;
+ wxString m_movieCodec, m_audioCodec;
+
+ public:
wxVideoXANIM();
wxVideoXANIM(wxInputStream& str);
wxVideoXANIM(const wxString& filename);
bool Stop();
bool SetVolume(wxUint8 vol);
- bool Resize(wxUint16 w, wxUint16 h);
+ bool SetSize(wxSize size);
bool GetSize(wxSize& size) const;
- bool IsCapable(wxVideoType v_type);
+ // Return the video codec name
+ wxString GetMovieCodec() const;
+ // Return the audio codec name
+ wxString GetAudioCodec() const;
+ // Return misc info about audio
+ wxUint32 GetSampleRate() const;
+ wxUint8 GetChannels() const;
+ wxUint8 GetBPS() const;
+ // Return frame rate
+ double GetFrameRate() const;
+ // Return number of frames in the movie
+ wxUint32 GetNbFrames() const;
+
+ bool IsCapable(wxVideoType v_type) const;
bool AttachOutput(wxWindow& output);
void DetachOutput();
- bool IsPaused();
- bool IsStopped();
+ bool IsPaused() const;
+ bool IsStopped() const;
friend class wxVideoXANIMProcess;
// Send a command to the subprocess
bool SendCommand(const char *command,char **ret = NULL,
wxUint32 *size = NULL);
+
+ // Collect informations from XAnim
+ bool CollectInfo();
};
#endif
--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// Name: process.cpp
+// Purpose: Process termination classes
+// Author: Guilhem Lavaux
+// Modified by: Vadim Zeitlin to check error codes, added Detach() method
+// Created: 24/06/98
+// RCS-ID: $Id$
+// Copyright: (c) Guilhem Lavaux
+// Licence: wxWindows license
+/////////////////////////////////////////////////////////////////////////////
+
+#ifdef __GNUG__
+ #pragma implementation "process.h"
+#endif
+
+// For compilers that support precompilation, includes "wx.h".
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+ #pragma hdrstop
+#endif
+
+#ifndef WX_PRECOMP
+ #include "wx/defs.h"
+#endif
+
+#include "wx/process.h"
+
+IMPLEMENT_DYNAMIC_CLASS(wxProcess, wxEvtHandler)
+IMPLEMENT_DYNAMIC_CLASS(wxProcessEvent, wxEvent)
+
+wxProcess::wxProcess(wxEvtHandler *parent, bool needPipe, int id)
+{
+ if (parent)
+ SetNextHandler(parent);
+
+ m_id = id;
+ m_needPipe = needPipe;
+ m_in_stream = NULL;
+ m_out_stream = NULL;
+}
+
+wxProcess::~wxProcess()
+{
+ if (m_in_stream)
+ delete m_in_stream;
+ if (m_out_stream)
+ delete m_out_stream;
+}
+
+void wxProcess::OnTerminate(int pid, int status)
+{
+ wxProcessEvent event(m_id, pid, status);
+
+ if ( !ProcessEvent(event) )
+ delete this;
+ //else: the object which processed the event is responsible for deleting
+ // us!
+}
+
+void wxProcess::Detach()
+{
+ SetNextHandler(NULL);
+}
+
+void wxProcess::SetPipeStreams(wxInputStream *in_stream, wxOutputStream *out_stream)
+{
+ m_in_stream = in_stream;
+ m_out_stream = out_stream;
+}
+
+wxInputStream *wxProcess::GetInputStream() const
+{
+ return m_in_stream;
+}
+
+wxOutputStream *wxProcess::GetOutputStream() const
+{
+ return m_out_stream;
+}
+
+bool wxProcess::NeedPipe() const
+{
+ return m_needPipe;
+}
--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// Name: process.h
+// Purpose: wxProcess class
+// Author: Guilhem Lavaux
+// Modified by: Vadim Zeitlin to check error codes, added Detach() method
+// Created: 24/06/98
+// RCS-ID: $Id$
+// Copyright: (c) 1998 Guilhem Lavaux
+// Licence: wxWindows license
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WX_PROCESSH__
+#define _WX_PROCESSH__
+
+#ifdef __GNUG__
+ #pragma interface "process.h"
+#endif
+
+#include "wx/defs.h"
+#include "wx/object.h"
+#include "wx/event.h"
+#include "wx/stream.h"
+
+// Process Event handling
+class WXDLLEXPORT wxProcessEvent : public wxEvent
+{
+DECLARE_DYNAMIC_CLASS(wxProcessEvent)
+
+public:
+ wxProcessEvent(int id = 0, int pid = 0, int exitcode = 0) : wxEvent(id)
+ {
+ m_eventType = wxEVT_END_PROCESS;
+ m_pid = pid;
+ m_exitcode = exitcode;
+ }
+
+ // accessors
+ // PID of process which terminated
+ int GetPid() { return m_pid; }
+
+ // the exit code
+ int GetExitCode() { return m_exitcode; }
+
+public:
+ int m_pid, m_exitcode;
+};
+
+// A wxProcess object should be passed to wxExecute - than its OnTerminate()
+// function will be called when the process terminates.
+class WXDLLEXPORT wxProcess : public wxEvtHandler
+{
+DECLARE_DYNAMIC_CLASS(wxProcess)
+
+public:
+ wxProcess(wxEvtHandler *parent = (wxEvtHandler *) NULL, bool needPipe = FALSE, int id = -1);
+ ~wxProcess();
+
+ virtual void OnTerminate(int pid, int status);
+
+ // detach from the parent - should be called by the parent if it's deleted
+ // before the process it started terminates
+ void Detach();
+
+ // Pipe handling
+ wxInputStream *GetInputStream() const;
+ wxOutputStream *GetOutputStream() const;
+
+ // These functions should not be called by the usual user. They are only
+ // intended to be used by wxExecute.
+ // Install pipes
+ void SetPipeStreams(wxInputStream *in_stream, wxOutputStream *out_stream);
+ bool NeedPipe() const;
+
+protected:
+ int m_id;
+ bool m_needPipe;
+ wxInputStream *m_in_stream;
+ wxOutputStream *m_out_stream;
+};
+
+typedef void (wxObject::*wxProcessEventFunction)(wxProcessEvent&);
+
+#define EVT_END_PROCESS(id, func) { wxEVT_END_PROCESS, id, -1, (wxObjectEventFunction) (wxEventFunction) (wxProcessEventFunction) & func, NULL},
+
+#endif
+ // _WX_PROCESSH__
--- /dev/null
+/////////////////////////////////////////////////////////////////////////////
+// Name: utilsunx.cpp
+// Purpose: generic Unix implementation of many wx functions
+// Author: Vadim Zeitlin
+// Id: $Id$
+// Copyright: (c) 1998 Robert Roebling, Vadim Zeitlin
+// Licence: wxWindows licence
+/////////////////////////////////////////////////////////////////////////////
+
+// ============================================================================
+// declarations
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+#include "wx/defs.h"
+#include "wx/string.h"
+
+#include "wx/intl.h"
+#include "wx/log.h"
+
+#include "wx/utils.h"
+#include "wx/process.h"
+#include "wx/thread.h"
+
+#include "wx/stream.h"
+
+#if wxUSE_GUI
+ #include "wx/unix/execute.h"
+#endif
+
+#include <stdarg.h>
+#include <dirent.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <fcntl.h> // for O_WRONLY and friends
+#include <time.h> // nanosleep() and/or usleep()
+#include <ctype.h> // isspace()
+#include <sys/time.h> // needed for FD_SETSIZE
+
+#ifdef HAVE_UNAME
+ #include <sys/utsname.h> // for uname()
+#endif // HAVE_UNAME
+
+// ----------------------------------------------------------------------------
+// conditional compilation
+// ----------------------------------------------------------------------------
+
+// many versions of Unices have this function, but it is not defined in system
+// headers - please add your system here if it is the case for your OS.
+// SunOS < 5.6 (i.e. Solaris < 2.6) and DG-UX are like this.
+#if !defined(HAVE_USLEEP) && \
+ (defined(__SUN__) && !defined(__SunOs_5_6) && \
+ !defined(__SunOs_5_7) && !defined(__SUNPRO_CC)) || \
+ defined(__osf__) || defined(__EMX__)
+ extern "C"
+ {
+ #ifdef __SUN__
+ int usleep(unsigned int usec);
+ #else // !Sun
+ #ifdef __EMX__
+ /* I copied this from the XFree86 diffs. AV. */
+ #define INCL_DOSPROCESS
+ #include <os2.h>
+ inline void usleep(unsigned long delay)
+ {
+ DosSleep(delay ? (delay/1000l) : 1l);
+ }
+ #else // !Sun && !EMX
+ void usleep(unsigned long usec);
+ #endif
+ #endif // Sun/EMX/Something else
+ };
+
+ #define HAVE_USLEEP 1
+#endif // Unices without usleep()
+
+// ============================================================================
+// implementation
+// ============================================================================
+
+// ----------------------------------------------------------------------------
+// sleeping
+// ----------------------------------------------------------------------------
+
+void wxSleep(int nSecs)
+{
+ sleep(nSecs);
+}
+
+void wxUsleep(unsigned long milliseconds)
+{
+#if defined(HAVE_NANOSLEEP)
+ timespec tmReq;
+ tmReq.tv_sec = (time_t)(milliseconds / 1000);
+ tmReq.tv_nsec = (milliseconds % 1000) * 1000 * 1000;
+
+ // we're not interested in remaining time nor in return value
+ (void)nanosleep(&tmReq, (timespec *)NULL);
+#elif defined(HAVE_USLEEP)
+ // uncomment this if you feel brave or if you are sure that your version
+ // of Solaris has a safe usleep() function but please notice that usleep()
+ // is known to lead to crashes in MT programs in Solaris 2.[67] and is not
+ // documented as MT-Safe
+ #if defined(__SUN__) && wxUSE_THREADS
+ #error "usleep() cannot be used in MT programs under Solaris."
+ #endif // Sun
+
+ usleep(milliseconds * 1000); // usleep(3) wants microseconds
+#elif defined(HAVE_SLEEP)
+ // under BeOS sleep() takes seconds (what about other platforms, if any?)
+ sleep(milliseconds * 1000);
+#else // !sleep function
+ #error "usleep() or nanosleep() function required for wxUsleep"
+#endif // sleep function
+}
+
+// ----------------------------------------------------------------------------
+// process management
+// ----------------------------------------------------------------------------
+
+int wxKill(long pid, wxSignal sig)
+{
+ return kill((pid_t)pid, (int)sig);
+}
+
+#define WXEXECUTE_NARGS 127
+
+long wxExecute( const wxString& command, bool sync, wxProcess *process )
+{
+ wxCHECK_MSG( !command.IsEmpty(), 0, wxT("can't exec empty command") );
+
+ int argc = 0;
+ wxChar *argv[WXEXECUTE_NARGS];
+ wxString argument;
+ const wxChar *cptr = command.c_str();
+ wxChar quotechar = wxT('\0'); // is arg quoted?
+ bool escaped = FALSE;
+
+ // split the command line in arguments
+ do
+ {
+ argument=wxT("");
+ quotechar = wxT('\0');
+
+ // eat leading whitespace:
+ while ( wxIsspace(*cptr) )
+ cptr++;
+
+ if ( *cptr == wxT('\'') || *cptr == wxT('"') )
+ quotechar = *cptr++;
+
+ do
+ {
+ if ( *cptr == wxT('\\') && ! escaped )
+ {
+ escaped = TRUE;
+ cptr++;
+ continue;
+ }
+
+ // all other characters:
+ argument += *cptr++;
+ escaped = FALSE;
+
+ // have we reached the end of the argument?
+ if ( (*cptr == quotechar && ! escaped)
+ || (quotechar == wxT('\0') && wxIsspace(*cptr))
+ || *cptr == wxT('\0') )
+ {
+ wxASSERT_MSG( argc < WXEXECUTE_NARGS,
+ wxT("too many arguments in wxExecute") );
+
+ argv[argc] = new wxChar[argument.length() + 1];
+ wxStrcpy(argv[argc], argument.c_str());
+ argc++;
+
+ // if not at end of buffer, swallow last character:
+ if(*cptr)
+ cptr++;
+
+ break; // done with this one, start over
+ }
+ } while(*cptr);
+ } while(*cptr);
+ argv[argc] = NULL;
+
+ // do execute the command
+ long lRc = wxExecute(argv, sync, process);
+
+ // clean up
+ argc = 0;
+ while( argv[argc] )
+ delete [] argv[argc++];
+
+ return lRc;
+}
+
+bool wxShell(const wxString& command)
+{
+ wxString cmd;
+ if ( !!command )
+ cmd.Printf(wxT("xterm -e %s"), command.c_str());
+ else
+ cmd = command;
+
+ return wxExecute(cmd) != 0;
+}
+
+#if wxUSE_GUI
+
+void wxHandleProcessTermination(wxEndProcessData *proc_data)
+{
+ int pid = (proc_data->pid > 0) ? proc_data->pid : -(proc_data->pid);
+
+ // waitpid is POSIX so should be available everywhere, however on older
+ // systems wait() might be used instead in a loop (until the right pid
+ // terminates)
+ int status = 0;
+ int rc;
+
+ // wait for child termination and if waitpid() was interrupted, try again
+ do
+ {
+ rc = waitpid(pid, &status, 0);
+ }
+ while ( rc == -1 && errno == EINTR );
+
+
+ if( rc == -1 || ! (WIFEXITED(status) || WIFSIGNALED(status)) )
+ {
+ wxLogSysError(_("Waiting for subprocess termination failed"));
+ /* AFAIK, this can only happen if something went wrong within
+ wxGTK, i.e. due to a race condition or some serious bug.
+ After having fixed the order of statements in
+ GTK_EndProcessDetector(). (KB)
+ */
+ }
+ else
+ {
+ // notify user about termination if required
+ if (proc_data->process)
+ {
+ proc_data->process->OnTerminate(proc_data->pid,
+ WEXITSTATUS(status));
+ }
+ // clean up
+ if ( proc_data->pid > 0 )
+ {
+ delete proc_data;
+ }
+ else
+ {
+ // wxExecute() will know about it
+ proc_data->exitcode = status;
+
+ proc_data->pid = 0;
+ }
+ }
+}
+
+#endif // wxUSE_GUI
+
+#if wxUSE_GUI
+ #define WXUNUSED_UNLESS_GUI(p) p
+#else
+ #define WXUNUSED_UNLESS_GUI(p)
+#endif
+
+// New wxStream classes to clean up the data when the process terminates
+
+#if wxUSE_GUI
+class wxProcessFileInputStream: public wxInputStream {
+ public:
+ wxProcessFileInputStream(int fd);
+ ~wxProcessFileInputStream();
+
+ protected:
+ size_t OnSysRead(void *buffer, size_t bufsize);
+ off_t OnSysSeek(off_t seek, wxSeekMode mode);
+ off_t OnSysTell() const;
+
+ protected:
+ int m_fd;
+};
+
+class wxProcessFileOutputStream: public wxOutputStream {
+ public:
+ wxProcessFileOutputStream(int fd);
+ ~wxProcessFileOutputStream();
+
+ protected:
+ size_t OnSysWrite(const void *buffer, size_t bufsize);
+ off_t OnSysSeek(off_t seek, wxSeekMode mode);
+ off_t OnSysTell() const;
+
+ protected:
+ int m_fd;
+};
+
+wxProcessFileInputStream::wxProcessFileInputStream(int fd)
+{
+ m_fd = fd;
+}
+
+wxProcessFileInputStream::~wxProcessFileInputStream()
+{
+ close(m_fd);
+}
+
+size_t wxProcessFileInputStream::OnSysRead(void *buffer, size_t bufsize)
+{
+ int ret;
+
+ ret = read(m_fd, buffer, bufsize);
+ m_lasterror = wxSTREAM_NOERROR;
+ if (ret == 0)
+ m_lasterror = wxSTREAM_EOF;
+ if (ret == -1) {
+ m_lasterror = wxSTREAM_READ_ERROR;
+ ret = 0;
+ }
+ return ret;
+}
+
+off_t wxProcessFileInputStream::OnSysSeek(off_t WXUNUSED(seek),
+ wxSeekMode WXUNUSED(mode))
+{
+ return wxInvalidOffset;
+}
+
+off_t wxProcessFileInputStream::OnSysTell() const
+{
+ return wxInvalidOffset;
+}
+
+
+wxProcessFileOutputStream::wxProcessFileOutputStream(int fd)
+{
+ m_fd = fd;
+}
+
+wxProcessFileOutputStream::~wxProcessFileOutputStream()
+{
+ close(m_fd);
+}
+
+size_t wxProcessFileOutputStream::OnSysWrite(const void *buffer, size_t bufsize)
+{
+ int ret;
+
+ ret = write(m_fd, buffer, bufsize);
+ m_lasterror = wxSTREAM_NOERROR;
+ if (ret == -1) {
+ m_lasterror = wxSTREAM_WRITE_ERROR;
+ ret = 0;
+ }
+ return ret;
+}
+
+off_t wxProcessFileOutputStream::OnSysSeek(off_t WXUNUSED(seek),
+ wxSeekMode WXUNUSED(mode))
+{
+ return wxInvalidOffset;
+}
+
+off_t wxProcessFileOutputStream::OnSysTell() const
+{
+ return wxInvalidOffset;
+}
+
+#endif
+
+long wxExecute(wxChar **argv,
+ bool sync,
+ wxProcess * WXUNUSED_UNLESS_GUI(process))
+{
+ wxCHECK_MSG( *argv, 0, wxT("can't exec empty command") );
+
+#if wxUSE_UNICODE
+ int mb_argc = 0;
+ char *mb_argv[WXEXECUTE_NARGS];
+
+ while (argv[mb_argc])
+ {
+ wxWX2MBbuf mb_arg = wxConvertWX2MB(argv[mb_argc]);
+ mb_argv[mb_argc] = strdup(mb_arg);
+ mb_argc++;
+ }
+ mb_argv[mb_argc] = (char *) NULL;
+
+ // this macro will free memory we used above
+ #define ARGS_CLEANUP \
+ for ( mb_argc = 0; mb_argv[mb_argc]; mb_argc++ ) \
+ free(mb_argv[mb_argc])
+#else // ANSI
+ // no need for cleanup
+ #define ARGS_CLEANUP
+
+ wxChar **mb_argv = argv;
+#endif // Unicode/ANSI
+
+#if wxUSE_GUI
+ // create pipes
+ int end_proc_detect[2];
+ if (pipe(end_proc_detect) == -1)
+ {
+ wxLogSysError( _("Pipe creation failed") );
+
+ ARGS_CLEANUP;
+
+ return 0;
+ }
+#endif // wxUSE_GUI
+
+#if wxUSE_GUI
+ int in_pipe[2] = { -1, -1 };
+ int out_pipe[2] = { -1, -1 };
+ // Only asynchronous mode is interresting
+ if (!sync && process && process->NeedPipe())
+ {
+ if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1)
+ {
+ /* Free fds */
+ close(end_proc_detect[0]);
+ close(end_proc_detect[1]);
+ wxLogSysError( _("Pipe creation failed (Console pipes)") );
+
+ ARGS_CLEANUP;
+
+ return 0;
+ }
+ }
+#endif // wxUSE_GUI
+
+ // fork the process
+#ifdef HAVE_VFORK
+ pid_t pid = vfork();
+#else
+ pid_t pid = fork();
+#endif
+ if (pid == -1)
+ {
+#if wxUSE_GUI
+ close(end_proc_detect[0]);
+ close(end_proc_detect[1]);
+ close(in_pipe[0]);
+ close(in_pipe[1]);
+ close(out_pipe[0]);
+ close(out_pipe[1]);
+#endif
+ wxLogSysError( _("Fork failed") );
+
+ ARGS_CLEANUP;
+
+ return 0;
+ }
+ else if (pid == 0)
+ {
+#if wxUSE_GUI
+ // we're in child
+ close(end_proc_detect[0]); // close reading side
+#endif // wxUSE_GUI
+
+ // These three lines close the open file descriptors to to avoid any
+ // input/output which might block the process or irritate the user. If
+ // one wants proper IO for the subprocess, the right thing to do is
+ // to start an xterm executing it.
+ if (sync == 0)
+ {
+ // leave stderr opened, it won't do any hurm
+ for ( int fd = 0; fd < FD_SETSIZE; fd++ )
+ {
+#if wxUSE_GUI
+ if ( fd == end_proc_detect[1] || fd == in_pipe[0] || fd == out_pipe[1] )
+ continue;
+#endif // wxUSE_GUI
+
+ if ( fd != STDERR_FILENO )
+ close(fd);
+ }
+ }
+
+ // Fake a console by duplicating pipes
+#if wxUSE_GUI
+ if (in_pipe[0] != -1) {
+ dup2(in_pipe[0], STDIN_FILENO);
+ dup2(out_pipe[1], STDOUT_FILENO);
+ close(in_pipe[0]);
+ close(out_pipe[1]);
+ }
+#endif // wxUSE_GUI
+
+#if 0
+ close(STDERR_FILENO);
+
+ // some programs complain about stderr not being open, so redirect
+ // them:
+ open("/dev/null", O_RDONLY); // stdin
+ open("/dev/null", O_WRONLY); // stdout
+ open("/dev/null", O_WRONLY); // stderr
+#endif
+
+ execvp (*mb_argv, mb_argv);
+
+ // there is no return after successful exec()
+ wxFprintf(stderr, _("Can't execute '%s'\n"), *argv);
+
+ _exit(-1);
+ }
+ else
+ {
+#if wxUSE_GUI
+ wxEndProcessData *data = new wxEndProcessData;
+
+ ARGS_CLEANUP;
+
+ if ( sync )
+ {
+ wxASSERT_MSG( !process, wxT("wxProcess param ignored for sync exec") );
+ data->process = NULL;
+
+ // sync execution: indicate it by negating the pid
+ data->pid = -pid;
+ data->tag = wxAddProcessCallback(data, end_proc_detect[0]);
+ // we're in parent
+ close(end_proc_detect[1]); // close writing side
+
+ // it will be set to 0 from GTK_EndProcessDetector
+ while (data->pid != 0)
+ wxYield();
+
+ int exitcode = data->exitcode;
+
+ delete data;
+
+ return exitcode;
+ }
+ else
+ {
+ // pipe initialization: construction of the wxStreams
+ if (process && process->NeedPipe()) {
+ // These two streams are relative to this process.
+ wxOutputStream *my_output_stream;
+ wxInputStream *my_input_stream;
+
+ my_output_stream = new wxProcessFileOutputStream(in_pipe[1]);
+ my_input_stream = new wxProcessFileInputStream(out_pipe[0]);
+ close(in_pipe[0]); // close reading side
+ close(out_pipe[1]); // close writing side
+
+ process->SetPipeStreams(my_input_stream, my_output_stream);
+ }
+
+ // async execution, nothing special to do - caller will be
+ // notified about the process termination if process != NULL, data
+ // will be deleted in GTK_EndProcessDetector
+ data->process = process;
+ data->pid = pid;
+ data->tag = wxAddProcessCallback(data, end_proc_detect[0]);
+ // we're in parent
+ close(end_proc_detect[1]); // close writing side
+
+ return pid;
+ }
+#else // !wxUSE_GUI
+ wxASSERT_MSG( sync, wxT("async execution not supported yet") );
+
+ int exitcode = 0;
+ if ( waitpid(pid, &exitcode, 0) == -1 || !WIFEXITED(exitcode) )
+ {
+ wxLogSysError(_("Waiting for subprocess termination failed"));
+ }
+
+ return exitcode;
+#endif // wxUSE_GUI
+ }
+ return 0;
+
+ #undef ARGS_CLEANUP
+}
+
+// ----------------------------------------------------------------------------
+// file and directory functions
+// ----------------------------------------------------------------------------
+
+const wxChar* wxGetHomeDir( wxString *home )
+{
+ *home = wxGetUserHome( wxString() );
+ if ( home->IsEmpty() )
+ *home = wxT("/");
+
+ return home->c_str();
+}
+
+#if wxUSE_UNICODE
+const wxMB2WXbuf wxGetUserHome( const wxString &user )
+#else // just for binary compatibility -- there is no 'const' here
+char *wxGetUserHome( const wxString &user )
+#endif
+{
+ struct passwd *who = (struct passwd *) NULL;
+
+ if ( !user )
+ {
+ wxChar *ptr;
+
+ if ((ptr = wxGetenv(wxT("HOME"))) != NULL)
+ {
+ return ptr;
+ }
+ if ((ptr = wxGetenv(wxT("USER"))) != NULL || (ptr = wxGetenv(wxT("LOGNAME"))) != NULL)
+ {
+ who = getpwnam(wxConvertWX2MB(ptr));
+ }
+
+ // We now make sure the the user exists!
+ if (who == NULL)
+ {
+ who = getpwuid(getuid());
+ }
+ }
+ else
+ {
+ who = getpwnam (user.mb_str());
+ }
+
+ return wxConvertMB2WX(who ? who->pw_dir : 0);
+}
+
+// ----------------------------------------------------------------------------
+// network and user id routines
+// ----------------------------------------------------------------------------
+
+// retrieve either the hostname or FQDN depending on platform (caller must
+// check whether it's one or the other, this is why this function is for
+// private use only)
+static bool wxGetHostNameInternal(wxChar *buf, int sz)
+{
+ wxCHECK_MSG( buf, FALSE, wxT("NULL pointer in wxGetHostNameInternal") );
+
+ *buf = wxT('\0');
+
+ // we're using uname() which is POSIX instead of less standard sysinfo()
+#if defined(HAVE_UNAME)
+ struct utsname uts;
+ bool ok = uname(&uts) != -1;
+ if ( ok )
+ {
+ wxStrncpy(buf, wxConvertMB2WX(uts.nodename), sz - 1);
+ buf[sz] = wxT('\0');
+ }
+#elif defined(HAVE_GETHOSTNAME)
+ bool ok = gethostname(buf, sz) != -1;
+#else // no uname, no gethostname
+ wxFAIL_MSG(wxT("don't know host name for this machine"));
+
+ bool ok = FALSE;
+#endif // uname/gethostname
+
+ if ( !ok )
+ {
+ wxLogSysError(_("Cannot get the hostname"));
+ }
+
+ return ok;
+}
+
+bool wxGetHostName(wxChar *buf, int sz)
+{
+ bool ok = wxGetHostNameInternal(buf, sz);
+
+ if ( ok )
+ {
+ // BSD systems return the FQDN, we only want the hostname, so extract
+ // it (we consider that dots are domain separators)
+ wxChar *dot = wxStrchr(buf, wxT('.'));
+ if ( dot )
+ {
+ // nuke it
+ *dot = wxT('\0');
+ }
+ }
+
+ return ok;
+}
+
+bool wxGetFullHostName(wxChar *buf, int sz)
+{
+ bool ok = wxGetHostNameInternal(buf, sz);
+
+ if ( ok )
+ {
+ if ( !wxStrchr(buf, wxT('.')) )
+ {
+ struct hostent *host = gethostbyname(wxConvertWX2MB(buf));
+ if ( !host )
+ {
+ wxLogSysError(_("Cannot get the official hostname"));
+
+ ok = FALSE;
+ }
+ else
+ {
+ // the canonical name
+ wxStrncpy(buf, wxConvertMB2WX(host->h_name), sz);
+ }
+ }
+ //else: it's already a FQDN (BSD behaves this way)
+ }
+
+ return ok;
+}
+
+bool wxGetUserId(wxChar *buf, int sz)
+{
+ struct passwd *who;
+
+ *buf = wxT('\0');
+ if ((who = getpwuid(getuid ())) != NULL)
+ {
+ wxStrncpy (buf, wxConvertMB2WX(who->pw_name), sz - 1);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+bool wxGetUserName(wxChar *buf, int sz)
+{
+ struct passwd *who;
+
+ *buf = wxT('\0');
+ if ((who = getpwuid (getuid ())) != NULL)
+ {
+ // pw_gecos field in struct passwd is not standard
+#if HAVE_PW_GECOS
+ char *comma = strchr(who->pw_gecos, ',');
+ if (comma)
+ *comma = '\0'; // cut off non-name comment fields
+ wxStrncpy (buf, wxConvertMB2WX(who->pw_gecos), sz - 1);
+#else // !HAVE_PW_GECOS
+ wxStrncpy (buf, wxConvertMB2WX(who->pw_name), sz - 1);
+#endif // HAVE_PW_GECOS/!HAVE_PW_GECOS
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+wxString wxGetOsDescription()
+{
+#ifndef WXWIN_OS_DESCRIPTION
+ #error WXWIN_OS_DESCRIPTION should be defined in config.h by configure
+#else
+ return WXWIN_OS_DESCRIPTION;
+#endif
+}
+
+// ----------------------------------------------------------------------------
+// error and debug output routines (deprecated, use wxLog)
+// ----------------------------------------------------------------------------
+
+void wxDebugMsg( const char *format, ... )
+{
+ va_list ap;
+ va_start( ap, format );
+ vfprintf( stderr, format, ap );
+ fflush( stderr );
+ va_end(ap);
+}
+
+void wxError( const wxString &msg, const wxString &title )
+{
+ wxFprintf( stderr, _("Error ") );
+ if (!title.IsNull()) wxFprintf( stderr, wxT("%s "), WXSTRINGCAST(title) );
+ if (!msg.IsNull()) wxFprintf( stderr, wxT(": %s"), WXSTRINGCAST(msg) );
+ wxFprintf( stderr, wxT(".\n") );
+}
+
+void wxFatalError( const wxString &msg, const wxString &title )
+{
+ wxFprintf( stderr, _("Error ") );
+ if (!title.IsNull()) wxFprintf( stderr, wxT("%s "), WXSTRINGCAST(title) );
+ if (!msg.IsNull()) wxFprintf( stderr, wxT(": %s"), WXSTRINGCAST(msg) );
+ wxFprintf( stderr, wxT(".\n") );
+ exit(3); // the same exit code as for abort()
+}
+