How to prevent Compiled Racket GUI program opening a black terminal window?

I find this problem very strange because it does not behave the same on all OS that I tested.

The problem is following (actually not really a problem, just annoyance):

I compile #lang racket/gui source file with following options:

  • Distribution (to install on other machines)
  • Gracket

(for Windows version I also check Include DLL - or something)

The result is not the same on all machines/OSs.

On Rasperry Pi 4B (both 32 and 64 bit OS), it produces a standalone program, that does not open the black terminal when started -- the behavior I want.

On x86 Linux, some open black terminal, some do not?

on W10 & W11 it invariably opens black terminal.

Is there a way to disable this behavior. I understand that output to terminal is useful for debugging purposes, but once debugging is done this is really just annoying, and possibly confusing to users.

I can only offer a few suggestions. On Windows, there are two reasons why a terminal window will be opened for a GUI application:

  • The application was built in console mode -- based on your message it seems that this is not the case, since you selected "GRacket"
  • The application prints out something (even an empty string or a newline) to the console.

For the second point, note that Racket will print out objects that are created at top-level and not bound to names. For example, the following GUI program will always open a terminal on Windows because Racket will print out the message object:

#lang racket/gui
(define f (new frame% [label "Hello"] [width 100] [height 100]))
(new message% [parent f] [label "Hello World"])
(send f show #t)

Instead, you need to bind the message% to a name:

#lang racket/gui
(define f (new frame% [label "Hello"] [width 100] [height 100]))
(define m (new message% [parent f] [label "Hello World"]))
(send f show #t)

Not sure about Linux, although I believe in the Linux case, whether a console is opened or not depends how the application is launched, not how it is built...

Hope this helps,
Alex.

1 Like

Thanks, it does help. One thing is sure, I do "display" something out during execution of the program, something that I, actually, can dispense with. I should have thought of that! :slightly_frowning_face:
I guess, my confusion was based on why doesn't it open the terminal everywhere, because source is identical for all OS.

Secondly, yes I noticed this object printout (mostly when defining new fonts and colors, because in several instances, I did not bind them), but I got rid of it by wrapping it in functions that end with (void). after that object printouts disappeared. However, your binding solution is much simpler.

As for Linux, you're probably right as well. Say on x86 Mint I start in terminal (can't start any other way - that I know of), and I get the terminal. On rpi I start just by double-click, and I do not get the terminal.

I've run into this issue as well in my GUI app for Windows, Linux, and Mac OS. My solution was to remap stdout and stderr depending on the OS. In Linux and in Mac OS, if the program is launched from a terminal, it will display messages there but otherwise it will not display them. On Windows, it will never display any messages.

It would be nice to see prints on Windows if started from a cmd window, but I'm not sure if that is possible.

(define (remap-port port)
  (cond
    [(eq? (system-type) 'unix) port]
    [(eq? (system-type) 'windows) (open-output-nowhere)]
    [(terminal-port? port) port]
    [else
     (open-output-nowhere)]))

(parameterize ([current-output-port (remap-port (current-output-port))]
               [current-error-port  (remap-port (current-error-port))])
   <start GUI here>)

Thanks, with this, I do not have to alter my code at all.

If you remap the output ports to open-output-nowhere than any errors in your program will no longer be printed anywhere and they will be very difficult to diagnose, since the GUI program will just appear to hang or not respond to commands.

If you remap the ports, you'll also need to install your own error-display-handler to log any exceptions and errors into an application specific log file (which you'll need to manage yourself).

I think it is much simpler just to make sure nothing is printed out in your GUI application -- this is the strategy I use for my own applications, and it makes it faster to detect and diagnose errors when they happen.

Alex.

That is a fair point. Perhaps it would be better not to remap current-error-port. I develop on Linux so this has never been an issue for me. I just didn't want to remove all of my debug output in the terminal just to prevent the console from appearing on windows and mac os.

I think that I can safely remap output and error ports in my program (math-quiz for grade 1 - actually written for my daughter). The program is now very stable, and even so, the few nasty errors that I had to deal with while writing it, had to do with tail calls running wild. In that case, there is no error output anyway, the program just seems frozen.

As for printing out objects that are created at top level, and not bound. This was not exactly may case.

My annoying problem was following:

;; red
(define style-delta-red (make-object style-delta% 
                          'change-normal-color))
;; black
(define style-delta-black (make-object style-delta% 
                            'change-normal-color))
;; blue
(define style-delta-blue (make-object style-delta% 
                           'change-normal-color))
;; green
(define style-delta-green (make-object style-delta% 
                            'change-normal-color))

(define (silence-colors)
  (send style-delta-red set-delta-foreground "red")
  (send style-delta-black set-delta-foreground "black")
  (send style-delta-blue set-delta-foreground "blue")
  (send style-delta-green set-delta-foreground "Forest Green")
  (void))
;; silence the object notifications
(silence-colors)

Each send was printing something about "object", so finally I wrapped sends in a function.

Another option is to use void

#lang racket/gui
(define f (new frame% [label "Hello"] [width 100] [height 100]))
(void (new message% [parent f] [label "Hello World"]))
(send f show #t)

or let

#lang racket/gui
(define f (new frame% [label "Hello"] [width 100] [height 100]))
(let ()
  (new message% [parent f] [label "Hello World"]))
(send f show #t)

but begin does not work because the block get spliced

#lang racket/gui
(define f (new frame% [label "Hello"] [width 100] [height 100]))
(begin
  (new message% [parent f] [label "Hello World"])) ;  the result is printed anyway :(
(send f show #t)

Use simple functions like fg-style to avoid repetition, I also like to (sometimes) use really simple macros like define-fg-styles (depends on whether they can save a lot of repetition).

#lang racket/gui

(define (fg-style name)
  (define s (make-object style-delta% 'change-normal-color))
  (send s set-delta-foreground name)
  s)

(define-syntax-rule (define-fg-styles [name color] ...)
  (define-values (name ...) (values (fg-style color) ...)))
;; or this does the same
;;(define-syntax-rule (define-fg-styles [name color] ...)
;;  (begin (define name (fg-style color)) ...))

(define-fg-styles
  [style-delta-red "red"]
  [style-delta-black "black"]
  [style-delta-blue "blue"]
  [style-delta-green "Forest Green"])

Just a tip to make code like this easier to type.
Of course there are many other ways/options depending on the actual code details.
Also has the nice benefit that less points in the code need to be changed when some part is too noisy printing stuff. (when you don't go the port remapping route)

Thanks... very elegant.

You know what's the downside of all the changes I implemented following instructions received here?
My 6 year old is now complaining: "Where's the black window. I liked it because it was always sending messages to me!" :rofl:

4 Likes