Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

How to detect X?

by blazar (Canon)
on Feb 21, 2005 at 08:51 UTC ( [id://432975]=perlquestion: print w/replies, xml ) Need Help??

blazar has asked for the wisdom of the Perl Monks concerning the following question:

Personally I hate to see questions that are in fact FAQs or answered somewhere not too deep inside the docs, so I hope that this is not one such question. As a disclaimer I've checked perlfaq, PM, CPAN and Google: I have found some info, but not a short, definitive answer.

I would like to write a script that supports both a cmd line UI and a GUI (Tk), and in addition to cmd-line switches to activate one or the other, I'd like to it automatically start in GUI mode if an X server is available on the machine it's being launched.

In particular I gave a peek into the documentation of Tk itself, but it's really huge, and at a first glance I could not find anything relevant.

Also, once I find an answer to my question, I was thinking of organizing my program along these lines:

#!/usr/bin/perl use strict; use warnings; use File::Basename; use FindBin qw/$Bin/; my $name=basename $0; # Code to detect X exec $haveX ? "$Bin/$name.X" : "$Bin/$name.cmd"; __END__
Of course in that case I would put most of the non-UI code into a module and call it from the respective '.X' and '.cmd' versions. Does this sound as a good idea or would you do it differently?

Replies are listed 'Best First'.
Re: How to detect X?
by Corion (Patriarch) on Feb 21, 2005 at 08:55 UTC

    If an X server is available, it can be reached/detected via the %ENV hash:

    if ($ENV{DISPLAY}) { print "Found X server at $ENV{DISPLAY}\n"; };

    Keep in mind that it might be practical if the UI-less variant can be used even when there is an X server available; examples are shell-scripts and pipes...

      If an X server is available, it can be reached/detected via the %ENV hash:
      if ($ENV{DISPLAY}) { print "Found X server at $ENV{DISPLAY}\n"; };
      Yes, I knew, but a random look at the pages I found with google seemed to suggest that this may be highly unreliable, in particular that it may remain set also where not available. I have quickly checked here on a few machines and I have the impression that it is indeed so.
      Keep in mind that it might be practical if the UI-less variant can be used even when there is an X server available; examples are shell-scripts and pipes...
      Indeed this is why I wrote
      I would like to write a script that supports both a cmd line UI and a GUI (Tk), and in addition to cmd-line switches to activate one or the other[...]
      In the first place.
      That would not be very reliable. DISPLAY isn't always set, even if an X server is available. And while you shouldn't, it's not uncommon for an 'DISPLAY=...' line to be found in a .profile (usually because the GUI setup doesn't set DISPLAY properly) - my .profile had such a line for a long time.

      Probably the best way is to just try to launch the GUI version of the application. If it works, it works, if it doesn't, nothing is displayed and you fall back on the non GUI version.

Re: How to detect X?
by Zaxo (Archbishop) on Feb 21, 2005 at 08:58 UTC

    You could simply try the X version first. If the X server fails to appear, exec returns with an error -- the only way exec can return. Then try the command-line version.

    exec "$bin/$name.X", @args; exec "$bin/$name.cmd", @args;

    After Compline,
    Zaxo

      No, that doesn't work. From the manual page:
      It [exec] fails and returns false only if the command does not exist and it is executed directly instead of via your system's command shell (see below).
      Since in this case "$bin/$name.X" must exist, the exec will succeed. The fact that "$bin/$name.X" can't do its job doesn't mean the exec will magically resurrect the program.

      In fact, if you turn warnings on, the lines you give above will generate a warning:

      Since it's a common mistake to use "exec" instead of "system", Perl warns you if there is a following statement which isn't "die", "warn", or "exit" (if "-w" is set - but you always do that).
      You could simply try the X version first. If the X server fails to appear, exec returns with an error -- the only way exec can return. Then try the command-line version.
      This is fundamentally what I had thought of doing myself, even though, really I was rather thinking of putting some minimal Tk code in an eval block instead. Yes: I know that basically that would mean doing the same thing twice, but it seems somewhat cleaner... of course unless there's an even cleaner solution.
Re: How to detect X?
by Courage (Parson) on Feb 21, 2005 at 09:50 UTC
    winfo server:
    E:\sh>perl -MTcl::Tk -we "print map {qq/[$_]/} Tcl::Tk::tkinit->interp +->winfo('server','.')" [Windows][5.0][2195][Win32]

    On X system there will be obvious difference.

    I know that similar call exists in Tk, but can't remember right now, as it is easier done in Tcl::Tk.

      Easier, eh? How about this:
      perl -MTk -we "print tkinit->server"
      Sorry, couldn't resist...
        it was easier for me to find in docs: I remember I saw it somewhere in Tk but had only Tcl/Tk handy.

        In my opinion Tcl::Tk is more Tk than Tk, because first one refers to most recent and up-to-date one.

        addition: as long as Tcl::Tk many compatible with Tk, and based on your post, here are two other ways which indeed work:

        perl -MTcl::Tk -we "print Tcl::Tk::tkinit->server"
        or
        perl -MTcl::Tk=:perlTk -we "print tkinit->server"
Re: How to detect X?
by Tanktalus (Canon) on Feb 21, 2005 at 14:52 UTC

    As has been said many times already - use $ENV{DISPLAY}. If it is set, but there is no actual display at the place pointed to by this environment variable, then the user is set up incorrectly. Lots of applications will fail, not just yours. If it is not set, but there is an actual X server available, it's probably because the user wants to use the text mode version. So don't go looking for it.

    I've seen dual-mode applications before. HP's "sam" comes to mind. Don't worry about it - yours won't be the first to "fail" in this way. Think of it this way: by having DISPLAY set, the user is saying, "I want graphical applications to appear in this location I am specifying." And if that fails, who are you to go display it elsewhere? By having DISPLAY not set, the user is saying, "I don't want any graphical application." Who are you to go and create a graphical display somewhere? This is even worse - since you may create a graphical display as user Z, on user Y's display. Talk about a security hole! (Nevermind the security hole that let you put up the application in the first place - but that's user Y's problem, not user Z's.)

    Using DISPLAY is just the convention with X. Trying to outsmart convention will merely annoy users who are aware of the convention, and are using it. For example, I routinely run applications as "DISPLAY= my_application arg1 arg2". This is because I prefer the text mode of these applications. Or I run "DISPLAY=host:2 my_application arg1 arg2" if I want the application to appear somewhere else (say a VNC session). Don't outsmart me - I'm the human, you're just the program running on the computer. I told you to do something, don't tell me I was wrong and do something else.

    Just my perspective on it :-)

    Update: See italicised text in first paragraph - added for clarification.

      Lots of applications will fail,
      Really? For years, I would set $ENV{DISPLAY} in my profile (so if I would telnet/rsh/rlogin to another box, things would display at my screen - and if I started X somewhere else, DISPLAY would be reset). Yet, I would never had an application fail because of that.

      See, if you don't have X running, X applications will fail. Not because $ENV{DISPLAY} is set, but because X isn't running. Non X-application don't give a shit about $ENV{DISPLAY}. The only thing that could "fail" is an X-application displaying on the wrong screen - but that would only happen if the environment had not setup $ENV{DISPLAY} - and then it would use a default variable instead of being undefined. Ho hum.

      Using DISPLAY is just the convention with X.
      Indeed. The fallacy in your reasoning is that there's also a convention about DISPLAY (namely, it being unset) in a non-X environment. Unfortunally, that isn't true.

        I've clarified the part where you seem to misunderstand my statement.

        Restated, it is that I make no assumptions about where the X server is.

        As for non-X environments: if you're in a non-X environment, and do not want to use an X server, then you should not be running X-based programs. HP's "sam" comes to mind again - if I want to run "sam" without the GUI, I have to unset DISPLAY. Maybe HP isn't the epitome of user-friendly unix-based software. Maybe it is. I don't know. But there is precedent for the OP's request, and it is to follow this convention that you don't seem to follow. Just because one person doesn't follow a convention doesn't mean the convention doesn't exist. Sorta like the leading underscores on "private" functions in perl modules...

        Update: Noticing that you point out that "A implies B" does not mean "B implies A". However, what you seem to misunderstand is that "A represents B". The DISPLAY variable represents the X-based display. And thus, if you set it and it's set incorrectly, then you have an incorrectly set display, and the program cannot figure out whether this was intentional or not, and would properly, IMO, refuse to run. No point in bringing up an ugly TUI if the user's environment says "I want a GUI".

        The fallacy in your reasoning is that there's also a convention about DISPLAY (namely, it being unset) in a non-X environment.
        Depends on what you mean by "non-X environment". Theoretically, any platform could define its own standards/conventions for the use of the DISPLAY variable; X doesn't "own" it. But I don't know of any. Do you? If you're talking about some home-brewed application that uses DISPLAY for its own purposes — well, the author (and users) of such a thing are SOL for potentially conflicting with X. And this is one of the reasons why Global Variables Are Evil. :-)
      As has been said many times already - use $ENV{DISPLAY}. If it is set, but there is no actual display at the place pointed to by this environment variable, then the user is set up incorrectly. Lots of applications will fail, not just yours. If it is not set, but there is an actual X server available, it's probably because the user wants to use the text mode version. So don't go looking for it.
      Thank you for your insightful and informed cmts. I will certainly follow your advice. However what I'm bothered most is having the GUI version fail because there's not actually an X server running even if DISPLAY is set as if there were. In that case still checking for success with an eval() (after checking DISPLAY, that is) would be lightweight enough and seems to be appropriate. Do you see any possible negative drawback with this approach?

        It somewhat depends on your interface. If it's easy to escape from the text version of your program, then it may be ok. I would suggest not trying to buck convention too much, so in the case of a fall-back, pop up a message box something like this:

        +----------------------------------------+ | DISPLAY set incorrectly. Falling back | | to a text interface. | | | | [ OK ] [ CANCEL ] | +----------------------------------------+
        If you provide on-line help, it should describe the use of the DISPLAY variable to bypass this warning (that is, to set it to a valid X server which you're authenticate to use, or to unset it if there is no such valid X server).

        The reason for this is that if I really meant to use the X version of your program, I want a quick and easy way out, so that I can properly set DISPLAY, and go back in. If you default to the "OK" button above, the user which has DISPLAY set incorrectly but is ok with the text interface just hits enter, while the one who wants to reset DISPLAY merely has to hit escape. It's a trivial compromise between all positions, IMO.

        Another nice-to-have option is a commandline option, say "-tui", which bypasses the DISPLAY check (and the above warning) and simply goes straight into the TUI, regardless of the DISPLAY setting. In other words, this is the user setting DISPLAY for general X use, but telling your program specifically that she wants you to ignore DISPLAY and go directly into an text-based interface.

        Along the same lines is a "-gui" option. It means, use DISPLAY, and don't fall back. If DISPLAY doesn't work, just exit with an error.

        For completeness, an "-auto-ui" option which does what you want the whole program to do. It's the default. :-)

Re: How to detect X?
by zentara (Archbishop) on Feb 21, 2005 at 12:41 UTC
    How about a plain old eval?
    #!/usr/bin/perl use warnings; use strict; use Tk; my $mw; my $xtest = eval( 'my $mw = new MainWindow;' ); if($@) { chomp($@); print "X error: '$@'\n"; } else { print "X ok\n"; } MainLoop;

    I'm not really a human, but I play one on earth. flash japh
      How about a plain old eval?
      Indeed: 432984!
         eval really isn't reliable here. The program may very well start in X if X is running, even if it is not called under X.
         IMHO, you really should check for $ENV{DISPLAY}, if X is running and DISPLAY is not set it is really the user's problem, not yours.
         Some gui toolkits have their own internal checks for this stuff, gtk has something like init_check() (wondering if it can run or not) maybe you could find something similar in Tk. This seems too low-level though for whatever the gui toolkit not to have.
         But again, until then, use $ENV{DISPLAY}. If the user doesn't have it set and has X running, i can bet it wouldn't be just your application he would have problems with.

        --
        perl -MLWP::Simple -e'print$_%%\n|,<br> get(q=http://cpan.org/misc/japh=)))'

Re: How to detect X?
by chb (Deacon) on Feb 21, 2005 at 08:58 UTC
    Usually one checks for the environment variable DISPLAY. If its defined and contains a nonempty string, you can assume a x-server is running. It is not completely reliable, AFAIK, but it is a good guess (for example, opening a window can fail even if you have DISPLAY set, on many systems you need additional authentication stuff).
Re: How to detect X?
by halley (Prior) on Feb 21, 2005 at 15:36 UTC
    A module I wrote at work does a rough-approximation of detecting if X11 service is available. It first checks DISPLAY, then it tries to connect a socket to the right port. (Sorry, can't give the whole module.)
    use Socket qw(SOCK_DGRAM SOCK_STREAM SOCK_RAW PF_INET inet_aton sockad +dr_in); use FileHandle; # ... other module junk ... # $spec = display(); # sub display { return $ENV{DISPLAY} || ':0'; } # $bool = scan($hostname, $tcpport); # sub scan { my $host = shift; my $port = shift; my $proto = (getprotobyname('tcp'))[2]; my $fh = new FileHandle(); my $ip = inet_aton($host); return undef if not defined $ip; return undef if not socket($fh, &PF_INET(), &SOCK_STREAM(), $proto); my $saddr = sockaddr_in($port, $ip); return undef if not connect($fh, $saddr); close($fh); return 1; } # $bool = ready(); # sub ready { my $display = display(); my ($host, $screen) = split(/:/, $display); $host ||= 'localhost'; return scan($host, 6000 + int($screen)); }
    There are other situations where the port in question is not so easily computed; these usually relate to tunneling of some sort, such as with (ssh -X) commands. Extend as desired.

    --
    [ e d @ h a l l e y . c c ]

      There are other situations where the port in question is not so easily computed; these usually relate to tunneling of some sort, such as with (ssh -X) commands.

      Not really, just use the $ENV{DISPLAY} like always. Add 6000 to the display number like always. No special action is required for this.

Re: How to detect X?
by bunnyman (Hermit) on Feb 21, 2005 at 15:52 UTC

    A cheap and easy way to see if the X server is running is to just try to connect to it. If the connection works, then X is running (probably) otherwise it is not running. (This would fail if some other program is listening to the same port as X uses.)

    First, you'll need $ENV{DISPLAY}. If it isn't there, then X is not running. The format of DISPLAY is "hostname:display.screen" where display and screen are numbers, and screen is optional. If hostname is not there, it defaults to localhost. Common settings are "localhost:0" and "localhost:0.0" or just ":0"

    Once you have the hostname and the display number, add 6000 to the display number and attempt to make a TCP connection to that port number. If the connection fails, X is not running. If it works, just disconnect immediately and assume you connected to the X server. (For extra credit, you might try sending data into the connection and see if you get the right reply back.)

    UPDATE: This method does not always work. The user's X server may not be listening on TCP. When DISPLAY=":0.0" then the "most efficient way of communicating to a server on the same machine" should be used. Usually that means UNIX domain sockets, but it is platform dependent. In this case, the TCP connection fails even though there is a running X server.

      you might try sending data into the connection and see if you get the right reply back

      X11::Protocol might simplify this part ;-)

      /J\

      Once you have the hostname and the display number, add 6000 to the display number and attempt to make a TCP connection to that port number. If the connection fails, X is not running.
      If you have setup X-Windows insecurely (that is, let anyone connect to it - you didn't think that "xhost" is much of a security, do you?), you are right. Sane people start their X-Windows server in such a way it isn't listening to port 6000. Or any other port for that matter.

      X-Windows works fine using Unix domain sockets as well. You just can't display an alien application from elsewhere. Which is a good thing. (Though I have no idea whether X-Windows servers on Windows (sic!) machines can do this).

        To further the argument against leaving X listening to a tcp port, I should point out that using a domain socket doesn't exclude the use of remote X apps. It's fairly easy to get them tunneled through ssh connections back to your display. ssh is smart enough to use xauth cookies for you, too.

        mhoward - at - hattmoward.org
      If it isn't there, then X is not running.
      Yes, but that doesn't mean that if it's there, X is running.

      A implies B doesn't mean B implies A.

Re: How to detect X?
by legato (Monk) on Feb 21, 2005 at 17:40 UTC

    You seem to be implying that you'd maintain two versions of your application -- one which runs via Tk, and one which is a console app. This is a lot of extra work. Also, consider that X is running may not be the only appropriate time to use Tk -- Windows, for example, can run Tk apps without an X server available.

    There are two problems here -- detecting whether to use a GUI, and creating an application that is dual-mode.

    Since the former one is relatively straightforward, depending on the OS configuration and detection accuracy you need, let's look closely at the latter.

    Maintaining two entire applications is probably not the best use of time. As soon as you need to make a change, you will have to be duplicating logic -- the potential for error is very high. To address that concern, you'll need to split up your application on the logic/interface boundary.

    There are really two approaches. First, if your application is simple, you can abstract all of your interface functions (print a message, prompt the user, etc.) into subroutines. Then you can have each routine check to see if you've instantiated a MainWindow or not before deciding whether to use the GUI or text interface component.

    The second, and probably better, apprach is to abstract your logic into a module. This module makes events available that a calling program can decide how to handle. Then, you write two pure-interface scripts (one Tk, one console) that present the interface and call the module for logic (and perhaps to poll for events).

    The second is likely better in part because it allows a future maintainer to alter logic (say, to fix a bug) in the module without having to update either interface script. It also allows for easy expansion of interfaces -- since all the logic is in a module, adding a web interface (for example) would be simple.

    Anima Legato
    .oO all things connect through the motion of the mind

      You seem to be implying that you'd maintain two versions of your application -- one which runs via Tk, and one which is a console app. This is a lot of extra work. Also, consider that X is running may not be the only appropriate time to use Tk -- Windows, for example, can run Tk apps without an X server available.

      There are two problems here -- detecting whether to use a GUI, and creating an application that is dual-mode.

      Well, for one thing this won't run under Windows in any case. So that's not a problem. Said this, if you read 432975 more carefully you'll notice that I wrote "[...] in that case I would put most of the non-UI code into a module and call it from the respective '.X' and '.cmd' versions.", which is fundamentally what you suggest yourself.

      Taking into account both this circumstance and the fact that after all it will be a relatively simple application, it most certainly won't be "a lot of extra work".

      The only problem here is detecting X and running a GUI version if it is available or a CLI version otherwise, which doesn't seem to be a choice everybody agrees on, but that's another matter...

      In any case thank you for the feedback!

        Aha, I missed the few key words in your node about calling a module, my apologies. As far as "a lot of extra work", it may be a simple application now, but damned if those things don't grow at the worst possible times. ;-)

        As far as X/CLI, the most reasonable algorithm is:

        my $have_X = (!$opt_forceCLI and $ENV{DISPLAY} and try_Xconnection($ENV{DISPLAY}) or $opt_forceX; exec ($have_X && !$opt_useconsole) ? "$app.gui" : "$app.X";

        In other words, check the DISPLAY and try to connect to the X server; if that fails, use the CLI. (Also makes certain to handle the command line options to force X or CLI). Yes, this isn't 100%. Yes, there are more complex ways to do it.

        In reality, since you are allowing the user to force one or the other via a command-line, you needn't even try to connect to X. With unusual configurations where DISPLAY is set without X running (or vice-versa), the user simply needs to specify a force option on the command line.

        Anima Legato
        .oO all things connect through the motion of the mind

Re: How to detect X?
by jdporter (Paladin) on Feb 21, 2005 at 19:14 UTC
      Depending entirely on the definedness/value of DISPLAY is absolutely the right thing to do. Any scheme to detect the presence of an X server on the process host is wrong, because there is no guarantee that the user running the application is sitting at the console of the process host. (I know this from painful experience. :-) In general, she won't be, although of course statistically it may be likely.
      Depending entirely on the definedness/value of DISPLAY is absolutely the right thing to do. Any scheme to detect the presence of an X server on the process host is wrong, because there is no guarantee that the user running the application is sitting at the console of the process host. (I know this from painful experience. :-) In general, she won't be, although of course statistically it may be likely.
      This seems to be the solution on which the best informed contributors to the thread agree, so I will stick with it, except that since I will have two versions in any case, namely a CLI one and a GUI one, I will still exec() the CLI one if running some minimal Tk code in an eval() block fails.
Re: How to detect X?
by Anonymous Monk on Feb 21, 2005 at 16:22 UTC

    I think that this autodetection is only going to be confusing to the user. Just provide two binaries - xxx-cli and xxx-gui, let the user decide what to run. Otherwise you're following the famous road paved with good intentions.

    But to the actual point of the question - seriously, why not use a simple Tk test stub (although I would actually fork() off a child to run it).

    Once again, though, be warned - people may hate this feature.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://432975]
Approved by Corion
Front-paged by Courage
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (9)
As of 2024-04-23 21:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found