/*
* wat - get Wayland xdg-activation token
*
* Copyright (C) 2021 Leon Henrik Plickat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#ifdef __linux__
#include<execinfo.h>
#endif
#include <wayland-client.h>
#include "xdg-activation-v1.h"
const char usage[] =
"Usage: wat [options...]\n"
" -h, --help Print this help text and exit.\n"
" --app-id <string> Set the app-id of the token.\n"
" --exec <string> Command to execute.\n"
" --fork If set, fork before executing the command.\n"
"\n";
int ret = EXIT_SUCCESS;
bool loop = true;
bool forkexec = false;
char *app_id = NULL;
char *cmd = NULL;
struct wl_display *wl_display = NULL;
struct wl_registry *wl_registry = NULL;
struct wl_callback *sync_callback = NULL;
struct xdg_activation_v1 *xdg_activation = NULL;
struct xdg_activation_token_v1 *xdg_activation_token = NULL;
static void exec (const char *cmd, const char *token)
{
setenv("XDG_ACTIVATION_TOKEN", token, true);
execl("/bin/sh", "/bin/sh", "-c", cmd, NULL); /* Only returns on error. */
fprintf(stderr, "ERROR: execl: %s\n", strerror(errno));
_exit(EXIT_FAILURE);
}
/* We need to fork two times for UNIXy resons. */
static void second_fork (const char *cmd, const char *token)
{
int ret = fork();
if ( ret == 0 )
exec(cmd, token);
else if ( ret < 0 ) /* Yes, fork can fail. */
{
fprintf(stderr, "ERROR: fork: %s\n", strerror(errno));
_exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
/* We need to fork two times for UNIXy resons. */
static void first_fork (const char *cmd, const char *token)
{
int ret = fork();
if ( ret == 0 )
{
setsid();
second_fork(cmd, token);
}
else if ( ret < 0 ) /* Yes, fork can fail. */
fprintf(stderr, "ERROR: fork: %s\n", strerror(errno));
else
waitpid(ret, NULL, 0);
}
static void xdg_activation_token_handle_done (void *data,
struct xdg_activation_token_v1 *token, const char *token_str)
{
loop = false;
if ( cmd == NULL )
fputs(token_str, stdout);
else if (forkexec)
first_fork(cmd, token_str);
else
exec(cmd, token_str);
}
static const struct xdg_activation_token_v1_listener xdg_activation_token_listener = {
.done = xdg_activation_token_handle_done,
};
static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data)
{
wl_callback_destroy(wl_callback);
sync_callback = NULL;
if ( xdg_activation == NULL )
{
fputs("ERROR: Wayland server does not support xdg-activation-v1.\n", stderr);
ret = EXIT_FAILURE;
loop = false;
return;
}
// TODO set app-id
xdg_activation_token = xdg_activation_v1_get_activation_token(xdg_activation);
xdg_activation_token_v1_add_listener(xdg_activation_token,
&xdg_activation_token_listener, NULL);
if ( app_id != NULL )
xdg_activation_token_v1_set_app_id(xdg_activation_token, app_id);
xdg_activation_token_v1_commit(xdg_activation_token);
}
static const struct wl_callback_listener sync_callback_listener = {
.done = sync_handle_done,
};
static void registry_handle_global (void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
if ( strcmp(interface, xdg_activation_v1_interface.name) == 0 )
xdg_activation = wl_registry_bind(registry, name,
&xdg_activation_v1_interface, version);
}
static void noop () {}
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = noop, /* We don't run long enough to care. */
};
/**
* Intercept error signals (like SIGSEGV and SIGFPE) so that we can try to
* print a fancy error message and a backtracke before letting the system kill us.
*/
static void handle_error (int signum)
{
const char *msg =
"\n"
"┌──────────────────────────────────────────┐\n"
"│ │\n"
"│ wat has crashed. │\n"
"│ │\n"
"│ This is likely a bug, so please │\n"
"│ report this to the mailing list. │\n"
"│ │\n"
"│ ~leon_plickat/public-inbox@lists.sr.ht │\n"
"│ │\n"
"└──────────────────────────────────────────┘\n"
"\n";
fputs(msg, stderr);
#ifdef __linux__
fputs("Attempting to get backtrace:\n", stderr);
/* In some rare cases, getting a backtrace can also cause a segfault.
* There is nothing we can or should do about that. All hope is lost at
* that point.
*/
void *buffer[255];
const int calls = backtrace(buffer, sizeof(buffer) / sizeof(void *));
backtrace_symbols_fd(buffer, calls, fileno(stderr));
fputs("\n", stderr);
#endif
/* Let the default handlers deal with the rest. */
signal(signum, SIG_DFL);
kill(getpid(), signum);
}
/**
* Set up signal handlers.
*/
static void init_signals (void)
{
signal(SIGSEGV, handle_error);
signal(SIGFPE, handle_error);
}
int main(int argc, char *argv[])
{
init_signals();
for (int i = 1; i < argc; i++)
{
if ( strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0 )
{
fputs(usage, stderr);
goto cleanup;
}
else if ( strcmp(argv[i], "--fork") == 0 )
forkexec = true;
else if ( strcmp(argv[i], "--app-id") == 0 )
{
if ( i == argc - 1 )
{
fputs("ERROR: '--app-id' needs an output name.\n", stderr);
ret = EXIT_FAILURE;
goto cleanup;
}
if ( app_id != NULL )
{
fputs("ERROR: '--app-id' may only be used once.\n", stderr);
ret = EXIT_FAILURE;
goto cleanup;
}
app_id = strdup(argv[i+1]);
if ( app_id == NULL )
{
fprintf(stderr, "ERROR: strdup: %s\n", strerror(errno));
ret = EXIT_FAILURE;
goto cleanup;
}
i++;
}
else if ( strcmp(argv[i], "--exec") == 0 )
{
if ( i == argc - 1 )
{
fputs("ERROR: '--exec' needs an output name.\n", stderr);
ret = EXIT_FAILURE;
goto cleanup;
}
if ( cmd != NULL )
{
fputs("ERROR: '--exec' may only be used once.\n", stderr);
ret = EXIT_FAILURE;
goto cleanup;
}
cmd = strdup(argv[i+1]);
if ( cmd == NULL )
{
fprintf(stderr, "ERROR: strdup: %s\n", strerror(errno));
ret = EXIT_FAILURE;
goto cleanup;
}
i++;
}
else
{
fprintf(stderr, "ERROR: Unknown option '%s'\n", argv[i]);
ret = EXIT_FAILURE;
goto cleanup;
}
}
/* We query the display name here instead of letting wl_display_connect()
* figure it out itself, because libwayland (for legacy reasons) falls
* back to using "wayland-0" when $WAYLAND_DISPLAY is not set, which is
* generally not desirable.
*/
const char *display_name = getenv("WAYLAND_DISPLAY");
if ( display_name == NULL )
{
fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr);
return EXIT_FAILURE;
}
wl_display = wl_display_connect(display_name);
if ( wl_display == NULL )
{
fputs("ERROR: Can not connect to wayland display.\n", stderr);
return EXIT_FAILURE;
}
wl_registry = wl_display_get_registry(wl_display);
wl_registry_add_listener(wl_registry, ®istry_listener, NULL);
sync_callback = wl_display_sync(wl_display);
wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL);
while ( loop && wl_display_dispatch(wl_display) > 0 );
if ( sync_callback != NULL )
wl_callback_destroy(sync_callback);
if ( xdg_activation_token != NULL )
xdg_activation_token_v1_destroy(xdg_activation_token);
if ( xdg_activation != NULL )
xdg_activation_v1_destroy(xdg_activation);
if ( wl_registry != NULL )
wl_registry_destroy(wl_registry);
wl_display_disconnect(wl_display);
cleanup:
if ( app_id != NULL )
free(app_id);
if ( cmd != NULL )
free(cmd);
return ret;
}