fltk  1.3.5-source
About: FLTK (Fast Light Tool Kit) is a cross-platform C++ GUI toolkit for UNIX/Linux (X11), Microsoft Windows, and MacOS X.
  Fossies Dox: fltk-1.3.5-source.tar.bz2  ("inofficial" and yet experimental doxygen-generated source code documentation)  

ExternalCodeEditor_UNIX.cxx
Go to the documentation of this file.
1 //
2 // "$Id$".
3 //
4 // External code editor management class for Unix
5 //
6 #ifndef WIN32 /* This entire file unix only */
7 
8 #include <errno.h> /* errno */
9 #include <string.h> /* strerror() */
10 #include <sys/types.h> /* stat().. */
11 #include <sys/stat.h>
12 #include <sys/wait.h> /* waitpid().. */
13 #include <fcntl.h> /* open().. */
14 #include <signal.h> /* kill().. */
15 #include <unistd.h>
16 #include <stdlib.h> /* free().. */
17 #include <stdio.h> /* snprintf().. */
18 
19 #include <FL/Fl.H> /* Fl_Timeout_Handler.. */
20 #include <FL/fl_ask.H> /* fl_alert() */
21 
23 
24 extern int G_debug; // defined in fluid.cxx
25 
26 // Static local data
27 static int L_editors_open = 0; // keep track of #editors open
28 static Fl_Timeout_Handler L_update_timer_cb = 0; // app's update timer callback
29 
30 // [Static/Local] See if file exists
31 static int is_file(const char *filename) {
32  struct stat buf;
33  if ( stat(filename, &buf) < 0 ) return(0);
34  return(S_ISREG(buf.st_mode) ? 1 : 0); // regular file?
35 }
36 
37 // [Static/Local] See if dir exists
38 static int is_dir(const char *dirname) {
39  struct stat buf;
40  if ( stat(dirname, &buf) < 0 ) return(0);
41  return(S_ISDIR(buf.st_mode) ? 1 : 0); // a dir?
42 }
43 
44 // CTOR
46  pid_ = -1;
47  filename_ = 0;
48  file_mtime_ = 0;
49  file_size_ = 0;
50 }
51 
52 // DTOR
54  if ( G_debug )
55  printf("ExternalCodeEditor() DTOR CALLED (this=%p, pid=%ld)\n",
56  (void*)this, (long)pid_);
57  close_editor(); // close editor, delete tmp file
58  set_filename(0); // free()s filename
59 }
60 
61 // [Protected] Set the filename. Handles memory allocation/free
62 // If set to NULL, frees memory.
63 //
64 void ExternalCodeEditor::set_filename(const char *val) {
65  if ( filename_ ) free((void*)filename_);
66  filename_ = val ? strdup(val) : 0;
67 }
68 
69 // [Public] Is editor running?
71  return( (pid_ != -1) ? 1 : 0 );
72 }
73 
74 // [Protected] Wait for editor to close
76  if ( G_debug ) printf("close_editor() called: pid=%ld\n", long(pid_));
77  // Wait until editor is closed + reaped
78  while ( is_editing() ) {
79  switch ( reap_editor() ) {
80  case -1: // error
81  fl_alert("Error reaping external editor\n"
82  "pid=%ld file=%s", long(pid_), filename());
83  break;
84  case 0: // process still running
85  switch ( fl_choice("Please close external editor\npid=%ld file=%s",
86  "Force Close", // button 0
87  "Closed", // button 1
88  0, // button 2
89  long(pid_), filename() ) ) {
90  case 0: // Force Close
91  kill_editor();
92  continue;
93  case 1: // Closed? try to reap
94  continue;
95  }
96  break;
97  default: // process reaped
98  return;
99  }
100  }
101 }
102 
103 // [Protected] Kill the running editor (if any)
104 // Kills the editor, reaps the process, and removes the tmp file.
105 // The dtor calls this to ensure no editors remain running when fluid exits.
106 //
108  if ( G_debug ) printf("kill_editor() called: pid=%ld\n", (long)pid_);
109  if ( !is_editing() ) return; // editor not running? return..
110  kill(pid_, SIGTERM); // kill editor
111  int wcount = 0;
112  while ( pid_ != -1 ) { // and wait for it to finish..
113  usleep(100000); // 1/10th sec delay gives editor time to close itself
114  switch (reap_editor()) {
115  case -1: // error
116  fl_alert("Can't seem to close editor of file: %s\n"
117  "waitpid() returned: %s\n"
118  "Please close editor and hit OK",
119  filename(), strerror(errno));
120  continue;
121  case 0: // process still running
122  if ( ++wcount > 3 ) { // retry 3x with 1/10th delay before showing dialog
123  fl_alert("Can't seem to close editor of file: %s\n"
124  "Please close editor and hit OK", filename());
125  }
126  continue;
127  default: // process reaped
128  if ( G_debug )
129  printf("*** REAPED KILLED EXTERNAL EDITOR: PID %ld\n", (long)pid_);
130  pid_ = -1;
131  break;
132  }
133  }
134  return;
135 }
136 
137 // [Public] Handle if file changed since last check, and update records if so.
138 // Load new data into 'code', which caller must free().
139 // If 'force' set, forces reload even if file size/time didn't change.
140 //
141 // Returns:
142 // 0 -- file unchanged or not editing
143 // 1 -- file changed, internal records updated, 'code' has new content
144 // -1 -- error getting file info (strerror() has reason)
145 //
146 int ExternalCodeEditor::handle_changes(const char **code, int force) {
147  code[0] = 0;
148  if ( !is_editing() ) return 0;
149  // Get current time/size info, see if file changed
150  int changed = 0;
151  {
152  struct stat sbuf;
153  if ( stat(filename(), &sbuf) < 0 ) return(-1); // TODO: show fl_alert(), do this in win32 too, adjust func call docs above
154  time_t now_mtime = sbuf.st_mtime;
155  size_t now_size = sbuf.st_size;
156  // OK, now see if file changed; update records if so
157  if ( now_mtime != file_mtime_ ) { changed = 1; file_mtime_ = now_mtime; }
158  if ( now_size != file_size_ ) { changed = 1; file_size_ = now_size; }
159  }
160  // No changes? done
161  if ( !changed && !force ) return 0;
162  // Changes? Load file, and fallthru to close()
163  int fd = open(filename(), O_RDONLY);
164  if ( fd < 0 ) {
165  fl_alert("ERROR: can't open '%s': %s", filename(), strerror(errno));
166  return -1;
167  }
168  int ret = 0;
169  char *buf = (char*)malloc(file_size_ + 1);
170  ssize_t count = read(fd, buf, file_size_);
171  if ( count == -1 ) {
172  fl_alert("ERROR: read() %s: %s", filename(), strerror(errno));
173  free((void*)buf);
174  ret = -1;
175  } else if ( (long)count != (long)file_size_ ) {
176  fl_alert("ERROR: read() failed for %s:\n"
177  "expected %ld bytes, only got %ld",
178  filename(), long(file_size_), long(count));
179  ret = -1;
180  } else {
181  // Success -- file loaded OK
182  buf[count] = '\0';
183  code[0] = buf; // return pointer to allocated buffer
184  ret = 1;
185  }
186  close(fd);
187  return ret;
188 }
189 
190 // [Public] Remove the tmp file (if it exists), and zero out filename/mtime/size
191 // Returns:
192 // -1 -- on error (dialog is posted as to why)
193 // 0 -- no file to remove
194 // 1 -- file was removed
195 //
197  const char *tmpfile = filename();
198  if ( !tmpfile ) return 0;
199  // Filename set? remove (if exists) and zero filename/mtime/size
200  if ( is_file(tmpfile) ) {
201  if ( G_debug ) printf("Removing tmpfile '%s'\n", tmpfile);
202  if ( remove(tmpfile) < 0 ) {
203  fl_alert("WARNING: Can't remove() '%s': %s", tmpfile, strerror(errno));
204  return -1;
205  }
206  }
207  set_filename(0);
208  file_mtime_ = 0;
209  file_size_ = 0;
210  return 1;
211 }
212 
213 // [Static/Public] Return tmpdir name for this fluid instance.
214 // Returns pointer to static memory.
215 //
217  static char dirname[100];
218  snprintf(dirname, sizeof(dirname), "/tmp/.fluid-%ld", (long)getpid());
219  return dirname;
220 }
221 
222 // [Static/Public] Clear the external editor's tempdir
223 // Static so that the main program can call it on exit to clean up.
224 //
226  const char *tmpdir = tmpdir_name();
227  if ( is_dir(tmpdir) ) {
228  if ( G_debug ) printf("Removing tmpdir '%s'\n", tmpdir);
229  if ( rmdir(tmpdir) < 0 ) {
230  fl_alert("WARNING: Can't rmdir() '%s': %s", tmpdir, strerror(errno));
231  }
232  }
233 }
234 
235 // [Protected] Creates temp dir (if doesn't exist) and returns the dirname
236 // as a static string. Returns NULL on error, dialog shows reason.
237 //
239  const char *dirname = tmpdir_name();
240  if ( ! is_dir(dirname) ) {
241  if ( mkdir(dirname, 0777) < 0 ) {
242  fl_alert("can't create directory '%s': %s",
243  dirname, strerror(errno));
244  return NULL;
245  }
246  }
247  return dirname;
248 }
249 
250 // [Protected] Returns temp filename in static buffer.
251 // Returns NULL if can't, posts dialog explaining why.
252 //
254  static char path[512];
255  const char *tmpdir = create_tmpdir();
256  if ( !tmpdir ) return 0;
257  extern const char *code_file_name; // fluid's global
258  const char *ext = code_file_name; // e.g. ".cxx"
259  snprintf(path, sizeof(path), "%s/%p%s", tmpdir, (void*)this, ext);
260  path[sizeof(path)-1] = 0;
261  return path;
262 }
263 
264 // [Static/Local] Save string 'code' to 'filename', returning file's mtime/size
265 // 'code' can be NULL -- writes an empty file if so.
266 // Returns:
267 // 0 on success
268 // -1 on error (posts dialog with reason)
269 //
270 static int save_file(const char *filename, const char *code) {
271  int fd = open(filename, O_WRONLY|O_CREAT, 0666);
272  if ( fd == -1 ) {
273  fl_alert("ERROR: open() '%s': %s", filename, strerror(errno));
274  return -1;
275  }
276  ssize_t clen = strlen(code);
277  ssize_t count = write(fd, code, clen);
278  int ret = 0;
279  if ( count == -1 ) {
280  fl_alert("ERROR: write() '%s': %s", filename, strerror(errno));
281  ret = -1; // fallthru to close()
282  } else if ( count != clen ) {
283  fl_alert("ERROR: write() '%s': wrote only %lu bytes, expected %lu",
284  filename, (unsigned long)count, (unsigned long)clen);
285  ret = -1; // fallthru to close()
286  }
287  close(fd);
288  return(ret);
289 }
290 
291 // [Static/Local] Convert string 's' to array of argv[], useful for execve()
292 // o 's' will be modified (words will be NULL separated)
293 // o argv[] will end up pointing to the words of 's'
294 // o Caller must free argv with: free(argv);
295 //
296 static int make_args(char *s, // string containing words (gets trashed!)
297  int *aargc, // pointer to argc
298  char ***aargv) { // pointer to argv
299  char *ss, **argv;
300  if ((argv=(char**)malloc(sizeof(char*) * (strlen(s)/2)))==NULL) {
301  return -1;
302  }
303  int t;
304  for(t=0; (t==0)?(ss=strtok(s," \t")):(ss=strtok(0," \t")); t++) {
305  argv[t] = ss;
306  }
307  argv[t] = 0;
308  aargv[0] = argv;
309  aargc[0] = t;
310  return(t);
311 }
312 
313 // [Protected] Start editor in background (fork/exec)
314 // Returns:
315 // > 0 on success, leaves editor child process running as 'pid_'
316 // > -1 on error, posts dialog with reason (child exits)
317 //
318 int ExternalCodeEditor::start_editor(const char *editor_cmd,
319  const char *filename) {
320  if ( G_debug ) printf("start_editor() cmd='%s', filename='%s'\n",
321  editor_cmd, filename);
322  char cmd[1024];
323  snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
324  // Fork editor to background..
325  switch ( pid_ = fork() ) {
326  case -1: // error
327  fl_alert("couldn't fork(): %s", strerror(errno));
328  return -1;
329  case 0: { // child
330  // NOTE: OSX wants minimal code between fork/exec, see Apple TN2083
331  int nargs;
332  char **args = 0;
333  make_args(cmd, &nargs, &args);
334  execvp(args[0], args); // run command - doesn't return if succeeds
335  fl_alert("couldn't exec() '%s': %s", cmd, strerror(errno));
336  exit(1);
337  }
338  default: // parent
339  if ( L_editors_open++ == 0 ) // first editor? start timers
340  { start_update_timer(); }
341  if ( G_debug )
342  printf("--- EDITOR STARTED: pid_=%ld #open=%d\n", (long)pid_, L_editors_open);
343  break;
344  }
345  return 0;
346 }
347 
348 // [Public] Try to reap external editor process
349 // Returns:
350 // -2 -- editor not open
351 // -1 -- waitpid() failed (errno has reason)
352 // 0 -- process still running
353 // >0 -- process finished + reaped (value is pid)
354 // Handles removing tmpfile/zeroing file_mtime/file_size
355 //
357  if ( !is_editing() ) return -2;
358  int status = 0;
359  pid_t wpid;
360  switch (wpid = waitpid(pid_, &status, WNOHANG)) {
361  case -1: // waitpid() failed
362  return -1;
363  case 0: // process didn't reap, still running
364  return 0;
365  default: // process reaped
366  remove_tmpfile(); // also zeroes mtime/size
367  pid_ = -1;
368  if ( --L_editors_open <= 0 )
369  { stop_update_timer(); }
370  break;
371  }
372  if ( G_debug )
373  printf("*** EDITOR REAPED: pid=%ld #open=%d\n", long(wpid), L_editors_open);
374  return wpid;
375 }
376 
377 // [Public] Open external editor using 'editor_cmd' to edit 'code'
378 // 'code' contains multiline code to be edited as a temp file.
379 //
380 // Returns:
381 // 0 if succeeds
382 // -1 if can't open editor (already open, etc),
383 // errors were shown to user in a dialog
384 //
385 int ExternalCodeEditor::open_editor(const char *editor_cmd,
386  const char *code) {
387  // Make sure a temp filename exists
388  if ( !filename() ) {
390  if ( !filename() ) return -1;
391  }
392  // See if tmpfile already exists or editor already open
393  if ( is_file(filename()) ) {
394  if ( is_editing() ) {
395  // See if editor recently closed but not reaped; try to reap
396  pid_t wpid = reap_editor();
397  switch (wpid) {
398  case -1: // waitpid() failed
399  fl_alert("ERROR: waitpid() failed: %s\nfile='%s', pid=%ld",
400  strerror(errno), filename(), (long)pid_);
401  return -1;
402  case 0: // process still running
403  fl_alert("Editor Already Open\n file='%s'\n pid=%ld",
404  filename(), (long)pid_);
405  return 0;
406  default: // process reaped, wpid is pid reaped
407  if ( G_debug )
408  printf("*** REAPED EXTERNAL EDITOR: PID %ld\n", (long)wpid);
409  break; // fall thru to open new editor instance
410  }
411  // Reinstate tmp filename (reap_editor() clears it)
413  }
414  }
415  if ( save_file(filename(), code) < 0 ) {
416  return -1; // errors were shown in dialog
417  }
418  // Update mtime/size from closed file
419  struct stat sbuf;
420  if ( stat(filename(), &sbuf) < 0 ) {
421  fl_alert("ERROR: can't stat('%s'): %s", filename(), strerror(errno));
422  return -1;
423  }
424  file_mtime_ = sbuf.st_mtime;
425  file_size_ = sbuf.st_size;
426  if ( start_editor(editor_cmd, filename()) < 0 ) { // open file in external editor
427  if ( G_debug ) printf("Editor failed to start\n");
428  return -1; // errors were shown in dialog
429  }
430  return 0;
431 }
432 
433 // [Public/Static] Start update timer
435  if ( !L_update_timer_cb ) return;
436  if ( G_debug ) printf("--- TIMER: STARTING UPDATES\n");
438 }
439 
440 // [Public/Static] Stop update timer
442  if ( !L_update_timer_cb ) return;
443  if ( G_debug ) printf("--- TIMER: STOPPING UPDATES\n");
445 }
446 
447 // [Public/Static] Set app's external editor update timer callback
448 // This is the app's callback callback we start while editors are open,
449 // and stop when all editors are closed.
450 //
453 }
454 
455 // [Static/Public] See if any external editors are open.
456 // App's timer cb can see if any editors need checking..
457 //
459  return L_editors_open;
460 }
461 
462 #endif /* !WIN32 */
463 //
464 // End of "$Id$".
465 //
Fl.H
buf
static char * buf
Definition: fl_encoding_mac_roman.cxx:76
ExternalCodeEditor::handle_changes
int handle_changes(const char **code, int force=0)
Definition: ExternalCodeEditor_UNIX.cxx:146
ExternalCodeEditor::filename_
const char * filename_
Definition: ExternalCodeEditor_UNIX.h:23
S_ISDIR
#define S_ISDIR(m)
Definition: Fl_File_Icon.cxx:62
ExternalCodeEditor::~ExternalCodeEditor
~ExternalCodeEditor()
Definition: ExternalCodeEditor_UNIX.cxx:53
ExternalCodeEditor::kill_editor
void kill_editor()
Definition: ExternalCodeEditor_UNIX.cxx:107
is_file
static int is_file(const char *filename)
Definition: ExternalCodeEditor_UNIX.cxx:31
fl_ask.H
ExternalCodeEditor::tmp_filename
const char * tmp_filename()
Definition: ExternalCodeEditor_UNIX.cxx:253
ExternalCodeEditor::file_size_
size_t file_size_
Definition: ExternalCodeEditor_UNIX.h:22
free
void free()
ExternalCodeEditor::remove_tmpfile
int remove_tmpfile()
Definition: ExternalCodeEditor_UNIX.cxx:196
NULL
#define NULL
Definition: forms.H:34
ExternalCodeEditor::set_update_timer_callback
static void set_update_timer_callback(Fl_Timeout_Handler)
Definition: ExternalCodeEditor_UNIX.cxx:451
is_dir
static int is_dir(const char *dirname)
Definition: ExternalCodeEditor_UNIX.cxx:38
G_debug
int G_debug
Definition: fluid.cxx:98
ExternalCodeEditor::filename
const char * filename()
Definition: ExternalCodeEditor_UNIX.h:36
ExternalCodeEditor::is_editing
int is_editing()
Definition: ExternalCodeEditor_UNIX.cxx:70
snprintf
#define snprintf
Definition: flstring.h:64
fl_choice
int fl_choice(const char *q, const char *b0, const char *b1, const char *b2,...)
Definition: fl_ask.cxx:459
ExternalCodeEditor::reap_editor
pid_t reap_editor()
Definition: ExternalCodeEditor_UNIX.cxx:356
ExternalCodeEditor::close_editor
void close_editor()
Definition: ExternalCodeEditor_UNIX.cxx:75
ExternalCodeEditor::start_update_timer
static void start_update_timer()
Definition: ExternalCodeEditor_UNIX.cxx:434
L_update_timer_cb
static Fl_Timeout_Handler L_update_timer_cb
Definition: ExternalCodeEditor_UNIX.cxx:28
save_file
static int save_file(const char *filename, const char *code)
Definition: ExternalCodeEditor_UNIX.cxx:270
ExternalCodeEditor::file_mtime_
time_t file_mtime_
Definition: ExternalCodeEditor_UNIX.h:21
filename
static const char * filename
Definition: fluid.cxx:119
ExternalCodeEditor::set_filename
void set_filename(const char *val)
Definition: ExternalCodeEditor_UNIX.cxx:64
ExternalCodeEditor::create_tmpdir
const char * create_tmpdir()
Definition: ExternalCodeEditor_UNIX.cxx:238
ExternalCodeEditor_UNIX.h
cb
static void cb(Fl_Widget *, void *v)
Definition: factory.cxx:937
Fl::remove_timeout
static void remove_timeout(Fl_Timeout_Handler, void *=0)
Definition: Fl.cxx:368
fd
static struct FD * fd
code_file_name
const char * code_file_name
Definition: fluid.cxx:701
ExternalCodeEditor::tmpdir_clear
static void tmpdir_clear()
Definition: ExternalCodeEditor_UNIX.cxx:225
Fl::add_timeout
static void add_timeout(double t, Fl_Timeout_Handler, void *=0)
Definition: Fl.cxx:329
ExternalCodeEditor::ExternalCodeEditor
ExternalCodeEditor()
Definition: ExternalCodeEditor_UNIX.cxx:45
malloc
voidp malloc()
Fl_Timeout_Handler
void(* Fl_Timeout_Handler)(void *data)
Definition: Fl.H:92
L_editors_open
static int L_editors_open
Definition: ExternalCodeEditor_UNIX.cxx:27
ExternalCodeEditor::editors_open
static int editors_open()
Definition: ExternalCodeEditor_UNIX.cxx:458
ExternalCodeEditor::start_editor
int start_editor(const char *cmd, const char *filename)
Definition: ExternalCodeEditor_UNIX.cxx:318
code
Definition: inftrees.h:24
ExternalCodeEditor::open_editor
int open_editor(const char *editor_cmd, const char *code)
Definition: ExternalCodeEditor_UNIX.cxx:385
fl_alert
void fl_alert(const char *,...)
Definition: fl_ask.cxx:361
make_args
static int make_args(char *s, int *aargc, char ***aargv)
Definition: ExternalCodeEditor_UNIX.cxx:296
ExternalCodeEditor::tmpdir_name
static const char * tmpdir_name()
Definition: ExternalCodeEditor_UNIX.cxx:216
ExternalCodeEditor::pid_
int pid_
Definition: ExternalCodeEditor_UNIX.h:20
ExternalCodeEditor::stop_update_timer
static void stop_update_timer()
Definition: ExternalCodeEditor_UNIX.cxx:441