/** @file dummy_compositor.c
*
* Tool for creating a mid compositor hand off stop gap
*
* Copyright (c) 2019 - 2023 Jolla Ltd.
*
* @author Simo Piiroinen
*
* mce is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* mce 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mce. If not, see .
*/
#include "../mce-dbus.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../dbus-gmain/dbus-gmain.h"
/* ========================================================================= *
* Constants
* ========================================================================= */
/** How long to delay exit after succesful D-Bus name acquisition */
#define DC_EXIT_DELAY_MS 500
/* ========================================================================= *
* Macros
* ========================================================================= */
#define dc_log_emit(LEV,FMT,ARGS...)\
do { \
if( dc_log_p(LEV) ) {\
dc_log_emit_real(LEV, FMT, ##ARGS);\
}\
} while(0)
#define dc_log_crit( FMT,ARGS...) dc_log_emit(LOG_CRIT, FMT, ##ARGS)
#define dc_log_err( FMT,ARGS...) dc_log_emit(LOG_ERR, FMT, ##ARGS)
#define dc_log_warn( FMT,ARGS...) dc_log_emit(LOG_WARNING, FMT, ##ARGS)
#define dc_log_notice(FMT,ARGS...) dc_log_emit(LOG_NOTICE, FMT, ##ARGS)
#define dc_log_info( FMT,ARGS...) dc_log_emit(LOG_INFO, FMT, ##ARGS)
#define dc_log_debug( FMT,ARGS...) dc_log_emit(LOG_DEBUG, FMT, ##ARGS)
/* ========================================================================= *
* Types
* ========================================================================= */
/** Diagnostic logging targets */
typedef enum
{
/** Use syslog() */
DC_LOG_TO_SYSLOG = 0,
/** Write to stderr */
DC_LOG_TO_STDERR = 1,
} dc_log_to_t;
/* ========================================================================= *
* Prototypes
* ========================================================================= */
/* ------------------------------------------------------------------------- *
* UTILITY
* ------------------------------------------------------------------------- */
static const char *dc_name_repr (const char *name);
static const char *dc_bool_repr (bool val);
static bool dc_equal_p (const char *s1, const char *s2);
static void dc_print_usage (void);
static void dc_print_version(void);
/* ------------------------------------------------------------------------- *
* DC_LOG
* ------------------------------------------------------------------------- */
static int dc_log_clip_level(int level);
static const char *dc_log_get_name (void);
static void dc_log_set_name (const char *name);
static const char *dc_log_level_repr(int level);
static int dc_log_get_level (void);
static void dc_log_set_level (int level);
static bool dc_log_p (int level);
static void dc_log_emit_real (int level, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
/* ------------------------------------------------------------------------- *
* DC_DBUS
* ------------------------------------------------------------------------- */
static bool dc_dbus_parse_message (DBusMessage *msg, int type, ...);
static void dc_dbus_handle_name_lost_signal (DBusMessage *sig);
static void dc_dbus_handle_name_acquired_signal (DBusMessage *sig);
static void dc_dbus_handle_name_owner_changed_signal (DBusMessage *sig);
static void dc_dbus_handle_disconnected_signal (DBusMessage *sig);
static DBusMessage *dc_dbus_handle_set_updates_enabled_method_call (DBusMessage *req);
static DBusMessage *dc_dbus_handle_get_topmost_window_pid_method_call(DBusMessage *req);
static DBusHandlerResult dc_dbus_message_filter_cb (DBusConnection *con, DBusMessage *msg, void *aptr);
static bool dc_dbus_connect (void);
static bool dc_dbus_connected (void);
static void dc_dbus_disconnect (void);
static bool dc_dbus_reserve_name (void);
static bool dc_dbus_release_name (void);
/* ------------------------------------------------------------------------- *
* DC_MAINLOOP
* ------------------------------------------------------------------------- */
int dc_mainloop_run (void);
void dc_mainloop_exit (int exit_code);
static gboolean dc_mainloop_delayed_exit_cb (gpointer aptr);
static void dc_mainloop_schedule_delayed_exit(void);
static void dc_mainloop_cancel_delayed_exit (void);
/* ------------------------------------------------------------------------- *
* MAIN
* ------------------------------------------------------------------------- */
int main(int ac, char **av);
/* ========================================================================= *
* UTILITY
* ========================================================================= */
/** Human readable representation for D-Bus names
*
* @param name D-Bus name string, or NULL
*
* @return Valid human readable C-string
*/
static const char *
dc_name_repr(const char *name)
{
return !name ? "" : !*name ? "" : name;
}
/** Human readable representation for boolean values
*
* @param val Boolean value
*
* @return Human readable C-string
*/
static const char *
dc_bool_repr(bool val)
{
return val ? "true" : "false";
}
/** String equality predicate
*
* @param s1 First string, or NULL
* @param s2 Second string. or NULL
*
* @return true if both strings are non-null and equal, false otherwise
*/
static bool
dc_equal_p(const char *s1, const char *s2)
{
return s1 && s2 && !strcmp(s1, s2);
}
/* ========================================================================= *
* DC_LOG
* ========================================================================= */
/** Cached application name */
static const char *dc_log_name = 0;
/** Currently chosen verbosity level */
static int dc_log_level = LOG_WARNING;
/** Currently chosen diagnostic logging target */
static dc_log_to_t dc_log_to = DC_LOG_TO_SYSLOG;
/** Normalize logging verbosity level to expected range
*
* @param level syslog() compatible logging levels
*
* @return the given level clipped to [LOG_CRIT ... LOG_DEBUG] range
*/
static int
dc_log_clip_level(int level)
{
if( level < LOG_CRIT )
level = LOG_CRIT;
else if( level > LOG_DEBUG )
level = LOG_DEBUG;
return level;
}
/** Get application name to use for diagnostic logging
*
* @returns value set via #dc_log_set_name(), or "unnamed"
*/
static const char *
dc_log_get_name(void)
{
return dc_log_name ?: "unnamed";
}
/** Set application name to use for diagnostic logging
*
* @note The name is used as is and thus must be valid for the whole
* lifetime of the process / until changed.
*
* @param name name string
*/
static void
dc_log_set_name(const char *name)
{
dc_log_name = name;
}
/** Get verbosity level indicator tag for use in diagnostic logging
*
* @param level Message verbosity level
*
* @return Single character verbosity level indicator string
*/
static const char *
dc_log_level_repr(int level)
{
const char *tag = "?";
switch( dc_log_clip_level(level) ) {
case LOG_EMERG: tag = "X"; break;
case LOG_ALERT: tag = "A"; break;
case LOG_CRIT: tag = "C"; break;
case LOG_ERR: tag = "E"; break;
case LOG_WARNING: tag = "W"; break;
case LOG_NOTICE: tag = "N"; break;
case LOG_INFO: tag = "I"; break;
case LOG_DEBUG: tag = "D"; break;
default: break;
}
return tag;
}
/** Get current verbosity level
*
* @return verbosity level
*/
static int
dc_log_get_level(void)
{
return dc_log_level;
}
/** Set current verbosity level
*
* @param level syslog() compatible verbosity level
*/
static void
dc_log_set_level(int level)
{
dc_log_level = dc_log_clip_level(level);
}
/** Message logging predicate
*
* @param level syslog() compatible verbosity level
*
* @return true if logging at given verbosity is allowed, false otherwise
*/
static bool
dc_log_p(int level)
{
return level <= dc_log_level;
}
/** Emit diagnostic message
*
* @note This function should not be called directly, but via
* macros like dc_log_err() etc.
*
* @param level syslog() compatible verbosity level
* @param fmt printf() compatible format string
* @param ... Arguments required by format string
*/
static void __attribute__((format(printf, 2, 3)))
dc_log_emit_real(int level, const char *fmt, ...)
{
if( dc_log_p(level) ) {
int saved = errno;
va_list va;
va_start(va, fmt);
char *msg = 0;
if( vasprintf(&msg, fmt, va) < 0 )
msg = 0;
va_end(va);
if( dc_log_to == DC_LOG_TO_SYSLOG ) {
syslog(level, "%s", msg ?: fmt);
}
else {
fprintf(stderr, "%s: %s: %s\n",
dc_log_get_name(),
dc_log_level_repr(level),
msg ?: fmt);
fflush(stderr);
}
free(msg);
errno = saved;
}
}
/* ========================================================================= *
* DC_DBUS
* ========================================================================= */
static bool dc_exit_on_enable = false;
static bool dc_release_name = false;
static bool dc_name_released = false;
static unsigned dc_setup_actions = COMPOSITOR_ACTION_NONE;
/** Cached system bus connection */
static DBusConnection *dc_dbus_con = 0;
/** Signal match rules to add on connect */
static const char * const dc_dbus_matches[] =
{
"type=signal"
",interface='"DBUS_INTERFACE_DBUS"'"
",path='"DBUS_PATH_DBUS"'"
",member='NameOwnerChanged'"
",arg0='"COMPOSITOR_SERVICE"'",
0
};
/** Helper for parsing dbus message arguments
*
* Can be used as replacement for dbus_message_get_args() when
* caller is not interested in possible error details.
*
* @param msg D-Bus message to parse
* @param type Type of the 1st argument to parse
* @para ... as with dbus_message_get_args()
*
* @returns true if all arguments were succesfully parsed, false otherwise
*/
static bool
dc_dbus_parse_message(DBusMessage *msg, int type, ...)
{
DBusError err = DBUS_ERROR_INIT;
va_list va;
va_start(va, type);
bool ack = dbus_message_get_args_valist(msg, &err, type, va);
va_end(va);
if( !ack )
dc_log_err("parse error: %s: %s", err.name, err.message);
dbus_error_free(&err);
return ack;
}
/** Handle incoming org.freedesktop.DBus.NameLost signals
*
* @param sig D-Bus signal message
*/
static void
dc_dbus_handle_name_lost_signal(DBusMessage *sig)
{
const char *name = 0;
if( !dc_dbus_parse_message(sig,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID) )
goto EXIT;
dc_log_info("name lost: %s", dc_name_repr(name));
if( dc_equal_p(name, COMPOSITOR_SERVICE) ) {
/* Something took name ownership from us
* -> expected when delayed exit is disabled
* -> assume success
* -> exit immediately
*/
if( !dc_release_name )
dc_mainloop_exit(EXIT_SUCCESS);
}
EXIT:
return;
}
/** Handle incoming org.freedesktop.DBus.NameAcquired signals
*
* @param sig D-Bus signal message
*/
static void
dc_dbus_handle_name_acquired_signal(DBusMessage *sig)
{
const char *name = 0;
if( !dc_dbus_parse_message(sig,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_INVALID) )
goto EXIT;
dc_log_info("name acquired: %s", dc_name_repr(name));
if( dc_equal_p(name, COMPOSITOR_SERVICE) ) {
/* We gained name ownership
* -> success
* -> exit (after brief delay)
*/
if( !dc_exit_on_enable )
dc_mainloop_schedule_delayed_exit();
}
EXIT:
return;
}
/** Handle incoming org.freedesktop.DBus.NameOwnerChanged signals
*
* @param sig D-Bus signal message
*/
static void
dc_dbus_handle_name_owner_changed_signal(DBusMessage *sig)
{
const char *name = 0;
const char *prev = 0;
const char *curr = 0;
if( !dc_dbus_parse_message(sig,
DBUS_TYPE_STRING, &name,
DBUS_TYPE_STRING, &prev,
DBUS_TYPE_STRING, &curr,
DBUS_TYPE_INVALID) )
goto EXIT;
dc_log_info("name owner changed: %s: %s -> %s",
dc_name_repr(name),
dc_name_repr(prev),
dc_name_repr(curr));
if( dc_equal_p(name, COMPOSITOR_SERVICE) ) {
if( dc_equal_p(curr, "") ) {
/* Compositor has no name owner
* -> unexpected, but assume success
* -> exit immediately
*/
if( !dc_release_name )
dc_mainloop_exit(EXIT_SUCCESS);
}
}
EXIT:
return;
}
/** Handle dispatched org.freedesktop.DBus.Local.Disconnected signals
*
* @param sig D-Bus signal message
*/
static void
dc_dbus_handle_disconnected_signal(DBusMessage *sig)
{
(void)sig;
/* While we expect to get terminated/killed, make
* orderly exit also when/if systembus dies.
*/
dc_mainloop_exit(EXIT_FAILURE);
}
/** Handle incoming compositor setUpdatesEnabled method calls
*
* @param req D-Bus method call message
*
* @return D-Bus method call reply message
*/
static DBusMessage *
dc_dbus_handle_set_updates_enabled_method_call(DBusMessage *req)
{
DBusMessage *rsp = 0;
dbus_bool_t enabled = false;
if( !dc_dbus_parse_message(req,
DBUS_TYPE_BOOLEAN, &enabled,
DBUS_TYPE_INVALID) )
goto EXIT;
dc_log_debug("set_updates_enabled(%s)", dc_bool_repr(enabled));
if( enabled && dc_exit_on_enable ) {
/* We have gained name ownership and
* mce gave us permission to draw
* -> exit
*/
dc_mainloop_schedule_delayed_exit();
}
EXIT:
rsp = dbus_message_new_method_return(req);
return rsp;
}
/** Handle incoming privateGetSetupActions method calls
*
* @param req D-Bus method call message
*
* @return D-Bus method call reply message
*/
static DBusMessage *
dc_dbus_handle_get_setup_actions_method_call(DBusMessage *req)
{
DBusMessage *rsp = NULL;
dbus_uint32_t flags = dc_setup_actions;
if( !(rsp = dbus_message_new_method_return(req)) )
goto EXIT;
dbus_message_append_args(rsp,
DBUS_TYPE_UINT32, &flags,
DBUS_TYPE_INVALID);
dc_log_debug("get_setup_actions() -> 0x%x", (unsigned)flags);
EXIT:
return rsp;
}
/** Handle incoming compositor privateTopmostWindowProcessId method calls
*
* @param req D-Bus method call message
*
* @return D-Bus method call reply message
*/
static DBusMessage *
dc_dbus_handle_get_topmost_window_pid_method_call(DBusMessage *req)
{
DBusMessage *rsp = 0;
rsp = dbus_message_new_method_return(req);
dbus_int32_t pid = getpid();
dbus_message_append_args(rsp,
DBUS_TYPE_INT32, &pid,
DBUS_TYPE_INVALID);
dc_log_debug("get_topmost_window_pid() -> %d", (int)pid);
return rsp;
}
/** System bus message filter callback
*
* @param con D-Bus connection
* @param msg Incoming D-Bus message
* @param aptr (unused) User data pointer
*
* @return DBUS_HANDLER_RESULT_HANDLED if msg was method call message
* handled by the filter, DBUS_HANDLER_RESULT_NOT_YET_HANDLED
* otherwise
*/
static DBusHandlerResult
dc_dbus_message_filter_cb(DBusConnection *con, DBusMessage *msg, void *aptr)
{
(void)aptr;
DBusHandlerResult res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
DBusMessage *rsp = 0;
const char *path = dbus_message_get_path(msg);
const char *ifce = dbus_message_get_interface(msg);
const char *memb = dbus_message_get_member(msg);
int type = dbus_message_get_type(msg);
if( type == DBUS_MESSAGE_TYPE_SIGNAL ) {
if( dc_equal_p(path, DBUS_PATH_DBUS) &&
dc_equal_p(ifce, DBUS_INTERFACE_DBUS) ) {
if( dc_equal_p(memb, "NameLost") )
dc_dbus_handle_name_lost_signal(msg);
else if( dc_equal_p(memb, "NameAcquired") )
dc_dbus_handle_name_acquired_signal(msg);
else if( dc_equal_p(memb, "NameOwnerChanged") )
dc_dbus_handle_name_owner_changed_signal(msg);
}
else if( dc_equal_p(path, DBUS_PATH_LOCAL) &&
dc_equal_p(ifce, DBUS_INTERFACE_LOCAL) ) {
if( dc_equal_p(memb, "Disconnected") )
dc_dbus_handle_disconnected_signal(msg);
}
}
else if( type == DBUS_MESSAGE_TYPE_METHOD_CALL ) {
const char *dest = dbus_message_get_destination(msg);
if( dc_equal_p(dest, COMPOSITOR_SERVICE) &&
dc_equal_p(path, COMPOSITOR_PATH) &&
dc_equal_p(ifce, COMPOSITOR_IFACE) ) {
if( dc_equal_p(memb, COMPOSITOR_SET_UPDATES_ENABLED) )
rsp = dc_dbus_handle_set_updates_enabled_method_call(msg);
else if( dc_equal_p(memb, COMPOSITOR_GET_TOPMOST_WINDOW_PID) )
rsp = dc_dbus_handle_get_topmost_window_pid_method_call(msg);
else if( dc_equal_p(memb, COMPOSITOR_GET_SETUP_ACTIONS) )
rsp = dc_dbus_handle_get_setup_actions_method_call(msg);
}
}
if( rsp ) {
res = DBUS_HANDLER_RESULT_HANDLED;
if( !dbus_message_get_no_reply(msg) )
dbus_connection_send(con, rsp, 0);
dbus_message_unref(rsp);
}
return res;
}
/** Connect to D-Bus SystemBus
*
* @return true on success, false otherwise
*/
static bool
dc_dbus_connect(void)
{
DBusError err = DBUS_ERROR_INIT;
int bus = DBUS_BUS_SYSTEM;
/* Already done? */
if( dc_dbus_con )
goto EXIT;
dc_log_debug("dbus connect");
if( !(dc_dbus_con = dbus_bus_get(bus, &err)) ) {
dc_log_err("dbus connect failed: %s: %s",
err.name, err.message);
goto EXIT;
}
if( !dbus_connection_add_filter(dc_dbus_con,
dc_dbus_message_filter_cb, 0, 0) )
goto EXIT;
for( size_t i = 0; dc_dbus_matches[i]; ++i )
dbus_bus_add_match(dc_dbus_con, dc_dbus_matches[i], 0);
dbus_connection_set_exit_on_disconnect(dc_dbus_con, false);
dbus_gmain_set_up_connection(dc_dbus_con, 0);
EXIT:
dbus_error_free(&err);
return dc_dbus_con != 0;
}
/** Connected to SystemBus predicate
*
* If dbus daemon should get killed / something similar, we are left with
* valid dbus connection object that is in disconnected state - any attempt
* to do IPC over such connection generates a lot of debugging noise and/or
* libdbus calls exit.
*
* @return true when a connection exists and is alive, false otherwise
*/
static bool
dc_dbus_connected(void)
{
return (dc_dbus_con &&
dbus_connection_get_is_connected(dc_dbus_con));
}
/** Disconnect from D-Bus SystemBus
*/
static void
dc_dbus_disconnect(void)
{
if( dc_dbus_con ) {
dc_log_debug("dbus disconnect");
/* Detach filter callback */
dbus_connection_remove_filter(dc_dbus_con,
dc_dbus_message_filter_cb, 0);
if( dbus_connection_get_is_connected(dc_dbus_con) ) {
for( size_t i = 0; dc_dbus_matches[i]; ++i )
dbus_bus_remove_match(dc_dbus_con, dc_dbus_matches[i], 0);
/* Note: Releasing D-Bus name is intentionally
* handled implicitly i.e. when process
* exits and drops out from system bus.
*/
}
dbus_connection_unref(dc_dbus_con),
dc_dbus_con = 0;
}
}
/** Request ownership of COMPOSITOR_SERVICE D-Bus name
*
* @return false if request was outright denied, true otherwise
*/
static bool
dc_dbus_reserve_name(void)
{
DBusError err = DBUS_ERROR_INIT;
bool ack = false;
if( !dc_dbus_connected() )
goto EXIT;
int flags = 0;
flags |= DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
flags |= DBUS_NAME_FLAG_REPLACE_EXISTING;
int rc = dbus_bus_request_name(dc_dbus_con,
COMPOSITOR_SERVICE,
flags, &err);
switch( rc ) {
case DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER:
/* A NameAcquired signal should arrive shortly */
dc_log_debug("primary name owner");
break;
case DBUS_REQUEST_NAME_REPLY_IN_QUEUE:
/* A NameAcquired signal should eventually arrive */
dc_log_debug("queued for name ownership");
break;
default:
/* We are not going to get name ownerhip */
dc_log_err("reserving dbus name failed");
goto EXIT;
}
ack = true;
EXIT:
dbus_error_free(&err);
return ack;
}
/** Release ownership of COMPOSITOR_SERVICE D-Bus name
*
* @return true on success, false otherwise
*/
static bool
dc_dbus_release_name(void)
{
DBusError err = DBUS_ERROR_INIT;
bool ack = false;
if( !dc_dbus_connected() )
goto EXIT;
int rc = dbus_bus_release_name(dc_dbus_con,
COMPOSITOR_SERVICE,
&err);
switch( rc ) {
case DBUS_RELEASE_NAME_REPLY_RELEASED:
/* A NameLost signal should arrive shortly */
dc_log_debug("name released");
break;
case DBUS_RELEASE_NAME_REPLY_NOT_OWNER:
dc_log_err("can't release name: not owner");
goto EXIT;
case DBUS_RELEASE_NAME_REPLY_NON_EXISTENT:
dc_log_err("can't release name: does not exist");
goto EXIT;
default:
dc_log_err("releasing dbus name failed");
goto EXIT;
}
ack = true;
EXIT:
dbus_error_free(&err);
return ack;
}
/* ========================================================================= *
* DC_MAINLOOP
* ========================================================================= */
/** Exit code applicable after leaving mainloop */
static int dc_mainloop_exit_code = EXIT_SUCCESS;
/** Delayed exit timer id */
static guint dc_mainloop_delayed_exit_id = 0;
/** Delayed exit timer duration */
static int dc_mainloop_exit_delay = DC_EXIT_DELAY_MS;
/** Currently active mainloop */
static GMainLoop *dc_mainloop_handle = 0;
/** Run application mainloop
*
* @return exit code to use on process exit
*/
int
dc_mainloop_run(void)
{
dc_mainloop_handle = g_main_loop_new(0, 0);
g_main_loop_run(dc_mainloop_handle);
g_main_loop_unref(dc_mainloop_handle), dc_mainloop_handle = 0;
return dc_mainloop_exit_code;
}
/** Stop application mainloop
*
* @param exit_code exit code to use on process exit
*/
void
dc_mainloop_exit(int exit_code)
{
if( dc_mainloop_exit_code < exit_code )
dc_mainloop_exit_code = exit_code;
if( !dc_mainloop_handle ) {
dc_log_crit("exit from mainloop without mainloop; exit immediately");
_exit(dc_mainloop_exit_code);
}
g_main_loop_quit(dc_mainloop_handle);
}
/** Delayed exit timer callback
*
* @param aptr (unused) User data pointer
*
* @return G_SOURCE_REMOVE (to stop timer from repeating)
*/
static gboolean
dc_mainloop_delayed_exit_cb(gpointer aptr)
{
(void)aptr;
if( dc_release_name && !dc_name_released ) {
dc_name_released = true;
dc_dbus_release_name();
return G_SOURCE_CONTINUE;
}
dc_log_debug("delayed exit: triggered");
dc_mainloop_delayed_exit_id = 0;
dc_mainloop_exit(EXIT_SUCCESS);
return G_SOURCE_REMOVE;
}
/** Schedule delayed exit
*/
static void
dc_mainloop_schedule_delayed_exit(void)
{
if( !dc_mainloop_delayed_exit_id && dc_mainloop_exit_delay >= 0 ) {
dc_log_debug("delayed exit: scheduled");
dc_mainloop_delayed_exit_id
= g_timeout_add(dc_mainloop_exit_delay,
dc_mainloop_delayed_exit_cb,
0);
}
}
/** Cancel scheduled delayed exit
*/
static void
dc_mainloop_cancel_delayed_exit(void)
{
if( dc_mainloop_delayed_exit_id ) {
dc_log_debug("delayed exit: canceled");
g_source_remove(dc_mainloop_delayed_exit_id),
dc_mainloop_delayed_exit_id = 0;
}
}
/* ========================================================================= *
* MAIN
* ========================================================================= */
/** Output application usage information
*/
static void
dc_print_usage(void)
{
static const char format[] =
"NAME\n"
" %s - dummy Sailfish OS Compositor D-Bus service\n"
"\n"
"SYNOPSIS\n"
" %s [options]\n"
"\n"
"DESCRIPTION\n"
" Attempts to acquire compositor service D-Bus name and then\n"
" exits either immediately or after a brief delay.\n"
"\n"
" Normally switching from one compositor to another happens\n"
" so that compositor A (such as unlock ui) allows replacing\n"
" dbus service name owner and compositor B (e.g. lipstick)\n"
" takes over display management by acquiring the D-Bus name.\n"
"\n"
" In situations where compositor A does not work / interferes\n"
" with android services while compositor B requires the android\n"
" services to function properly, dummy compositor service can\n"
" be used as a stop gap where relevant android services are\n"
" started and/or stopped as required.\n"
"\n"
"OPTIONS\n"
" -h --help Print usage information.\n"
" -V --version Print version information.\n"
" -v --verbose Increase program verbosity.\n"
" -q --quiet Decrease program verbosity.\n"
" -s --force-syslog Use syslog for logging.\n"
" -T --force-stderr Use stderr for logging.\n"
" -d --exit-delay= Set successful exit delay [ms].\n"
" -e --exit-on-enable Exit on setUpdatesEnabled(true).\n"
" -r --release-name Release name before exiting.\n"
" --hwc-stop Stop hwc service before enabling updates.\n"
" --hwc-start Start hwc service before enabling updates.\n"
" --hwc-restart Re-start hwc service before enabling updates.\n"
"\n";
const char *name = dc_log_get_name();
fprintf(stderr, format, name, name);
}
/** Output application version information
*/
static void
dc_print_version(void)
{
const char *name = dc_log_get_name();
const char *vers = G_STRINGIFY(PRG_VERSION);
fprintf(stdout, "%s %s\n", name, vers);
}
/** Application main entry point
*/
int
main(int ac, char **av)
{
static struct option opt_long[] = {
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'V' },
{ "verbose", no_argument, 0, 'v' },
{ "quiet", no_argument, 0, 'q' },
{ "exit-delay", required_argument, 0, 'd' },
{ "exit-on-enable", no_argument, 0, 'e' },
{ "release-name", no_argument, 0, 'r' },
{ "force-syslog", no_argument, 0, 's' },
{ "force-stderr", no_argument, 0, 'T' },
{ "hwc-stop", no_argument, 0, 901 },
{ "hwc-start", no_argument, 0, 902 },
{ "hwc-restart", no_argument, 0, 903 },
{ 0, 0, 0, 0 }
};
static const char opt_short[] = "hVvqd:ersT";
int xc = EXIT_FAILURE;
dc_log_debug("parse arguments");
dc_log_set_name(basename(*av));
int level = dc_log_get_level();
for( ;; ) {
int opt = getopt_long(ac, av, opt_short, opt_long, 0);
if( opt == -1 )
break;
switch( opt ) {
case 'h':
dc_print_usage();
exit(EXIT_SUCCESS);
case 'V':
dc_print_version();
exit(EXIT_SUCCESS);
case 'v':
++level;
break;
case 'q':
--level;
break;
case 'd':
dc_mainloop_exit_delay = strtol(optarg, 0, 0);
break;
case 'e':
dc_exit_on_enable = true;
break;
case 'r':
dc_release_name = true;
break;
case 's':
dc_log_to = DC_LOG_TO_SYSLOG;
break;
case 'T':
dc_log_to = DC_LOG_TO_STDERR;
break;
case 901:
dc_setup_actions |= COMPOSITOR_ACTION_STOP_HWC;
break;
case 902:
dc_setup_actions |= COMPOSITOR_ACTION_START_HWC;
break;
case 903:
dc_setup_actions |= COMPOSITOR_ACTION_RESTART_HWC;
break;
case '?':
exit(EXIT_FAILURE);
default:
fprintf(stderr, "getopt() returned code %d\n", opt);
exit(EXIT_FAILURE);
}
}
dc_log_set_level(level);
dc_log_debug("initalize");
if( !dc_dbus_connect() )
goto EXIT;
if( !dc_dbus_reserve_name() )
goto EXIT;
dc_log_debug("enter mainloop");
xc = dc_mainloop_run();
dc_log_debug("leave mainloop");
EXIT:
dc_log_debug("cleanup");
dc_mainloop_cancel_delayed_exit();
dc_dbus_disconnect();
dc_log_info("exit %d", xc);
return xc;
}