Detect whether macOS app was launched from the command line (Terminal) - macos

I have a GUI macOS app that may also be launched from Terminal, with optional command line arguments.
When launched with arguments, I like to run the app in a "cmdline" mode where I do not display any UI but instead communicate via stdin + stdout only.
I can detect this cmdline mode like this:
BOOL cmdMode = NSProcessInfo.processInfo.arguments.count > 1;
(arg 0 is always the executable's path, so any more args would be manually passed args).
Now, here's the big question:
If the user invokes my app without arguments from Terminal (by invoking the app's executable in Contents/MacOS, i.e. not via the open cmd), I like to also go into the cmdline mode. How do I detect this?
Note: Older OS X versions did pass a "-psn ..." argument that, when not present, could be used to detect a launch from cmdline, but recent macOS versions seem to not pass this argument any more when launching apps from the Finder, so I cannot use that for detection any more.
Update
I realize that I can almost correctly solve this by checking for the presence of certain environment variables:
TERM and PWD are only set when launching the app from Terminal but not from Finder.
However, I also like to be able to tell the difference between being launched directly (executable in Contents/MacOS dir) vs. launched with the open command as I consider the open cmd being equivalent to opening the app via Finder or from another app via Launch Services.
In short, the question might also be: Detect whether an app was launched by Launch Services
For the record, here are the values from environ(). The ones marked with an asterisk are only present when invoked from Terminal.app but not when lanched from Finder:
__CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0
* _=/Applications/Myapp.app/Contents/MacOS/Myapp
Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.laVQnD7IXl/Render
HOME=/Users/username
* LANG=en_US.UTF-8
* LC_ALL=en_US.UTF-8
* LC_CTYPE=UTF-8
LOGNAME=username
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
* PWD=/Users/username
SHELL=/bin/bash
* SHLVL=1
SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.KeHv8KNuuk/Listeners
* TERM_PROGRAM_VERSION=388.1.2
* TERM_PROGRAM=Apple_Terminal
* TERM_SESSION_ID=EF2C59E6-D661-45BE-B7EF-7A0E71158C8D
* TERM=xterm-color
TMPDIR=/var/folders/hm/ycnxcbwx8xl1v7008k8wnpjh0000gn/T/
USER=username
XPC_FLAGS=0x0
XPC_SERVICE_NAME=0
There are, however, no envinment values that are unique to apps launched with Launch Services (such as when double clicked in Finder).

If you want to know what process has executed your program, you could use getppid() to get the parent process ID, then inspect that process to determine whether you were executed by an interactive shell process, or Finder, or launchctl, etc.
/sbin/launchd is PID 1 - if your process's parent PID is 1, you were executed by launchd.
Otherwise, you were executed by another process - probably an interactive shell, or as a subprocess of another process. You can use the KERN_PROCARGS syscall with sysctl() to get the process name by its PID.
You might also want to consider using isatty(STDIN) as well: interactive shells have a TTY, non-interactive shells and other processes won't.

Related

Running AppleScripts through the ScriptMonitor.app Utility

When AppleScripts are run through the system-wide Script Menu, their progress is displayed in the menu bar using the ScriptMonitor applet (located in /System/Library/CoreServices/ScriptMonitor.app, introduced in OS X Yosemite). This allows you to stay aware of running scripts, monitor progress, and easily stop running scripts.
Is it possible to run AppleScripts through the ScriptMonitor from outside the Script Menu, for example from Terminal or from system calls from other applications?
I have tried various permutations of the following commands, all without success:
/System/Library/CoreServices/ScriptMonitor.app/Contents/MacOS/ScriptMonitor /PATH/TO/SCRIPT
or
open -a /System/Library/CoreServices/ScriptMonitor.app --args /PATH/TO/SCRIPT
The reason this would be useful is that there are many helper applications that run AppleScripts in response to events, but tend not to be very good at notifying the user about their success or failure.
So, it turns out this can be done using NSUserScriptTask from the Cocoa frameworks, either as part of a compiled command-line application or through AppleScript/Objective-C (ASObjC).
This solution allows AppleScripts, Automator workflows, and shell scripts to be run from the System ScriptMonitor.app utility.
ASObjC Solution
This handler will run natively on OS X 10.10 Yosemite and later. It takes a single parameter, a string containing the POSIX-style (slash-delimited) path to the script. The script is executed immediately in the background, and no result is returned.
use framework "Foundation"
to runInScriptMonitor(script_path)
set {script_task, url_error} to current application's NSUserScriptTask's alloc()'s initWithURL:(script_path as POSIX file) |error|:(reference)
if url_error is not missing value then error (url_error's localizedDescription as string) number (url_error's code as integer)
script_task's executeWithCompletionHandler:(missing value)
# The following delay was increased due to a system hang on Mojave after installation of Security Update 2020-004 (previously, the delay was 0.05).
delay 10 -- Necessary to allow NSUserScriptTask to be dispatched before execution terminates.
return
end runInScriptMonitor
It is called as follows: runInScriptMonitor("/PATH/TO/FILE")
This allows you to run scripts in ScriptMonitor from within AppleScript. If the call is placed in a wrapper AppleScript, the wrapper script can then be called from the command line using osascript.
Compiled Objective-C Solution
Follow these instructions to create a command-line program that takes the script path as input and runs the script using ScriptMonitor. You must have the Xcode Command Line Tools (or the full Xcode) installed in order to compile the code.
Save the following code as osascriptmonitor.m in the Desktop folder:
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[]) {
if (argc < 2) {
printf("usage: osascriptmonitor /path/to/script\n");
} else {
NSString *script_path = [NSString stringWithUTF8String:argv[1]];
NSUserScriptTask *script_task = [[NSUserScriptTask alloc] initWithURL:[NSURL fileURLWithPath:script_path] error:nil];
[script_task executeWithCompletionHandler:nil];
[NSThread sleepForTimeInterval:60.0f];
}
return 0;
}
Compile the program by running the following commands from Terminal:
cd ~/Desktop
gcc -framework Foundation osascriptmonitor.m -o osascriptmonitor
You will now have an executable file named osascriptmonitor on your Desktop. You can run that program from Terminal, passing the path of the script that you want to run in ScriptMonitor.
Example (replace /PATH/TO/SCRIPT with the path of the script you want to run):
~/Desktop/osascriptmonitor "/PATH/TO/SCRIPT"
If you then move the executable file to /usr/local/bin, you can run the program without having to specify its entire path.
osascriptmonitor "/PATH/TO/SCRIPT"
Edit Jan 3, 2020:
Direct Solution
By happy accident, I stumbled across an undocumented option for osascript that makes the above largely unnecessary for AppleScripts: the -P switch.
Usage: osascript -P "/PATH/TO/SCRIPT"
On its own, this will make the script appear in the menu only if Script Monitor is already running. Script Monitor can be launched ahead of time (or while the script is running) and will quit automatically once all scripts have finished.
The best way to launch Script Monitor is using the -g option:
open -g /System/Library/CoreServices/ScriptMonitor.app
Using this method, it is possible to easily pass arguments to the script in addition to having it appear in Script Monitor.

get X11 window id of terminal from shell

How can I get the window ID of the terminal that my shell script is running in?
Background:
I need to write a script to do something based on which virtual desktop it is running in. To get the virtual desktop (not current desktop, I may change it while the script is running), I need the window ID of the terminal that the script is running in. If the script is not run from a terminal, it should fail with an error.
I know very little of X11, but running the env command in my terminal, I spotted an environment variable WINDOWID, that shows the same number in different tabs of the same terminal, but a different number in another terminal. Perhaps that's what you need?
It does appear to be an Xterm thing (and probably any terminal that mimicks Xterms behaviour. The xterm(1) man page only says:
ENVIRONMENT
Xterm sets several environment variables:
...
WINDOWID
is set to the X window id number of the xterm window.
Some other possibly relevant questions with answers:
https://unix.stackexchange.com/questions/3197/how-to-identify-which-xterm-a-shell-or-process-is-running-in
Get X window id from process in bash

In Bash, how can I tell if I am currently in a terminal

I want to create my own personal logfile that logs not only when I log in and out, but also when I lock/unlock my screen. Kindof like /var/log/wtmp on steroids.
To do this, I decided to run a script when I log into Ubuntu that runs in the background until I quit. My plan to do this is to add the script to .bashrc, using ./startlogging.sh & and in the script I will use trap to catch signals. That's great, except .bashrc gets run every time I open a new terminal, which is not what I want for the logger.
Is there a way to tell in Bash that the current login is a gnome login? Alternatively, is there some sort of .gnomerc I can use to run my script?
Edit: Here is my script:
Edit 2: Removed the script, since it's not related to the question. I will repost my other question, rather than repurpose this one.
Are you looking for a way to detect what type of terminal it is?
Try:
echo $TERM
From Wikipedia:
TERM (Unix-like) - specifies the type of computer terminal or terminal
emulator being used (e.g., vt100 or dumb).
See also: List of Terminal Emulators
for bash use : ~/.bash_logout
that will get executed when you logout, which sounds like what you are trying to do.
Well, for just bash, what you want are .bash_login/.bash_logout in your home directory (rather than .bashrc) These are run whenever a LOGIN shell starts/finishes, which happens any time you log in to a shell (on a tty or console, or via ssh or other network login). These are NOT run for bash processes created to run in terminal windows that you create (as those are not login shells) so won't get run any time you open a new terminal.
The problem is that if you log in with some mechanism that does not involve a terminal (such as gdm running on the console to start a gnome or kde or unity session), then there's no login shell so .bash_login/logout never get run. For that case, the easiest is probably to put something in your .xsessionrc, which will get run every time you start an X session (which happens for any of those GUI environments, regardless of which one you run). Unfortunately, there's no standard script that runs when an X session finishes.

How to get a bash instance with loaded default profiles within objective-C

Problem:
I want to be able to run a bash instance from my cocoa application (OS X) with all the normal profiles loaded (~/.bash_profile, etc). I don't want to load the profiles manually since I want to have a default bash instance that is exactly the same as one you would get by firing terminal. From there, I'd like to retrieve some pre-defined environment variable (Ruby version manager's variables).
What I've tried:
I've already tried some solutions with no success. Let me list them here:
NSTask
system() call
for every solutions I tried to execute "/bin/sh -l" to have a bash instance loaded as the current username... unfortunately it didn't work.
When you run bash as sh, it runs in a compatibility mode where it doesn't read .bash_profile. If you want to run bash then run /bin/bash (or if you want other people to use your application, make sure you account for whatever shell the user has selected.)
You can use the command line option '--login' to tell bash to behave as a login shell.
Classically, a shell would act as a login shell if the basename of its argv[0] started with a dash.
You might be able to get the required effect with:
bash --login -c 'echo $RUBY_VARIABLE_OF_INTEREST'
If you are doing this with popen(), you can read the output from the shell.

Linux equivalent of the DOS "start" command?

I'm writing a ksh script and I have to run a executable at a separate Command Prompt window.
xdg-open is a similar command line app in linux.
see https://superuser.com/questions/38984/linux-equivalent-command-for-open-command-on-mac-windows for details on its use.
I believe you mean something like xterm -e your.sh &
Don't forget the final &
maybe it´s not a seperate window that gets started, but you can run some executables in background using "&"
e.g.
./myexecutable &
means your script will not wait until myexecutable has finished but goes on immediately. maybe this is what you are looking for.
regards
xdg-open is a good equivalent for the MS windows commandline start command:
xdg-open file
opens that file or url with its default application
xdg-open .
opens the currect folder in the default file manager
One of the most useful terminal session programs is screen.
screen -dmS title executable
You can list all your screen sessions by running
screen -ls
And you can connect to your created screen session (also allowing multiple simultaneous/synchronized sessions) by running
screen -x title
This will open up the emulated terminal in the current window where executable is running. You can detach a screen session by pressing C-a C-d, and can reattach as many times as you wish.
If you really want your program started in a new terminal window, you could do something like this:
xterm yourtextmodeprogram
or
gnome-terminal -e yourtextmodeprogram
or
konsole -e mc
Trouble is that you cannot count on a particular terminal emulator being installed, so (again: if you really want to do this) you would need to look for the common ones and then execute the first one encountered.
As Joachim mentioned: The normal way to do this is to background the command (read about shell job control somewhere, if you want to dig deeper).
There are also cases where you want to start a persistent shell, i.e. a shell session which lives on when you close the terminal window. There are two ways to do this:
batch-oriented: nohup command-to-run &
interactive: screen
if you want a new windows, just start a new instance of your terminal application: in kde it's
konsole -e whatever
i'm sure the Gnome terminal has similar options
Some have recommended starting it in the background with &, but beware that that will still send all console output from the application you launch to the terminal you launched it from. Additionally, if you close the initial terminal the program you loaded will end.
If you're using a desktop environment like KDE or GNOME, I'd check the alt+f2 launching apps (gnome-open is the one for GNOME, I don't know the name of the KDE app) and see if you can pass them the command to launch as an argument.
Also, if your intention is to launch a daemon, you should check the nohup documentation.
I used nohup as the following command and it works:
nohup <your command> &
then press enter and enter!
don't forget the last &
for example, I ran a python code listening to port 5000:
nohup python3 -W ignore mycode.py &
then I made sure of running by netstat -tulnp | grep :5000 and it was ok.

Resources